gemivim

VIM plugin for Gemini browsing
Log (Feed) | Files | Refs (Tags) | README | LICENSE

commit abcfa048372faa1c4520c39b45010d7a6a6c4647
Author: k1nkreet <polyakovskiy.ilya@gmail.com>
Date:   Wed, 23 Jun 2021 17:07:17 +0300

added first version of gemivim

Diffstat:
AREADME.md | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Aautoload/gemivim.vim | 194+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aplugin/gemivim.vim | 6++++++
3 files changed, 249 insertions(+), 0 deletions(-)

diff --git a/README.md b/README.md @@ -0,0 +1,49 @@ +# Gemivim - Gemini browser in VIM + +This is a simple VIM plugin for browsing [Gemini](https://gemini.circumlunar.space) pages + +It uses [gmni](https://sr.ht/~sircmpwn/gmni/) as a gemini client and supports: + + * Get gemini content by given URL + * Netrw gx-like open gemini link under cursor + * Capturing input when gemini resource anticipates to + * Bookmarks management + +Since plugin does nothing but storing content in temporary files and opening them in buffers, one could find quite convenient +to use whole power of VIM with search, jumplists, tabs, whatever to browse efficiently. + +[![Screenshot of the line-mode browser](https://asciinema.org/a/rbdVrL4TJfCT8uSifV6xYoERS.png)](https://asciinema.org/a/rbdVrL4TJfCT8uSifV6xYoERS) + +## Dependencies: + + * [GMNI](https://git.sr.ht/~sircmpwn/gmni/) + * You may find useful [gemini-vim-syntax](https://tildegit.org/sloum/gemini-vim-syntax/) project, but it's necessary for Gemivim + +## Installation: + +With plugin managers: + +### Vim-Plug: + +``` +Plug 'https://git.sr.ht/~k1nkreet/gemivim' +``` + +### Vundle: + +``` +Bundle 'https://git.sr.ht/~k1nkreet/gemivim' +``` + +or just copy autoload/gemivim.vim into ~/.vim/autoload and plugin/gemivim.vim into ~/vim/plugin directories. + +## Usage: + +Gemivim provides several commands for processing gemini links and bookmarks, consider map any of them as a shortcut: + + * GemivimOpen - invokes prompt for input gemini url + * GemivimGX - open link under the cursor + * GemivimOpenBookmarks - open bookmarks file + * GemivimAddBookmark - add current link into bookmarks + * GemivimRefresh - get and reopen link in current buffer + diff --git a/autoload/gemivim.vim b/autoload/gemivim.vim @@ -0,0 +1,194 @@ +let g:tmp_prefix = '/tmp/gemivim/' + +if !has_key(g:, 'gemini_bookmarks_file') + if filereadable(expand('~/.local/share/gmni/bookmarks.gmi')) + let g:gemini_bookmarks_file = $HOME . '/.local/share/gmni/bookmarks.gmi' + else + let g:gemini_bookmarks_file = $HOME . '/.vim/bookmarks.gmi' + endif +endif + +if !isdirectory(g:tmp_prefix) && g:tmp_prefix =~ '.*/$' + call mkdir(g:tmp_prefix) +endif + +function! s:error_msg(msg) + echohl WarningMsg | echom a:msg | echohl None +endfunction + +function! s:info_msg(msg) + echohl Question | echom a:msg | echohl None +endfunction + +function! s:tmp_file_name(url, mime_type) + let l:file = g:tmp_prefix . substitute(a:url, 'gemini://', '', 'g') + if a:mime_type ==# 'text/gemini' && a:url !~# '.*\.gmi$' + return l:file . '/index.gmi' + else + return l:file + endif +endfunction + +function! s:mkdir_for_file(file) + call mkdir(fnamemodify(a:file, ':p:h'), 'p') +endfunction + +function! s:url_from_file(file) + "let l:url = substitute(a:file, '^/tmp/', 'gemini://', 'g') + let l:url = substitute(a:file, '^' . g:tmp_prefix, 'gemini://', 'g') + if l:url =~# '.*/index\.gmi$' + return substitute(l:url, 'index\.gmi$', '', 'g') + else + return l:url + endif +endfunction + +function! s:system_split_output(command) + let l:stdout = g:tmp_prefix . 'stdout' + let l:stderr = g:tmp_prefix . 'stderr' + call system(a:command . ' 1>' . l:stdout . ' 2>' . l:stderr) + return [v:shell_error, readfile(l:stdout), readfile(l:stderr)] +endfunction + +function s:ssl_error(output) + for line in a:output + let l:groups = matchlist(line, '.*SSL error \(\d\+\)$') + if len(l:groups) > 1 + return str2nr(l:groups[1]) + endif + endfor + return 0 +endfunction + +function! gemivim#Get(url, ...) + let l:trust_choice = get(a:, 1, '') + + if l:trust_choice ==# 'always' + let l:j_opt = '-j always' + elseif l:trust_choice ==# 'once' + let l:j_opt = '-j once' + else + let l:j_opt = '' + endif + + if !executable('gmni') + call s:error_msg('gmni is not installed. Visit https://sr.ht/~sircmpwn/gmni/') + return + endif + + let [l:exit_code, l:stdout, l:stderr] = s:system_split_output('gmni -I ' . l:j_opt . ' "' . a:url .'"') + if l:exit_code != 0 + if s:ssl_error(l:stderr) == 62 + let l:choice = input(l:stderr[0] ."\n". l:stderr[1] . "\nIf you knew the fingerprint to expect in advance, verify that this matches.\n + \ Otherwise, it should be safe to trust this certificate. + \ [t]rust always, [o]nce, [a]bort: ") + if l:choice == "t" + return gemivim#Get(a:url, 'always') + elseif l:choice == "o" + return gemivim#Get(a:url, 'once') + else + s:info_msg('Aborted') + return + endif + else + call s:error_msg(join(l:stderr, "\n")) + return + endif + endif + + let l:header = split(l:stdout[0]) + if l:header[0] =~# '1\d' + let l:prompt = join(header[1:], ' ') . ': ' + let l:input = input(l:prompt) + call gemivim#Get(a:url . '?' . l:input, l:trust_choice) + elseif l:header[0] =~# '2\d' + let l:tmp_file = s:tmp_file_name(a:url, split(l:header[1], ";")[0]) + call s:mkdir_for_file(l:tmp_file) + let [l:exit_code, l:stdout, l:stderr] = s:system_split_output('gmni ' . l:j_opt . ' "' . a:url . '"') + if l:exit_code == 0 + call writefile(l:stdout, l:tmp_file) + execute 'view ' . l:tmp_file + else + call s:error_msg(join(l:stderr, "\n")) + endif + elseif l:header[0] =~# '3\d' + call gemivim#Get(l:header[1], l:trust_choice) + elseif l:header[0] =~# '[456]\d' + call s:error_msg(a:url . ': ' . join(l:stdout, "\n")) + else + call s:error_msg(a:url . ': ' . join(l:stdout + l:stderr, "\n")) + endif +endfunction + +function! s:current_url() + return s:url_from_file(bufname()) +endfunction + +function! s:base_url(url) + let l:url = 'gemini://' . split(substitute(a:url, 'gemini://', '', 'g'), '/')[0] + return substitute(l:url, '.gmi$', '', 'g') +endfunction + +function! s:relative_url(from, relative) + if a:relative =~# '^/.*' + return s:base_url(a:from) . a:relative + else + return a:from . a:relative + endif +endfunction + +function! gemivim#Open() + let l:url = trim(input("Open gemini URL: ")) + if l:url !~? '^gemini://.*' + let l:url = 'gemini://' . l:url + endif + + if len(l:url) > 0 + call gemivim#Get(l:url) + else + call s:info_msg('Nevermind') + endif +endfunction + +function! gemivim#GX() + let url = expand('<cfile>') + if url =~? 'gemini://.*' + call gemivim#Get(url) + elseif url =~? 'http://.*' || url =~? 'https://.*' || url =~? 'gopher://.*' + call s:error_msg('Not a gemini url') + elseif &filetype ==# 'gmi' || bufname() =~? '.*\.\(gmi\|gemini\)$' + call gemivim#Get(s:relative_url(s:current_url(), url)) + endif +endfunction + +function! s:add_url_to_bookmarks(url) + if a:url =~? 'gemini://.*' + for link in readfile(g:gemini_bookmarks_file) + if link =~? '=> ' . a:url . '$' || link =~? '=> ' . a:url . '\_s.*' + call s:info_msg('Already in bookmarks') + return + endif + endfor + call writefile(['=> '. a:url], g:gemini_bookmarks_file, 'a') + call s:info_msg('Added to bookmarks: ' . a:url) + else + call s:error_msg('Not a gemini url: ' . a:url) + endif +endfunction + +function! gemivim#OpenBookmarks() + execute 'edit ' . g:gemini_bookmarks_file +endfunction + +function! gemivim#CurrentUrlToBookmarks() + call s:add_url_to_bookmarks(s:current_url()) +endfunction! + +function! gemivim#CursorUrlToBookmarks() + call s:add_url_to_bookmarks(expand('<cfile>')) +endfunction + +function! gemivim#Refresh() + call gemivim#Get(s:current_url()) +endfunction + diff --git a/plugin/gemivim.vim b/plugin/gemivim.vim @@ -0,0 +1,6 @@ + +command! -nargs=0 GemivimOpen :call gemivim#Open() +command! -nargs=0 GemivimGX :call gemivim#GX() +command! -nargs=0 GemivimOpenBookmarks :call gemivim#OpenBookmarks() +command! -nargs=0 GemivimAddBookmark :call gemivim#CurrentUrlToBookmarks() +command! -nargs=0 GemivimRefresh :call gemivim#Refresh()