commit abcfa048372faa1c4520c39b45010d7a6a6c4647
Author: k1nkreet <polyakovskiy.ilya@gmail.com>
Date: Wed, 23 Jun 2021 17:07:17 +0300
added first version of gemivim
Diffstat:
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()