Fix: VimwikiTOC is broken against headers with link (Issue #182)

Problem: VimwikiTOC and follow_link do not support header (anchor) with
link in their body

Solution:
- VimwikiTOC (easy): Create function base.vim s:clean_header_text that
converts [DESC](URL) -> DESC
- follow_link (hard):
-- [[URL]]: was already working due to punctuation removal.
-- [DESC](URL): Search for a potential `]([^)]*)` after every character
This commit is contained in:
Tinmarino 2020-08-05 22:26:05 -04:00
parent e4186adc3d
commit 69ead3bf3c
7 changed files with 160 additions and 24 deletions

View File

@ -2,7 +2,7 @@
[中文](README-cn.md) [中文](README-cn.md)
- [Intro](#intro) - [Intro](#introduction)
- [Screenshots](#screenshots) - [Screenshots](#screenshots)
- [Installation](#installation) - [Installation](#installation)
- [Prerequisites](#prerequisites) - [Prerequisites](#prerequisites)

View File

@ -761,8 +761,13 @@ function! s:unnormalize_anchor(anchor) abort
" -- Done after: Add spaces leading and trailing => Later with the template " -- Done after: Add spaces leading and trailing => Later with the template
" Link: Inspired from https://gist.github.com/asabaylus/3071099 " Link: Inspired from https://gist.github.com/asabaylus/3071099
" Issue: #664 => Points to all others " Issue: #664 => Points to all others
let anchor = a:anchor let anchor = a:anchor
let punctuation_rx = s:get_punctuaction_regex() . '*' let punctuation_rx = s:get_punctuaction_regex()
" Permit url part of link: '](www.i.did.it.my.way.cl)'
let link_rx = '\%(\]([^)]*)\)'
let invisible_rx = '\%( \|-\|' . punctuation_rx . '\|' . link_rx . '\)'
" 4 Add '-1', '-2', '-3',... to make it unique if not unique " 4 Add '-1', '-2', '-3',... to make it unique if not unique
" -- Save the trailing -12 " -- Save the trailing -12
@ -773,7 +778,7 @@ function! s:unnormalize_anchor(anchor) abort
let anchor_nb = 1 let anchor_nb = 1
else else
" Yes Sufix: number <- read suffix " Yes Sufix: number <- read suffix
let sufix = '[ \-]' . anchor_nb let sufix = invisible_rx.'*' . anchor_nb . invisible_rx.'*'
let anchor_nb = str2nr(anchor_nb) let anchor_nb = str2nr(anchor_nb)
endif endif
" -- Remove it " -- Remove it
@ -782,22 +787,34 @@ function! s:unnormalize_anchor(anchor) abort
" For each char " For each char
let anchor_loop = '' let anchor_loop = ''
for char in split(anchor, '\zs') for char in split(anchor, '\zs')
" Nest the char for easyer debugging
let anchor_loop .= '\%('
" 3 Change any space to a hyphen " 3 Change any space to a hyphen
if char ==# '-' if char ==# '-'
let anchor_loop .= '[ \-]\+' " Match Space or hyphen or punctuation or link
let anchor_loop .= invisible_rx.'\+'
" 2 Remove anything that is not a letter, number, CJK character, hyphen or space " 2 Remove anything that is not a letter, number, CJK character, hyphen or space
" -- So add puncutation regex at each char " -- So add puncutation regex at each char
else else
let anchor_loop .= char . punctuation_rx " Match My_char . punctuation . ( link . punctuaction )?
" Note: Because there may be punctuation before ad after link
let anchor_loop .= char . punctuation_rx.'*'
let anchor_loop .= '\%(' . link_rx . punctuation_rx.'*' . '\)' . '\?'
endif endif
" Close nest
let anchor_loop .= '\)'
endfor endfor
let anchor = punctuation_rx . anchor_loop let anchor = punctuation_rx.'*' . anchor_loop
" 1 Downcase the string " 1 Downcase the string
let anchor = '\c' . anchor let anchor = '\c' . anchor
" 4.bis Add the optional suffix " 4.bis Add the optional suffix
let anchor = anchor . '\(' . sufix . '\)\?' let anchor = anchor . '\%(' . sufix . '\)\?'
return [anchor, anchor_nb] return [anchor, anchor_nb]
endfunction endfunction
@ -833,7 +850,7 @@ function! s:jump_to_anchor(anchor) abort
" Go: Move cursor: maybe more than onces (see markdown suffix) " Go: Move cursor: maybe more than onces (see markdown suffix)
let success_nb = 0 let success_nb = 0
let fail = 0 let is_last_segment = 0
for i in range(segment_nb) for i in range(segment_nb)
" Search " Search
let pos = 0 let pos = 0
@ -841,25 +858,42 @@ function! s:jump_to_anchor(anchor) abort
let pos = pos != 0 ? pos : search(anchor_header, 'Wc') let pos = pos != 0 ? pos : search(anchor_header, 'Wc')
let pos = pos != 0 ? pos : search(anchor_bold, 'Wc') let pos = pos != 0 ? pos : search(anchor_bold, 'Wc')
echom 'Tin pos: ' . pos
" Get the result and reloop or leave " Get the result and reloop or leave
if pos != 0 if pos != 0
" Avance, one line more to not rematch the same pattern if not last segment_nb " Avance, one line more to not rematch the same pattern if not last segment_nb
if success_nb < segment_nb-1 | let pos += 1 | endif if success_nb < segment_nb-1
let pos += 1
let is_last_segment = -1
endif
call cursor(pos, 1) call cursor(pos, 1)
let success_nb += 1 let success_nb += 1
" Break if last line (avoid infinite loop)
" Anyway leave the loop: (Imagine heading # 7271212 at last line)
if pos >= line('$')
let is_last_segment = 1
break
endif
" Do not move " Do not move
" But maybe suffix -2 is not the segment number but the real header suffix " But maybe suffix -2 is not the segment number but the real header suffix
" TODO make this more robust " TODO make this more robust
elseif i == 0 else
" Next segment (default syntax) " If fail at first: do not move
if i == 0
call setpos('.', oldpos) call setpos('.', oldpos)
let fail = 1 endif
" Anyway leave the loop: (Imagine heading # 7271212, you do not want to loop all that)
" Go one line back: if I advanced too much
if is_last_segment == -1 | call cursor(line('.')-1, 1) | endif
let is_last_segment = 1
break break
endif endif
endfor endfor
" Check if happy " Check if happy
if success_nb == segment_nb || fail == 1 if success_nb == segment_nb || is_last_segment == 1
break break
endif endif
@ -2258,6 +2292,22 @@ function! s:current_header(headers, line_number) abort
endfunction endfunction
" Returns: heading with link urls
" Called: table_of_content
function! s:clean_header_text(h_text) abort
" Note: I hardcode, who cares ?
let h_text = a:h_text
" Convert: [[url]] -> url
let h_text = substitute(h_text, '\[\[\([^]]*\)\]\]', '\1', 'g')
" Convert: [desc](url) -> url
let h_text = substitute(h_text, '\[\([^]]*\)\]([^)]*)', '\1', 'g')
return h_text
endfunction
" Returns: index of neighbor header " Returns: index of neighbor header
" Called: by header cursor movements " Called: by header cursor movements
function! s:get_another_header(headers, current_index, direction, operation) abort function! s:get_another_header(headers, current_index, direction, operation) abort
@ -2341,7 +2391,7 @@ endfunction
" a:create == 1: creates or updates TOC in current file " a:create == 1: creates or updates TOC in current file
" a:create == 0: update if TOC exists " a:create == 0: update if TOC exists
function! vimwiki#base#table_of_contents(create) abort function! vimwiki#base#table_of_contents(create) abort
" Collect headers " Gather heading
let headers = s:collect_headers() let headers = s:collect_headers()
let toc_header_text = vimwiki#vars#get_wikilocal('toc_header') let toc_header_text = vimwiki#vars#get_wikilocal('toc_header')
@ -2365,7 +2415,7 @@ function! vimwiki#base#table_of_contents(create) abort
" copy all local variables into dict (add a: if arguments are needed) " copy all local variables into dict (add a: if arguments are needed)
let GeneratorTOC = copy(l:) let GeneratorTOC = copy(l:)
function! GeneratorTOC.f() abort function! GeneratorTOC.f() abort
" Gather heading informations " Clean heading informations
let numbering = vimwiki#vars#get_global('html_header_numbering') let numbering = vimwiki#vars#get_global('html_header_numbering')
" TODO numbering not used ! " TODO numbering not used !
let headers_levels = [['', 0], ['', 0], ['', 0], ['', 0], ['', 0], ['', 0]] let headers_levels = [['', 0], ['', 0], ['', 0], ['', 0], ['', 0], ['', 0]]
@ -2373,16 +2423,21 @@ function! vimwiki#base#table_of_contents(create) abort
for header in self.headers for header in self.headers
let h_text = header[2] let h_text = header[2]
let h_level = header[1] let h_level = header[1]
" Don't include the TOC's header itself " Don't include the TOC's header itself
if h_text ==# self.toc_header_text if h_text ==# self.toc_header_text
continue continue
endif endif
" Clean text
let h_text = s:clean_header_text(h_text)
" Treat levels
let headers_levels[h_level-1] = [h_text, headers_levels[h_level-1][1]+1] let headers_levels[h_level-1] = [h_text, headers_levels[h_level-1][1]+1]
for idx in range(h_level, 5) | let headers_levels[idx] = ['', 0] | endfor for idx in range(h_level, 5) | let headers_levels[idx] = ['', 0] | endfor
let h_complete_id = ''
" Add description to the link if toc_link_format == 1 => extended " Add description to the link if toc_link_format == 1 => extended
let h_complete_id = ''
if vimwiki#vars#get_wikilocal('toc_link_format') == 1 if vimwiki#vars#get_wikilocal('toc_link_format') == 1
for l in range(h_level-1) for l in range(h_level-1)
if headers_levels[l][0] !=? '' if headers_levels[l][0] !=? ''
@ -2392,6 +2447,7 @@ function! vimwiki#base#table_of_contents(create) abort
endif endif
let h_complete_id .= headers_levels[h_level-1][0] let h_complete_id .= headers_levels[h_level-1][0]
" Store
call add(complete_header_infos, [h_level, h_complete_id, h_text]) call add(complete_header_infos, [h_level, h_complete_id, h_text])
endfor endfor

View File

@ -900,14 +900,14 @@ function! s:populate_extra_markdown_vars() abort
let rxWeblink1Ext = '__FileExtension__' let rxWeblink1Ext = '__FileExtension__'
endif endif
" [DESCRIPTION](URL) " [DESCRIPTION](FILE.MD)
let mkd_syntax.Weblink1Template = mkd_syntax.rxWeblink1Prefix . '__LinkDescription__'. let mkd_syntax.Weblink1Template = mkd_syntax.rxWeblink1Prefix . '__LinkDescription__'.
\ mkd_syntax.rxWeblink1Separator. '__LinkUrl__'. rxWeblink1Ext. \ mkd_syntax.rxWeblink1Separator. '__LinkUrl__'. rxWeblink1Ext.
\ mkd_syntax.rxWeblink1Suffix \ mkd_syntax.rxWeblink1Suffix
" [DESCRIPTION](ANCHOR) " [DESCRIPTION](FILE)
let mkd_syntax.Weblink2Template = mkd_syntax.rxWeblink1Prefix . '__LinkDescription__'. let mkd_syntax.Weblink2Template = mkd_syntax.rxWeblink1Prefix . '__LinkDescription__'.
\ mkd_syntax.rxWeblink1Separator. '__LinkUrl__'. mkd_syntax.rxWeblink1Suffix \ mkd_syntax.rxWeblink1Separator. '__LinkUrl__'. mkd_syntax.rxWeblink1Suffix
" [DESCRIPTION](FILE#ANCHOR) " [DESCRIPTION](FILE.MD#ANCHOR)
let mkd_syntax.Weblink3Template = mkd_syntax.rxWeblink1Prefix . '__LinkDescription__'. let mkd_syntax.Weblink3Template = mkd_syntax.rxWeblink1Prefix . '__LinkDescription__'.
\ mkd_syntax.rxWeblink1Separator. '__LinkUrl__'. rxWeblink1Ext. \ mkd_syntax.rxWeblink1Separator. '__LinkUrl__'. rxWeblink1Ext.
\ '#__LinkAnchor__'. mkd_syntax.rxWeblink1Suffix \ '#__LinkAnchor__'. mkd_syntax.rxWeblink1Suffix

View File

@ -3743,6 +3743,7 @@ Changed:~
Removed:~ Removed:~
Fixed:~ Fixed:~
* Issue #182: VimwikiTOC support headers with link
* Issue #813: iMap <Cr> interfere with completion (pum) * Issue #813: iMap <Cr> interfere with completion (pum)
* Issue #709: Support inline code spans inside emphasis * Issue #709: Support inline code spans inside emphasis
Refactoring code, del, eq, sup, sub as regions Refactoring code, del, eq, sup, sub as regions

View File

@ -141,7 +141,7 @@ else
endif endif
" Weblink " Weblink [DESCRIPTION](FILE)
call s:add_target_syntax_ON(vimwiki#vars#get_syntaxlocal('rxWeblink'), 'VimwikiLink') call s:add_target_syntax_ON(vimwiki#vars#get_syntaxlocal('rxWeblink'), 'VimwikiLink')

View File

@ -1,6 +1,8 @@
# VimwikiTOC {{{1 # VimwikiTOC {{{1
# #
# TODO implement: If link in the heading (see README.md) # Just generate the TOC
# See: link_* for link movement and creation
#
# TODO (10min) test if g:vimwiki_to_header well readen # TODO (10min) test if g:vimwiki_to_header well readen
# TODO (10min) test vimviki_toc_link_format # TODO (10min) test vimviki_toc_link_format
# TODO (1h) test if really wiki dependant (for 2 diffrent wikis) # TODO (1h) test if really wiki dependant (for 2 diffrent wikis)
@ -28,6 +30,30 @@
# #
# Start {{{1 # Start {{{1
Given vimwiki (With link header (#182) {{{1):
# A [link](anything here) B
# t[link](anything here)
## 7.4.1528
Execute (VimwikiTOC: Set syntax markdown && Set sw=8):
call SetSyntax('markdown')
set sw=8
VimwikiTOC
Expect vimwiki (With link header (#182) {{{1):
# Contents
- [A link B](#a-link-b)
- [tlink](#tlink)
- [7.4.1528](#741528)
# A [link](anything here) B
# t[link](anything here)
## 7.4.1528
Given vimwiki (Underline header (SetExt) (#209) {{{1): Given vimwiki (Underline header (SetExt) (#209) {{{1):
First with spaces First with spaces

View File

@ -1,7 +1,14 @@
# Link internal to a file # Link internal to a file
#
# See: generate_toc.vim
#
# See issue #666 for anchor support (then internal links) # See issue #666 for anchor support (then internal links)
# Preambule set file onces and for all {{{1 # Preambule set file onces and for all {{{1
# Otherwise the bash script is freezing # Otherwise the bash script is freezing
Given vimwiki (a): Given vimwiki (a):
a a
@ -11,6 +18,52 @@ Execute (Set filename wiki_test.md):
Expect (a): Expect (a):
a a
Given vimwiki (VimwikiTOC is broken against headers with link #182 {{{1):
[A link B](#a-link-b)
[tlink](#tlink)
[7.4.1528](#741528)
[link (333)](#link-333)
# A [link](anything here) B
# t[link](anything here)
## 7.4.1528
#### [link]() (333)
Execute (VimwikiTOC: Set syntax markdown && Set sw=8):
call SetSyntax('markdown')
Do (Enter link):
gg\<Cr>
A__HERE1__\<Esc>
ggj\<Cr>
A__HERE2__\<Esc>
ggjj\<Cr>
A__HERE3__\<Esc>
ggjjj\<Cr>
A__HERE4__\<Esc>
Expect vimwiki (Good anchor with link navigation):
[A link B](#a-link-b)
[tlink](#tlink)
[7.4.1528](#741528)
[link (333)](#link-333)
# A [link](anything here) B__HERE1__
# t[link](anything here)__HERE2__
## 7.4.1528__HERE3__
#### [link]() (333)__HERE4__
# Link to anchor in SetExt {{{1 # Link to anchor in SetExt {{{1
# Like that # Like that
# ----- # -----