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()