lib/soywiki.vim in soywiki-0.0.9 vs lib/soywiki.vim in soywiki-0.1.1

- old
+ new

@@ -1,12 +1,11 @@ " Vim script that turns Vim into a personal wiki " Maintainer: Daniel Choi <dhchoi@gmail.com> " License: MIT License (c) 2011 Daniel Choi -" This regex matches namedspaced WikiWords, top-level WikiWords, and relative -" .WikiWords in a namespace -let s:wiki_link_pattern = '\C\<\([a-z][[:alnum:]_]\+\.\)\?[A-Z][a-z]\+[A-Z]\w*\>\|\.[A-Z][a-z]\+[A-Z]\w*\>' +" This regex matches namedspaced WikiWords and unqualified WikiWords +let s:wiki_link_pattern = '\C\<\([a-z][[:alnum:]_]\+\.\)\?[A-Z][a-z]\+[A-Z]\w*\>' let s:http_link_pattern = 'https\?:[^ >)\]]\+' let s:rename_links_command = 'soywiki-rename ' let s:find_pages_linking_in_command = 'soywiki-pages-linking-in ' let s:expand_command = 'soywiki-expand ' @@ -17,191 +16,221 @@ let string = substitute(a:string, '\s\+$', '', '') return substitute(string, '^\s\+', '', '') endfunc func! s:page_title() - let title_line = getline(1) - return s:trimString(title_line) + return substitute(bufname(''), '\/', '.', '') endfunc +func! s:display_missing_namespace_error(num_segments) + if a:num_segments == 1 + call s:error("Invalid wiki page: missing a namespace. Put it in a namespace subdirectory.") + elseif a:num_segments > 2 + call s:error("Invalid wiki page: nested too deeply. Namespaces are limited to one level.") + endif +endfunc + +func! s:display_invalid_wiki_word_error(word) + call s:error(a:word . " is not a valid WikiWord.") +endfunc + +func! s:namespace_of_title(page_title) + let segments = split(a:page_title, '\.') + " page must have namespace + if len(segments) == 2 + return get(segments, 0) + else + call s:display_missing_namespace_error(len(segments)) + endif +endfunc + func! s:page_namespace() - let segments = split(s:page_title(), '\.') - return get(segments, 0) + return s:namespace_of_title(s:page_title()) endfunc func! s:title_without_namespace(page_title) - if len(split(a:page_title, '\.')) == 2 - return "." . get(split(a:page_title, '\.'), 1) + let segments = split(a:page_title, '\.') + if len(segments) == 2 + return "." . get(segments, 1) else - return a:page_title + call s:display_missing_namespace_error(len(segments)) endif endfunc -func! s:namespace_of_title(page_title) - if len(split(a:page_title, '\.')) == 2 - return get(split(a:page_title, '\.'), 0) +" returns 1 or 0 +func! s:has_namespace(link) + return (match(a:link, '\a\.') != -1) +endfunc + +" adds current page's namespace to the link +func! s:infer_namespace(link) + if s:has_namespace(s:filename2pagetitle(a:link)) + return s:filename2pagetitle(a:link) else - "" + let x = s:page_namespace() . "." . a:link + return x endif endfunc +func! s:valid_wiki_word(link) + return (match(a:link, s:wiki_link_pattern) == 0) +endfunc func! s:is_wiki_page() - return (match(getline(1), s:wiki_link_pattern) == 0) + return s:valid_wiki_word(s:page_title()) endfunc -func! s:page_title2file(page) +func! s:pagetitle2file(page) return substitute(a:page, '\.', '/', 'g') endfunc func! s:filename2pagetitle(page) return substitute(a:page, '/', '.', 'g') endfunc func! s:list_pages() let s:search_for_link = "" - call s:get_page_list() - call s:page_list_window("CompletePageInSelectionWindow", "Select page: ") + call s:page_list_window(s:get_page_list(), "Select page: ") endfunc +" returns a fully namespaced link func! s:link_under_cursor() let link = expand("<cWORD>") " strip off non-letters at the end (e.g., a comma) let link = substitute(link, '[^[:alnum:]]*$', '', '') - " see if we have a link relative to the namespace - if (match(link, '^\.')) == 0 - " find the namespace from the page title - let link = s:page_namespace() . link " this link already has a period at the beginning + if ! s:has_namespace(link) + let link = s:infer_namespace(link) endif - let link = substitute(link, '^[^\.[:alnum:]]', '', '') " link may begin with period - return link + if match(link, s:wiki_link_pattern) == -1 + if match(link, s:http_link_pattern) != -1 + call s:open_href() + endif + return "" + else + return link + end endfunc " follows a camel case link to a new page func! s:follow_link(split) let link = s:link_under_cursor() - if match(link, s:wiki_link_pattern) == -1 + if link == "" let link = s:find_next_wiki_link(0) + if link == "" + return "" + endif endif call s:load_page(link, a:split) endfunc func! s:follow_link_under_cursor(split) let link = s:link_under_cursor() - if match(link, s:wiki_link_pattern) == -1 - echom "Not a wiki link" - return + if link == "" + echom link . " is not a wiki link" + return "" + else + call s:load_page(link, a:split) endif - call s:load_page(link, a:split) endfunc func! s:find_next_wiki_link(backward) let n = 0 - let result = search(s:wiki_link_pattern, 'w' . (a:backward == 1 ? 'b' : '')) + " don't wrap + let result = search(s:wiki_link_pattern, 'W' . (a:backward == 1 ? 'b' : '')) if (result == 0) - return + return "" end return s:link_under_cursor() endfunc func! s:load_page(page, split) if (s:is_wiki_page()) write endif - - let file = s:page_title2file(a:page) - + let file = s:pagetitle2file(a:page) + let title = s:filename2pagetitle(a:page) if (!filereadable(file)) " create the file let namespace = s:namespace_of_title(a:page) - if len(namespace) > 0 - call system("mkdir -p " . namespace) - endif - call writefile([a:page, '', ''], file) + call system("mkdir -p " . namespace) + call writefile([title, '', ''], file) endif if (a:split == 2) exec "vsplit ". file else exec "split ". file endif if (a:split == 0) wincmd p close endif - if len(s:search_for_link) > 0 - let res = search(s:search_for_link, 'cw') + let res = search(s:search_for_link, 'cw') let s:search_for_link = '' endif endfunc -func! s:load_most_recently_modified_page() +func! s:load_most_recently_modified_page(index) let pages = split(system(s:ls_command), "\n") - let start_page = len(pages) > 0 ? get(pages, 0) : "HomePage" + let start_page = len(pages) > a:index ? get(pages, a:index) : "main.HomePage" call s:load_page(start_page, 0) endfunc func! s:delete_page() let file = bufname('%') let bufnr = bufnr('%') - call delete(file) - call system("git commit " . bufname('%') . " -m 'deletion'") + " go to most recently saved - let target = s:trimString(system(s:ls_command . " | head -1")) - exec "e " . target + " this should be a function call + split + call s:load_most_recently_modified_page(1) + wincmd p + + echo system("git rm " . file) + call system("git commit " . file . " -m 'deletion'") exec "bdelete " . bufnr redraw echom "Deleted " . file - call s:load_most_recently_modified_page() endfunc -func! s:prompt_for_wiki_word(prompt, default) - let input = s:trimString(input(a:prompt, a:default)) - while match(input, s:wiki_link_pattern) == -1 - let input = s:trimString(input("Must be a WikiWord! Press CTRL-c to cancel. " . a:prompt , a:default)) - endwhile - return input -endfunc - -func! s:rename_page() - let oldfile = bufname('%') - let newfile = s:page_title2file( s:prompt_for_wiki_word("Rename oldfile: ", l:oldfile) ) - if (oldfile == newfile) - echo "Canceled" - return - endif +func! s:rename_page(page_path_or_title) + let page_title = s:infer_namespace(a:page_path_or_title) + let newfile = s:pagetitle2file(page_title) if (filereadable(newfile)) exe "echom '" . newfile . " already exists!'" return endif - call system("git mv " . l:oldfile . " " . newfile) - exec "e ". newfile - " replace all existing inbound links - " TODO replace this with a ruby script - exec "! " . s:rename_links_command . oldfile . " " . newfile - call system("git commit -am 'rename wiki page'") - e! + if s:valid_wiki_word(page_title) + let original_file = bufname('') + echo system("git mv " . original_file . " " . newfile) + exec "!" . s:rename_links_command . original_file . " " . newfile + call system("git commit -am 'rename wiki page and links'") + exec "e " . newfile + else + call s:display_invalid_wiki_word_error(page_title) + endif endfunc -func! s:create_page() - let title = s:prompt_for_wiki_word("New page title: ", "") - let newfile = s:page_title2file(title) - if (filereadable(newfile)) - exe "echom '" . newfile . " already exists!'" - return +func! s:create_page(page_path) + let page_title = s:infer_namespace(a:page_path) + let page_path = s:pagetitle2file(page_title) + if (filereadable(page_path)) + exe "echom '" . page_path . " already exists! Loaded.'" endif - call writefile([s:filename2pagetitle(title), '', ''], newfile) - exec "e ". newfile + if s:valid_wiki_word(page_title) + call s:load_page(s:filename2pagetitle(page_path), 0) + else + call s:display_invalid_wiki_word_error(page_title) + endif endfunc func! s:save_revision() call system("git add " . bufname('%')) call system("git commit " . bufname('%') . " -m 'edit'") endfunc func! s:show_revision_history(stat) - " maybe later allow --stat if (a:stat) exec ":!git log --stat " . bufname('%') else exec ":!git log --color-words -p " . bufname('%') end @@ -209,111 +238,100 @@ func! s:show_blame() exec ":! git blame --date=relative " . bufname('%') endfunc - " ------------------------------------------------------------------------------- " select Page +" This function both sets a script variable and returns the value. func! s:get_page_list() + " no file current in buffer if len(bufname('%')) == 0 - let s:page_list = split(system(s:ls_command), "\n") + return split(system(s:ls_command), "\n") else - let s:page_list = split(system(s:ls_command . " | grep -vF '" . bufname('%') . "'" ), "\n") + return split(system(s:ls_command . " | grep -vF '" . s:page_title() . "'" ), "\n") endif endfunction func! s:pages_in_this_namespace(pages) let namespace = s:page_namespace() - let pages = filter( a:pages, 'v:val =~ "^' . namespace . '"') + let pages = filter( a:pages, 'v:val =~ "^' . namespace . '\."') " strip leading namespace let pages = map( pages, "substitute(v:val, '^" . namespace . "\.', '', '') " ) return pages endfunc +" When user press TAB after typing a few characters in the page selection +" window, if the user started typing a namespace (which starts with a +" lowercase letter), try to complete it. Otherwise take no action. func! s:reduce_matches() if (!exists("s:matching_pages")) return endif let fragment = expand("<cWORD>") - let reduced_pages = filter( s:matching_pages, 'v:val =~ "^' . fragment . '"') " find the first namespace in the list let namespaced_matches = filter( s:matching_pages, 'v:val =~ "^' . fragment . '\."') if (len(namespaced_matches) == 0) return - elseif match(fragment, '^[A-Z]') == -1 && match(fragment, '\.' == -1) + elseif match(fragment, '^[a-z]') == 0 && match(fragment, '\.' == -1) " we're beginning to type a namespace let namespace = get(split(get(namespaced_matches, 0), '\.'), 0) let namespace .= "." - call feedkeys( "BcW". namespace. "\<C-x>\<C-u>\<C-p>" , "t") + call feedkeys( "BcW" . namespace . "\<C-x>\<C-u>\<C-p>" , "t") else - " we're tabbing to auto complete the term, not find a namespace return endif endfunc -function! s:page_list_window(complete_function, prompt) +function! s:page_list_window(page_match_list, prompt) " remember the original window let s:return_to_winnr = winnr() + let s:matching_pages = a:page_match_list topleft split page-list-buffer + setlocal completefunc=CompletePageTitle setlocal buftype=nofile setlocal noswapfile setlocal modifiable resize 1 inoremap <silent> <buffer> <cr> <Esc>:call <SID>select_page()<CR> inoremap <buffer> <Tab> <Esc>:call <SID>reduce_matches()<cr> noremap <buffer> q <Esc>:close<cr> inoremap <buffer> <Esc> <Esc>:close<cr> - exec "setlocal completefunc=" . a:complete_function " c-p clears the line call setline(1, a:prompt) normal $ call feedkeys("a\<c-x>\<c-u>\<c-p>", 't') " call feedkeys("a", 't') endfunction -function! CompletePage(findstart, base) - let s:matching_pages = s:page_list[:] - let possible_period = getline('.')[col('.') - 2] - if (possible_period == '.') - " filter to pages in this namespace +" This function assumes s:matching_pages has been set by the calling function +function! CompletePageTitle(findstart, base) + let fragment = expand("<cWORD>") + if !exists("s:matching_pages") + let s:matching_pages = s:get_page_list() + endif + if match(fragment, '^[A-Z]') == 0 + " we have a WikiWord without a namespace; filter down to pages in pages in this + " namespace let s:matching_pages = s:pages_in_this_namespace(s:matching_pages) endif if a:findstart " locate the start of the word - let line = getline('.') - let start = col('.') - 1 - while start > 0 && line[start - 1] =~ '[[:alnum:]]' - let start -= 1 - endwhile - return start - else - let base = s:trimString(a:base) - if (base == '') - return s:matching_pages + " Assume we're in a page select window if there is a ': ' in the line. + " Admittedly, this is not work well in all cases + if bufname('') == 'page-list-buffer' + " by starting after prompt ': ' + let start = match(getline('.'), ': ') + 2 else - let res = [] - for m in s:matching_pages - if m =~ '\c' . base - call add(res, m) - endif - endfor - return res - endif - endif -endfun - -function! CompletePageInSelectionWindow(findstart, base) - let s:matching_pages = s:page_list[:] - if a:findstart - " locate the start of the word - let line = getline('.') - let start = col('.') - 1 - while start > 0 && line[start - 1] =~ '[[:alnum:]\.]' - let start -= 1 - endwhile + " locate the start of the word + let line = getline('.') + let start = col('.') - 1 + while start > 0 && line[start - 1] =~ '\a' + let start -= 1 + endwhile + end return start else let base = s:trimString(a:base) if (base == '') return s:matching_pages @@ -351,47 +369,21 @@ " this logic could be more precise, in cases where pages have same name " in different namespaces func! s:list_pages_linking_in() let s:pages_linking_in = split(system(s:find_pages_linking_in_command . s:page_title()), "\n") - let s:search_for_link = s:title_without_namespace( s:page_title()) + " cursor should jump to this string after the selected page is loaded: + let s:search_for_link = s:title_without_namespace(s:page_title()) if len(s:pages_linking_in) == 1 call s:load_page(get(s:pages_linking_in, 0), 0) elseif len(s:pages_linking_in) == 0 echom "No pages link to " . s:page_title() . "!" else - call s:page_list_window("CompletePagesLinkingIn_InSelectionWindow", "Pages that link to " . s:page_title() . ": ") + call s:page_list_window(s:pages_linking_in, "Pages that link to " . s:page_title() . ": ") endif endfunc -function! CompletePagesLinkingIn_InSelectionWindow(findstart, base) - " todo, this must be smarter, deal with different namespaces - let s:matching_pages = s:pages_linking_in[:] - if a:findstart - " locate the start of the word - let line = getline('.') - let start = col('.') - 1 - while start > 0 && line[start - 1] =~ '[[:alnum:]\.]' - let start -= 1 - endwhile - return start - else - let base = s:trimString(a:base) - if (base == '') - return s:matching_pages - else - let res = [] - for m in s:matching_pages - if m =~ '\c' . base - call add(res, m) - endif - endfor - return res - endif - endif -endfun - "------------------------------------------------------------------------ " This appends the selected text (use visual-mode) to the page selected " in the page selection window. func! s:extract(...) range if a:0 != 3 @@ -416,10 +408,11 @@ silent exe "norm! :".first.",".last."change\<CR>".replacement."\<CR>.\<CR>" else " this one just deletes the line silent exe "norm! :".first.",".last."change\<CR>.\<CR>" endif + write! if bufnr(file) == -1 || bufwinnr(bufnr(file)) == -1 if !filereadable(file) " create the file let page_title = s:filename2pagetitle(file) let namespace = s:namespace_of_title(page_title) @@ -442,11 +435,10 @@ silent put end write! endfunc - func! s:error(str) echohl ErrorMsg echomsg a:str echohl None endfunction @@ -488,65 +480,67 @@ silent! normal 1G call s:highlight_wikiwords() redraw echom "Expanded " . (a:seamless == 0 ? 'seamfully' : 'seamlessly') . "." endfunc - "------------------------------------------------------------------------ - func! s:open_href() let line = search(s:http_link_pattern, 'cw') - let href = matchstr(getline(line('.')), s:http_link_pattern) + let href = expand("<cWORD>") let command = g:SoyWiki#browser_command . " '" . href . "' " call system(command) echom command endfunc - " -------------------------------------------------------------------------------- " HELP func! s:show_help() let command = g:SoyWiki#browser_command . ' ' . shellescape('http://danielchoi.com/software/soywiki.html') call system(command) endfunc - - "------------------------------------------------------------------------ func! s:global_mappings() noremap <leader>m :call <SID>list_pages()<CR> noremap <leader>M :call <SID>list_pages_linking_in()<CR> noremap <silent> <leader>o :call <SID>open_href()<cr> + nnoremap <silent> q :close<cr> + nnoremap <silent> <C-h> :close<cr> + " reflow text + nnoremap \ gwap + " insert a line + nmap <Leader>- o<Esc>k72i-<Esc><CR> + " insert date + map <Leader>d :r !date<CR>o + command! -bar -nargs=1 -range -complete=file SWAppend :<line1>,<line2>call s:extract(<f-args>, 'append', 0) command! -bar -nargs=1 -range -complete=file SWInsert :<line1>,<line2>call s:extract(<f-args>, 'insert', 0) command! -bar -nargs=1 -range -complete=file SWLinkAppend :<line1>,<line2>call s:extract(<f-args>, 'append', 1) command! -bar -nargs=1 -range -complete=file SWLinkInsert :<line1>,<line2>call s:extract(<f-args>, 'insert', 1) command! -bar -nargs=1 SWSearch :call s:wiki_search(<f-args>) + " TODO a search confined to current namespace - autocmd BufReadPost,BufNewFile,WinEnter,BufEnter,BufNew * call s:highlight_wikiwords() + autocmd BufReadPost,BufNewFile,WinEnter,BufEnter,BufNew,BufAdd * call s:highlight_wikiwords() autocmd BufEnter * call s:prep_buffer() endfunc " this checks if the buffer is a SoyWiki file (from firstline) " and then turns on syntax coloring and mappings as necessary func! s:prep_buffer() if (s:is_wiki_page()) set textwidth=72 nnoremap <buffer> <cr> :call <SID>follow_link_under_cursor(0)<cr> - nnoremap <buffer> - :call <SID>follow_link_under_cursor(1)<cr> - nnoremap <buffer> \| :call <SID>follow_link_under_cursor(2)<cr> + nnoremap <buffer> <c-l> :call <SID>follow_link_under_cursor(1)<cr> + nnoremap <buffer> <c-n> :call <SID>follow_link_under_cursor(2)<cr> noremap <buffer> <leader>f :call <SID>follow_link(0)<CR> - noremap <buffer> <c-n> :call <SID>find_next_wiki_link(0)<CR> - noremap <buffer> <c-p> :call <SID>find_next_wiki_link(1)<CR> + noremap <buffer> <c-j> :call <SID>find_next_wiki_link(0)<CR> + noremap <buffer> <c-k> :call <SID>find_next_wiki_link(1)<CR> - noremap <leader>c :call <SID>create_page()<CR> - command! -buffer SWRename :call s:rename_page() - - noremap <buffer> <leader>r :call <SID>rename_page()<CR> + command! -bar -nargs=1 -range -complete=file SWCreate :call <SID>create_page(<f-args>) + command! -bar -nargs=1 -range -complete=file SWRenameTo :call <SID>rename_page(<f-args>) command! -buffer SWDelete :call s:delete_page() - noremap <buffer> <leader># :call <SID>delete_page()<CR> command! -buffer SWLog :call s:show_revision_history(0) noremap <buffer> <leader>l :call <SID>show_revision_history(0)<CR> command! -buffer SWLogStat :call s:show_revision_history(1) command! -buffer SWBlame :call s:show_blame() @@ -559,22 +553,20 @@ noremap <buffer> <leader>VX :call <SID>expand(1,1)<CR> noremap <silent> <leader>? :call <SID>show_help()<cr> set nu - setlocal completefunc=CompletePage + setlocal completefunc=CompletePageTitle augroup <buffer> au! autocmd BufWritePost <buffer> call s:save_revision() augroup END endif endfunc func! s:highlight_wikiwords() if (s:is_wiki_page()) - " exe ":match Constant /". s:http_link_pattern . "/" - "exe ":2match Comment /". s:wiki_link_pattern. "/" syntax clear exe "syn match Comment /". s:wiki_link_pattern. "/" exe "syn match Constant /". s:http_link_pattern . "/" endif endfunc @@ -583,10 +575,12 @@ if (!isdirectory(".git")) call system("git init") echom "Created .git repository to store revisions" endif +" compress the repo +call system("git gc") if !exists("g:SoyWiki#browser_command") for cmd in ["gnome-open", "open"] if executable(cmd) let g:SoyWiki#browser_command = cmd @@ -597,13 +591,12 @@ echom "Can't find the to open your web browser." endif endif if len(bufname("%")) == 0 - call s:load_most_recently_modified_page() + call s:load_most_recently_modified_page(0) else call s:load_page(bufname("%"), 0) endif - -call s:get_page_list() syntax enable let mapleader = ',' +call s:highlight_wikiwords()