From 5c4112e3ff9e3e74c75c16aea58203dbaea3ce76 Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Wed, 3 May 2017 01:31:26 +0200 Subject: [PATCH 01/20] dot support for splitting edges --- autoload/sj/dot.vim | 39 ++++++++++++++++++++++++++++++++++++++ ftplugin/dot/splitjoin.vim | 11 +++++++++++ 2 files changed, 50 insertions(+) create mode 100644 autoload/sj/dot.vim create mode 100644 ftplugin/dot/splitjoin.vim diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim new file mode 100644 index 00000000..2b893fb0 --- /dev/null +++ b/autoload/sj/dot.vim @@ -0,0 +1,39 @@ +let s:edge = '->' +let s:node = '\("*[^\"]\{-}"\|\i\+\)' + +function! sj#dot#SplitEdges() + " if sj#SearchUnderCursor('[.\{-}]', '') <= 0 + " " No split if line contains [] + " " Just a rough guess to use this function + " echom "WARNING" + " return 0 + " endif + let line = getline('.') + if line !~ s:edge + return 0 + endif + let sides = split(getline('.'), s:edge) + let lhs = split(get(sides, 0, ''), ',') + let rhs = split(get(sides, 1, ''), ',') + if len(lhs) < 2 && len(rhs) < 2 + return 0 + endif + let edges = [] + for source_node in lhs + for dest_node in rhs + " TODO more beautiful trimming and readding of spaces here + let edges += [source_node . s:edge . dest_node] + endfor + endfor + let body = join(edges, "\n") + call sj#ReplaceMotion('V', body) + return 1 +endfunction + +function! sj#dot#JoinEdges() + " if sj#SearchUnderCursor('[.\{-}]', '') <= 0 + " return 0 + " endif + return 1 + +endfunction diff --git a/ftplugin/dot/splitjoin.vim b/ftplugin/dot/splitjoin.vim new file mode 100644 index 00000000..c06fd79a --- /dev/null +++ b/ftplugin/dot/splitjoin.vim @@ -0,0 +1,11 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#dot#SplitEdges' + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#dot#JoinEdges' + \ ] +endif From 94a78689bd93fc25211aaca0bbcf7d5362df3219 Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Wed, 3 May 2017 11:53:28 +0200 Subject: [PATCH 02/20] complete matching function start --- autoload/sj/dot.vim | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index 2b893fb0..c11a671d 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -12,17 +12,23 @@ function! sj#dot#SplitEdges() if line !~ s:edge return 0 endif - let sides = split(getline('.'), s:edge) - let lhs = split(get(sides, 0, ''), ',') - let rhs = split(get(sides, 1, ''), ',') + let statements = split(line, ';') + " Use last statement of a line as heuristic + " in case there are mor than one + let sides = split(statements[-1], s:edge) + + " FIXME will fail on \" , \" + let lhs = sj#TrimList(split(get(sides, 0, ''), ',')) + let rhs = sj#TrimList(split(get(sides, 1, ''), ',')) + if len(lhs) < 2 && len(rhs) < 2 + " nothing to do here: A -> B; or incomplete return 0 endif let edges = [] for source_node in lhs for dest_node in rhs - " TODO more beautiful trimming and readding of spaces here - let edges += [source_node . s:edge . dest_node] + let edges += [source_node . ' ' . s:edge . ' ' . dest_node . ';'] endfor endfor let body = join(edges, "\n") @@ -30,10 +36,33 @@ function! sj#dot#SplitEdges() return 1 endfunction +function! sj#dot#CompleteMatching(edges) + " edges should be [src, dst] pairs + " srcs, dsts = unzip(edges) + matching = {} + let all_dest_nodes = [] + for edge in edges + let [source_node, dest_node] = edge + matching[source_node] += [dest_node] + let all_dest_nodes += [dest_node] + endfor + for dest_nodes in values(matching) + " FIXME pseudocode, we actually need set equality + if dest_nodes != all_dest_nodes + return 0 + endif + endfor + return 1 +endfunction + function! sj#dot#JoinEdges() " if sj#SearchUnderCursor('[.\{-}]', '') <= 0 " return 0 " endif + call sj#PushCursor() + + + call sj#PopCursor() return 1 endfunction From 8684da1ecc25f78cc67ef00a2083004b7281747e Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Wed, 3 May 2017 16:04:52 +0200 Subject: [PATCH 03/20] support for multi-edges --- autoload/sj/dot.vim | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index c11a671d..92291eb1 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -1,6 +1,12 @@ let s:edge = '->' let s:node = '\("*[^\"]\{-}"\|\i\+\)' +function! sj#dot#ExtractNodes(side) + " Just split on comma + " FIXME will fail on \" , \" + return sj#TrimList(split(side, ',')) +endfunction + function! sj#dot#SplitEdges() " if sj#SearchUnderCursor('[.\{-}]', '') <= 0 " " No split if line contains [] @@ -17,18 +23,20 @@ function! sj#dot#SplitEdges() " in case there are mor than one let sides = split(statements[-1], s:edge) - " FIXME will fail on \" , \" - let lhs = sj#TrimList(split(get(sides, 0, ''), ',')) - let rhs = sj#TrimList(split(get(sides, 1, ''), ',')) - - if len(lhs) < 2 && len(rhs) < 2 - " nothing to do here: A -> B; or incomplete - return 0 - endif - let edges = [] - for source_node in lhs - for dest_node in rhs - let edges += [source_node . ' ' . s:edge . ' ' . dest_node . ';'] + let [edges, idx] = [[], 0] + while idx < len(sides) - 1 + " handling of chained expressions + " A -> B -> C + let edges += [sj#dot#ExtractNodes(get(sides, idx)), + \ sj#dot#ExtractNodes(get(sides, idx + 1))] + endwhile + let new_edges= [] + for edge in edges + [lhs, rhs] = edge + for source_node in lhs + for dest_node in rhs + let new_edges += [source_node . ' ' . s:edge . ' ' . dest_node . ';'] + endfor endfor endfor let body = join(edges, "\n") From d357192caae9e2435a0a51e99193b002869797eb Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Wed, 3 May 2017 16:07:26 +0200 Subject: [PATCH 04/20] bugfix --- autoload/sj/dot.vim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index 92291eb1..4647407e 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -4,7 +4,8 @@ let s:node = '\("*[^\"]\{-}"\|\i\+\)' function! sj#dot#ExtractNodes(side) " Just split on comma " FIXME will fail on \" , \" - return sj#TrimList(split(side, ',')) + let nodes = split(a:side, ',') + return sj#TrimList(nodes) endfunction function! sj#dot#SplitEdges() From 729cfe4c25aa6972bf3995ac03dd16607f48441a Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Wed, 3 May 2017 16:55:20 +0200 Subject: [PATCH 05/20] robust splitting also on multi-statements --- autoload/sj/dot.vim | 59 +++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index 4647407e..bb06aec3 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -1,4 +1,5 @@ let s:edge = '->' +" node regexp unused let s:node = '\("*[^\"]\{-}"\|\i\+\)' function! sj#dot#ExtractNodes(side) @@ -8,39 +9,52 @@ function! sj#dot#ExtractNodes(side) return sj#TrimList(nodes) endfunction +function! sj#dot#SplitStatements() + " FIXME use proper regex + let statements = split(getline('.'), ';') + if len(statements) < 2 | return 0 | endif + call map(statements, 'v:val . ";"') + call sj#ReplaceMotion('V', join(statements, "\n")) + return 1 +endfunction + +function! sj#dot#JoinStatements() + " unused + normal! J +endfunction + function! sj#dot#SplitEdges() - " if sj#SearchUnderCursor('[.\{-}]', '') <= 0 - " " No split if line contains [] - " " Just a rough guess to use this function - " echom "WARNING" - " return 0 - " endif + " split multi statement if possible + if sj#dot#SplitStatements() | return 0 | endif + let line = getline('.') - if line !~ s:edge + " chop off potential trailing ; + let statement = split(line, ';')[-1] + " Split to elements of an edge + let sides = split(statement, s:edge) + if len(sides) < 2 return 0 endif - let statements = split(line, ';') - " Use last statement of a line as heuristic - " in case there are mor than one - let sides = split(statements[-1], s:edge) let [edges, idx] = [[], 0] while idx < len(sides) - 1 " handling of chained expressions - " A -> B -> C - let edges += [sj#dot#ExtractNodes(get(sides, idx)), - \ sj#dot#ExtractNodes(get(sides, idx + 1))] + " such as A -> B -> C + let edges += [[sj#dot#ExtractNodes(get(sides, idx)), + \ sj#dot#ExtractNodes(get(sides, idx + 1))]] + let idx = idx + 1 endwhile - let new_edges= [] + + let new_edges = [] for edge in edges - [lhs, rhs] = edge + let [lhs, rhs] = edge for source_node in lhs for dest_node in rhs let new_edges += [source_node . ' ' . s:edge . ' ' . dest_node . ';'] endfor endfor endfor - let body = join(edges, "\n") + let body = join(new_edges, "\n") call sj#ReplaceMotion('V', body) return 1 endfunction @@ -65,13 +79,6 @@ function! sj#dot#CompleteMatching(edges) endfunction function! sj#dot#JoinEdges() - " if sj#SearchUnderCursor('[.\{-}]', '') <= 0 - " return 0 - " endif - call sj#PushCursor() - - - call sj#PopCursor() - return 1 - + " TODO apply some sort of matching algorithm + return sj#dot#JoinStatements() endfunction From 042c1893a5f1fc270a2c18e4f1d6c8f4d5b86da6 Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Thu, 4 May 2017 01:12:03 +0200 Subject: [PATCH 06/20] joining close to working, two more bugs to fix --- autoload/sj/dot.vim | 167 ++++++++++++++++++++++++++++--------- ftplugin/dot/splitjoin.vim | 6 +- 2 files changed, 131 insertions(+), 42 deletions(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index bb06aec3..55bcd864 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -2,15 +2,35 @@ let s:edge = '->' " node regexp unused let s:node = '\("*[^\"]\{-}"\|\i\+\)' +" Helper functions {{{ function! sj#dot#ExtractNodes(side) + " Split multiple nodes into single elements " Just split on comma " FIXME will fail on \" , \" let nodes = split(a:side, ',') - return sj#TrimList(nodes) + call sj#TrimList(nodes) + call uniq(sort(nodes)) + return nodes endfunction -function! sj#dot#SplitStatements() - " FIXME use proper regex +function! sj#dot#ExtractEdges(statement) + let sides = split(a:statement, s:edge) + if len(sides) < 2 | return [] | endif + let [edges, idx] = [[], 0] + while idx < len(sides) - 1 + " handling of chained expressions + " such as A -> B -> C + let edges += [[sj#dot#ExtractNodes(get(sides, idx)), + \ sj#dot#ExtractNodes(get(sides, idx + 1))]] + let idx = idx + 1 + endwhile + return edges +endfunction +" }}} + +" Callback functions {{{ + +function! sj#dot#SplitStatement() let statements = split(getline('.'), ';') if len(statements) < 2 | return 0 | endif call map(statements, 'v:val . ";"') @@ -18,32 +38,32 @@ function! sj#dot#SplitStatements() return 1 endfunction -function! sj#dot#JoinStatements() +function! sj#dot#JoinStatement() " unused - normal! J + join endfunction -function! sj#dot#SplitEdges() - " split multi statement if possible - if sj#dot#SplitStatements() | return 0 | endif - +function! sj#dot#SplitEdge() let line = getline('.') " chop off potential trailing ; let statement = split(line, ';')[-1] " Split to elements of an edge - let sides = split(statement, s:edge) - if len(sides) < 2 - return 0 - endif + " let sides = split(statement, s:edge) + " if len(sides) < 2 + " return 0 + " endif - let [edges, idx] = [[], 0] - while idx < len(sides) - 1 - " handling of chained expressions - " such as A -> B -> C - let edges += [[sj#dot#ExtractNodes(get(sides, idx)), - \ sj#dot#ExtractNodes(get(sides, idx + 1))]] - let idx = idx + 1 - endwhile + " let [edges, idx] = [[], 0] + " while idx < len(sides) - 1 + " " handling of chained expressions + " " such as A -> B -> C + " let edges += [[sj#dot#ExtractNodes(get(sides, idx)), + " \ sj#dot#ExtractNodes(get(sides, idx + 1))]] + " let idx = idx + 1 + " endwhile + let edges = sj#dot#ExtractEdges(statement) + + if !len(edges) | return 0 | endif let new_edges = [] for edge in edges @@ -59,26 +79,93 @@ function! sj#dot#SplitEdges() return 1 endfunction -function! sj#dot#CompleteMatching(edges) - " edges should be [src, dst] pairs - " srcs, dsts = unzip(edges) - matching = {} - let all_dest_nodes = [] - for edge in edges - let [source_node, dest_node] = edge - matching[source_node] += [dest_node] - let all_dest_nodes += [dest_node] +function! s:MergeEdges(edges) + let edges = copy(a:edges) + let finished = 0 + for [src_nodes, dst_nodes] in edges + call uniq(sort(src_nodes)) + call uniq(sort(dst_nodes)) endfor - for dest_nodes in values(matching) - " FIXME pseudocode, we actually need set equality - if dest_nodes != all_dest_nodes - return 0 - endif - endfor - return 1 + " all node sets sorted + call uniq(sort(edges)) + " all edges sorted + while !finished + let finished = 1 + let idx = 0 + while idx < len(edges) + let [source_nodes, dest_nodes] = edges[idx] + let jdx = idx + 1 + while jdx < len(edges) + if source_nodes == edges[jdx][0] + let dest_nodes += edges[jdx][1] + call uniq(sort(dest_nodes)) + let finished = 0 + elseif dest_nodes == edges[jdx][1] + let source_nodes += edges[jdx][0] + call uniq(sort(source_nodes)) + let finished = 0 + endif + if !finished + unlet edges[jdx] + else + let jdx += 1 + endif + endwhile + let idx = idx + 1 + endwhile + call uniq(sort(edges)) + endwhile + return edges +endfunction + +function! s:ChainTransitiveEdges(edges) + let edges = copy(a:edges) + let finished = 0 + while !finished + let finished = 1 + let idx = 0 + while idx < len(edges) + let jdx = idx + 1 + while jdx < len(edges) + if edges[idx][-1] == edges[jdx][0] + " FIXME + let edges[idx] = edges[idx][0] + edges[jdx][-1] + let finished = 0 + endif + if !finished + unlet edges[jdx] + else + let jdx += 1 + endif + endwhile + let idx += 1 + endwhile + endwhile endfunction -function! sj#dot#JoinEdges() - " TODO apply some sort of matching algorithm - return sj#dot#JoinStatements() +function! s:Edge2string(edge) + " FIXME + let edge = copy(a:edge) + let edge = map(edge, 'join(v:val, ", ")'}) + let edge = join(edge, ' -> ') + let edge = edge . ';' + return edge +endfunction + +function! sj#dot#JoinEdge() + call sj#PushCursor() + let s1 = substitute(getline('.'), ';$', '', '') + normal! j + let s2 = substitute(getline('.'), ';$', '', '') + call sj#PopCursor() + let edges1 = sj#dot#ExtractEdges(s1) + let edges2 = sj#dot#ExtractEdges(s2) + let edges = edges1 + edges2 + let edges = s:MergeEdges(edges) + echo edges + let edges = s:ChainTransitiveEdges(edges) + if len(edges) > 1 | return 0 | endif + call sj#ReplaceMotion('Vj', s:Edge2string(edges[0])) + return 1 endfunction +" }}} diff --git a/ftplugin/dot/splitjoin.vim b/ftplugin/dot/splitjoin.vim index c06fd79a..0cdf58a5 100644 --- a/ftplugin/dot/splitjoin.vim +++ b/ftplugin/dot/splitjoin.vim @@ -1,11 +1,13 @@ if !exists('b:splitjoin_split_callbacks') let b:splitjoin_split_callbacks = [ - \ 'sj#dot#SplitEdges' + \ 'sj#dot#SplitStatement', + \ 'sj#dot#SplitEdge' \ ] endif if !exists('b:splitjoin_join_callbacks') let b:splitjoin_join_callbacks = [ - \ 'sj#dot#JoinEdges' + \ 'sj#dot#JoinEdge', + \ 'sj#dot#JoinStatement' \ ] endif From a7d979ca586f2d9ff94db5bc80f2e2f61a406dae Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Thu, 4 May 2017 14:49:41 +0200 Subject: [PATCH 07/20] two different use cases --- autoload/sj/dot.vim | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index 55bcd864..15884e94 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -30,6 +30,38 @@ endfunction " Callback functions {{{ +function! sj#dot#SplitChainedEdge + let line = getline('.') + let statement = substitute(getline('.'), ';$', '', '') + let edges = sj#dot#ExtractEdges(statement) + if len(edges) < 1 | return 0 | endif + call map(statements, 'v:val . ";"') + call sj#ReplaceMotion('V', join(edges, "\n")) + return 1 +endfunction + +function s:ParseConsecutiveLines(...) + " Could accept parameter for amount of lines to parse + call sj#PushCursor() + let s1 = substitute(getline('.'), ';$', '', '') + normal! j + let s2 = substitute(getline('.'), ';$', '', '') + call sj#PopCursor() + let edges1 = sj#dot#ExtractEdges(s1) + let edges2 = sj#dot#ExtractEdges(s2) + let edges = edges1 + edges2 + return edges +endfunction + +function! sj#dot#JoinChainedEdge + " TODO + let edges = s:ParseConsecutiveLines() + let edges = s:ChainTransitiveEdges(edges) + if len(edges) > 1 | return 0 | endif + call sj#ReplaceMotion('Vj', 'todo') +endif +endfunction + function! sj#dot#SplitStatement() let statements = split(getline('.'), ';') if len(statements) < 2 | return 0 | endif @@ -43,7 +75,7 @@ function! sj#dot#JoinStatement() join endfunction -function! sj#dot#SplitEdge() +function! sj#dot#SplitMultiEdge() let line = getline('.') " chop off potential trailing ; let statement = split(line, ';')[-1] @@ -153,17 +185,8 @@ function! s:Edge2string(edge) endfunction function! sj#dot#JoinEdge() - call sj#PushCursor() - let s1 = substitute(getline('.'), ';$', '', '') - normal! j - let s2 = substitute(getline('.'), ';$', '', '') - call sj#PopCursor() - let edges1 = sj#dot#ExtractEdges(s1) - let edges2 = sj#dot#ExtractEdges(s2) - let edges = edges1 + edges2 + let edges = s:ParseConsecutiveLines() let edges = s:MergeEdges(edges) - echo edges - let edges = s:ChainTransitiveEdges(edges) if len(edges) > 1 | return 0 | endif call sj#ReplaceMotion('Vj', s:Edge2string(edges[0])) return 1 From a6aef8f70695356fde0e0b5eed1510a2b7351d94 Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Thu, 4 May 2017 17:01:27 +0200 Subject: [PATCH 08/20] release candidate --- autoload/sj/dot.vim | 162 ++++++++++++++++++------------------- ftplugin/dot/splitjoin.vim | 6 +- 2 files changed, 82 insertions(+), 86 deletions(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index 15884e94..fa093969 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -5,16 +5,26 @@ let s:node = '\("*[^\"]\{-}"\|\i\+\)' " Helper functions {{{ function! sj#dot#ExtractNodes(side) " Split multiple nodes into single elements - " Just split on comma - " FIXME will fail on \" , \" + " INPUT: 'A, B, C' + " OUTPUT: ['A', 'B', 'C'] + " FIXME will fail on 'A, B, "some,label"' let nodes = split(a:side, ',') call sj#TrimList(nodes) call uniq(sort(nodes)) return nodes endfunction +function! s:TrimSemicolon(statement) + return substitute(a:statement, ';$', '', '') +endfunction + function! sj#dot#ExtractEdges(statement) - let sides = split(a:statement, s:edge) + " Extract elements of potentially chained edges as [src,dst] pairs + " INPUT: 'A, B -> C -> D' + " OUTPUT: [[[A, B], [C]], [[C], [D]]] + let statement = s:TrimSemicolon(a:statement) + " FIXME will fail if '->' inside "s + let sides = split(statement, s:edge) if len(sides) < 2 | return [] | endif let [edges, idx] = [[], 0] while idx < len(sides) - 1 @@ -26,26 +36,13 @@ function! sj#dot#ExtractEdges(statement) endwhile return edges endfunction -" }}} - -" Callback functions {{{ - -function! sj#dot#SplitChainedEdge - let line = getline('.') - let statement = substitute(getline('.'), ';$', '', '') - let edges = sj#dot#ExtractEdges(statement) - if len(edges) < 1 | return 0 | endif - call map(statements, 'v:val . ";"') - call sj#ReplaceMotion('V', join(edges, "\n")) - return 1 -endfunction -function s:ParseConsecutiveLines(...) +function! s:ParseConsecutiveLines(...) " Could accept parameter for amount of lines to parse call sj#PushCursor() - let s1 = substitute(getline('.'), ';$', '', '') + let s1 = s:TrimSemicolon(getline('.')) normal! j - let s2 = substitute(getline('.'), ';$', '', '') + let s2 = s:TrimSemicolon(getline('.')) call sj#PopCursor() let edges1 = sj#dot#ExtractEdges(s1) let edges2 = sj#dot#ExtractEdges(s2) @@ -53,62 +50,12 @@ function s:ParseConsecutiveLines(...) return edges endfunction -function! sj#dot#JoinChainedEdge - " TODO - let edges = s:ParseConsecutiveLines() - let edges = s:ChainTransitiveEdges(edges) - if len(edges) > 1 | return 0 | endif - call sj#ReplaceMotion('Vj', 'todo') -endif -endfunction - -function! sj#dot#SplitStatement() - let statements = split(getline('.'), ';') - if len(statements) < 2 | return 0 | endif - call map(statements, 'v:val . ";"') - call sj#ReplaceMotion('V', join(statements, "\n")) - return 1 -endfunction - -function! sj#dot#JoinStatement() - " unused - join -endfunction - -function! sj#dot#SplitMultiEdge() - let line = getline('.') - " chop off potential trailing ; - let statement = split(line, ';')[-1] - " Split to elements of an edge - " let sides = split(statement, s:edge) - " if len(sides) < 2 - " return 0 - " endif - - " let [edges, idx] = [[], 0] - " while idx < len(sides) - 1 - " " handling of chained expressions - " " such as A -> B -> C - " let edges += [[sj#dot#ExtractNodes(get(sides, idx)), - " \ sj#dot#ExtractNodes(get(sides, idx + 1))]] - " let idx = idx + 1 - " endwhile - let edges = sj#dot#ExtractEdges(statement) - - if !len(edges) | return 0 | endif - - let new_edges = [] - for edge in edges - let [lhs, rhs] = edge - for source_node in lhs - for dest_node in rhs - let new_edges += [source_node . ' ' . s:edge . ' ' . dest_node . ';'] - endfor - endfor - endfor - let body = join(new_edges, "\n") - call sj#ReplaceMotion('V', body) - return 1 +function! s:Edge2string(edge) + let edge = copy(a:edge) + let edge = map(edge, 'join(v:val, ", ")') + let edge = join(edge, ' -> ') + let edge = edge . ';' + return edge endfunction function! s:MergeEdges(edges) @@ -161,7 +108,7 @@ function! s:ChainTransitiveEdges(edges) while jdx < len(edges) if edges[idx][-1] == edges[jdx][0] " FIXME - let edges[idx] = edges[idx][0] + edges[jdx][-1] + let edges[idx] += [edges[jdx][-1]] let finished = 0 endif if !finished @@ -173,18 +120,65 @@ function! s:ChainTransitiveEdges(edges) let idx += 1 endwhile endwhile + return edges endfunction -function! s:Edge2string(edge) - " FIXME - let edge = copy(a:edge) - let edge = map(edge, 'join(v:val, ", ")'}) - let edge = join(edge, ' -> ') - let edge = edge . ';' - return edge +" }}} +" Callback functions {{{ +function! sj#dot#SplitStatement() + let statements = split(getline('.'), ';') + if len(statements) < 2 | return 0 | endif + call map(statements, 'v:val . ";"') + call sj#ReplaceMotion('V', join(statements, "\n")) + return 1 +endfunction + +function! sj#dot#JoinStatement() + " unused + join +endfunction + +function! sj#dot#SplitChainedEdge() + let line = getline('.') + if line !~ s:edge . '.*' . s:edge | return 0 | endif + let statement = s:TrimSemicolon(line) + let edges = sj#dot#ExtractEdges(statement) + call map(edges, 's:Edge2string(v:val)') + call sj#ReplaceMotion('V', join(edges, "\n")) + return 1 +endfunction + +function! sj#dot#JoinChainedEdge() + let edges = s:ParseConsecutiveLines() + let edges = s:ChainTransitiveEdges(edges) + if len(edges) > 1 | return 0 | endif + let edge_string = s:Edge2string(edges[0]) + call sj#ReplaceMotion('Vj', edge_string) + return 1 +endif +endfunction + +function! sj#dot#SplitMultiEdge() + " chop off potential trailing ';' + let statement = substitute(getline('.'), ';$', '', '') + let edges = sj#dot#ExtractEdges(statement) + if !len(edges) | return 0 | endif + + let new_edges = [] + for edge in edges + let [lhs, rhs] = edge + for source_node in lhs + for dest_node in rhs + let new_edges += [source_node . ' ' . s:edge . ' ' . dest_node . ';'] + endfor + endfor + endfor + let body = join(new_edges, "\n") + call sj#ReplaceMotion('V', body) + return 1 endfunction -function! sj#dot#JoinEdge() +function! sj#dot#JoinMultiEdge() let edges = s:ParseConsecutiveLines() let edges = s:MergeEdges(edges) if len(edges) > 1 | return 0 | endif diff --git a/ftplugin/dot/splitjoin.vim b/ftplugin/dot/splitjoin.vim index 0cdf58a5..c7c4df57 100644 --- a/ftplugin/dot/splitjoin.vim +++ b/ftplugin/dot/splitjoin.vim @@ -1,13 +1,15 @@ if !exists('b:splitjoin_split_callbacks') let b:splitjoin_split_callbacks = [ \ 'sj#dot#SplitStatement', - \ 'sj#dot#SplitEdge' + \ 'sj#dot#SplitChainedEdge', + \ 'sj#dot#SplitMultiEdge' \ ] endif if !exists('b:splitjoin_join_callbacks') let b:splitjoin_join_callbacks = [ - \ 'sj#dot#JoinEdge', + \ 'sj#dot#JoinMultiEdge', + \ 'sj#dot#JoinChainedEdge', \ 'sj#dot#JoinStatement' \ ] endif From 30a3250994d313540ccb64287d654e1953cc43e7 Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Thu, 4 May 2017 17:32:50 +0200 Subject: [PATCH 09/20] some more robustness --- autoload/sj/dot.vim | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index fa093969..1829269a 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -98,6 +98,7 @@ function! s:MergeEdges(edges) endfunction function! s:ChainTransitiveEdges(edges) + " FIXME BUG IN HERE let edges = copy(a:edges) let finished = 0 while !finished @@ -110,12 +111,10 @@ function! s:ChainTransitiveEdges(edges) " FIXME let edges[idx] += [edges[jdx][-1]] let finished = 0 - endif - if !finished unlet edges[jdx] - else - let jdx += 1 + break endif + let jdx += 1 endwhile let idx += 1 endwhile @@ -134,8 +133,8 @@ function! sj#dot#SplitStatement() endfunction function! sj#dot#JoinStatement() - " unused - join + " TODO guard for comments etc + normal! J endfunction function! sj#dot#SplitChainedEdge() @@ -179,10 +178,14 @@ function! sj#dot#SplitMultiEdge() endfunction function! sj#dot#JoinMultiEdge() + " TODO guard for comments or blank lines + " Check whether two lines are let edges = s:ParseConsecutiveLines() + if len(edges) < 2 | return 0 | endif let edges = s:MergeEdges(edges) if len(edges) > 1 | return 0 | endif call sj#ReplaceMotion('Vj', s:Edge2string(edges[0])) + echom "Joined Multi-edge" return 1 endfunction " }}} From 5fdcc51dcb30ec1c3da4a7650256d37758280bff Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Thu, 4 May 2017 18:03:38 +0200 Subject: [PATCH 10/20] todo statement --- autoload/sj/dot.vim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index 1829269a..25b7be3c 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -135,6 +135,7 @@ endfunction function! sj#dot#JoinStatement() " TODO guard for comments etc normal! J + return 1 endfunction function! sj#dot#SplitChainedEdge() @@ -148,6 +149,7 @@ function! sj#dot#SplitChainedEdge() endfunction function! sj#dot#JoinChainedEdge() + " TODO initial guard let edges = s:ParseConsecutiveLines() let edges = s:ChainTransitiveEdges(edges) if len(edges) > 1 | return 0 | endif From b8490bcaf09b2765a906b58906fea621fbba1fdc Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Sat, 6 May 2017 00:42:58 +0200 Subject: [PATCH 11/20] release candidate 1 --- autoload/sj/dot.vim | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index 25b7be3c..8aaf448a 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -38,14 +38,17 @@ function! sj#dot#ExtractEdges(statement) endfunction function! s:ParseConsecutiveLines(...) - " Could accept parameter for amount of lines to parse + " This should could also parse consecutive statements instead, only potentially on + " 2 consecutive lines + + " Safety guard, because multiple statements are not handled at the moment + if getline('.') =~ ';.*;' | return [] | endif + call sj#PushCursor() - let s1 = s:TrimSemicolon(getline('.')) + let edges1 = sj#dot#ExtractEdges(getline('.')) normal! j - let s2 = s:TrimSemicolon(getline('.')) + let edges2 = sj#dot#ExtractEdges(getline('.')) call sj#PopCursor() - let edges1 = sj#dot#ExtractEdges(s1) - let edges2 = sj#dot#ExtractEdges(s2) let edges = edges1 + edges2 return edges endfunction @@ -151,10 +154,13 @@ endfunction function! sj#dot#JoinChainedEdge() " TODO initial guard let edges = s:ParseConsecutiveLines() + echo edges let edges = s:ChainTransitiveEdges(edges) - if len(edges) > 1 | return 0 | endif + " should not be more than one, but also not zero + if len(edges) != 1 | return 0 | endif let edge_string = s:Edge2string(edges[0]) call sj#ReplaceMotion('Vj', edge_string) + echom "Joined chained edge" return 1 endif endfunction @@ -187,7 +193,7 @@ function! sj#dot#JoinMultiEdge() let edges = s:MergeEdges(edges) if len(edges) > 1 | return 0 | endif call sj#ReplaceMotion('Vj', s:Edge2string(edges[0])) - echom "Joined Multi-edge" + echom "Joined multi-edge" return 1 endfunction " }}} From 8bacdd05ef710711823fd5c2b68c5acbd05e70a9 Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Sat, 6 May 2017 01:02:41 +0200 Subject: [PATCH 12/20] rc2 --- autoload/sj/dot.vim | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index 8aaf448a..8385d086 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -42,7 +42,13 @@ function! s:ParseConsecutiveLines(...) " 2 consecutive lines " Safety guard, because multiple statements are not handled at the moment - if getline('.') =~ ';.*;' | return [] | endif + let statements = split(getline('.'), ';') + if len(statements) > 1 + " dont do anything if there is more than one statement in this line + " else we have to inform calling function that we actually did not use the + " 2nd line so that it would not get removed by 'Vj' replace motion + return [] + endif call sj#PushCursor() let edges1 = sj#dot#ExtractEdges(getline('.')) @@ -154,15 +160,12 @@ endfunction function! sj#dot#JoinChainedEdge() " TODO initial guard let edges = s:ParseConsecutiveLines() - echo edges let edges = s:ChainTransitiveEdges(edges) " should not be more than one, but also not zero if len(edges) != 1 | return 0 | endif let edge_string = s:Edge2string(edges[0]) call sj#ReplaceMotion('Vj', edge_string) - echom "Joined chained edge" return 1 -endif endfunction function! sj#dot#SplitMultiEdge() @@ -170,13 +173,14 @@ function! sj#dot#SplitMultiEdge() let statement = substitute(getline('.'), ';$', '', '') let edges = sj#dot#ExtractEdges(statement) if !len(edges) | return 0 | endif - + " Note that this is something else than applying map -> Edge2string + " since we need to expand all-to-all property of multi-edges let new_edges = [] for edge in edges let [lhs, rhs] = edge for source_node in lhs for dest_node in rhs - let new_edges += [source_node . ' ' . s:edge . ' ' . dest_node . ';'] + let new_edges += [s:Edge2string([[source_node], [dest_node]])] endfor endfor endfor @@ -191,9 +195,8 @@ function! sj#dot#JoinMultiEdge() let edges = s:ParseConsecutiveLines() if len(edges) < 2 | return 0 | endif let edges = s:MergeEdges(edges) - if len(edges) > 1 | return 0 | endif + if len(edges) != 1 | return 0 | endif call sj#ReplaceMotion('Vj', s:Edge2string(edges[0])) - echom "Joined multi-edge" return 1 endfunction " }}} From 4e03adbbe6487077a821e5d5f2e7febed670252b Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Sat, 6 May 2017 01:31:08 +0200 Subject: [PATCH 13/20] documentation --- autoload/sj/dot.vim | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index 8385d086..96347745 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -60,6 +60,8 @@ function! s:ParseConsecutiveLines(...) endfunction function! s:Edge2string(edge) + " INPUT: [[src_nodes], [dst_nodes]] + " OUTPUT: string representation of the aequivalent statement let edge = copy(a:edge) let edge = map(edge, 'join(v:val, ", ")') let edge = join(edge, ' -> ') @@ -68,6 +70,8 @@ function! s:Edge2string(edge) endfunction function! s:MergeEdges(edges) + " INPUT: Set of potentially mergable edges + " OUTPUT: Set of edges containing multi-edges let edges = copy(a:edges) let finished = 0 for [src_nodes, dst_nodes] in edges @@ -107,7 +111,8 @@ function! s:MergeEdges(edges) endfunction function! s:ChainTransitiveEdges(edges) - " FIXME BUG IN HERE + " INPUT: set of potentially transitive edges + " OUTPUT: all transitive edges are merged into chained edges let edges = copy(a:edges) let finished = 0 while !finished @@ -117,7 +122,6 @@ function! s:ChainTransitiveEdges(edges) let jdx = idx + 1 while jdx < len(edges) if edges[idx][-1] == edges[jdx][0] - " FIXME let edges[idx] += [edges[jdx][-1]] let finished = 0 unlet edges[jdx] From 1b9582da30351e8576f962bfbcbfedb87459392b Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Sat, 6 May 2017 01:49:50 +0200 Subject: [PATCH 14/20] support for 2 statements on 1 line --- autoload/sj/dot.vim | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index 96347745..31556b9b 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -38,25 +38,36 @@ function! sj#dot#ExtractEdges(statement) endfunction function! s:ParseConsecutiveLines(...) - " This should could also parse consecutive statements instead, only potentially on - " 2 consecutive lines + " OUTPUT: Either [edges, 0] when 2 statements on first line, else [edges, 1] + " when two statements on two lines " Safety guard, because multiple statements are not handled at the moment let statements = split(getline('.'), ';') - if len(statements) > 1 - " dont do anything if there is more than one statement in this line - " else we have to inform calling function that we actually did not use the - " 2nd line so that it would not get removed by 'Vj' replace motion - return [] + if len(statements) > 2 + return [[], 0] + elseif len(statements) == 2 + " only if exactly 2 edges in one line, else replacemotion fails (atm) + let edges = sj#dot#ExtractEdges(statements[0]) + + \ sj#dot#ExtractEdges(statements[1]) + return [edges, 0] + elseif len(statements) == 0 + return [[], 0] endif + " Exactly one statement found on the first lien + " Try to eat the next line call sj#PushCursor() - let edges1 = sj#dot#ExtractEdges(getline('.')) + " FIXME Dangerous on EOF? normal! j - let edges2 = sj#dot#ExtractEdges(getline('.')) + let statements2 = split(getline('.'), ';') + echo statements2 + if len(statements2) > 1 + return [[], 1] + endif + let edges = sj#dot#ExtractEdges(statements[0]) + + \ sj#dot#ExtractEdges(statements2[0]) call sj#PopCursor() - let edges = edges1 + edges2 - return edges + return [edges, 1] endfunction function! s:Edge2string(edge) @@ -163,12 +174,12 @@ endfunction function! sj#dot#JoinChainedEdge() " TODO initial guard - let edges = s:ParseConsecutiveLines() + let [edges, ate] = s:ParseConsecutiveLines() let edges = s:ChainTransitiveEdges(edges) " should not be more than one, but also not zero if len(edges) != 1 | return 0 | endif let edge_string = s:Edge2string(edges[0]) - call sj#ReplaceMotion('Vj', edge_string) + call sj#ReplaceMotion(ate ? 'Vj' : 'V', edge_string) return 1 endfunction @@ -196,11 +207,11 @@ endfunction function! sj#dot#JoinMultiEdge() " TODO guard for comments or blank lines " Check whether two lines are - let edges = s:ParseConsecutiveLines() + let [edges, ate] = s:ParseConsecutiveLines() if len(edges) < 2 | return 0 | endif let edges = s:MergeEdges(edges) if len(edges) != 1 | return 0 | endif - call sj#ReplaceMotion('Vj', s:Edge2string(edges[0])) + call sj#ReplaceMotion(ate ? 'Vj' : 'V', s:Edge2string(edges[0])) return 1 endfunction " }}} From 6095f461651c2416cc31b52039806b9e52428388 Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Sat, 6 May 2017 01:50:36 +0200 Subject: [PATCH 15/20] dropped echo statements --- autoload/sj/dot.vim | 1 - 1 file changed, 1 deletion(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index 31556b9b..7353b0b7 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -60,7 +60,6 @@ function! s:ParseConsecutiveLines(...) " FIXME Dangerous on EOF? normal! j let statements2 = split(getline('.'), ';') - echo statements2 if len(statements2) > 1 return [[], 1] endif From 6fd6a28330d3318add39b4926b89cbca8d9d3dff Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Sat, 13 May 2017 20:11:58 +0200 Subject: [PATCH 16/20] script-local helper functions --- autoload/sj/dot.vim | 153 ++++++++++++++++++++++---------------------- 1 file changed, 77 insertions(+), 76 deletions(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index 7353b0b7..c9c271fa 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -2,8 +2,77 @@ let s:edge = '->' " node regexp unused let s:node = '\("*[^\"]\{-}"\|\i\+\)' +" Callback functions {{{ +function! sj#dot#SplitStatement() + let statements = split(getline('.'), ';') + if len(statements) < 2 | return 0 | endif + call map(statements, 'v:val . ";"') + call sj#ReplaceMotion('V', join(statements, "\n")) + return 1 +endfunction + +function! sj#dot#JoinStatement() + " TODO guard for comments etc + normal! J + return 1 +endfunction + +function! sj#dot#SplitChainedEdge() + let line = getline('.') + if line !~ s:edge . '.*' . s:edge | return 0 | endif + let statement = s:TrimSemicolon(line) + let edges = s:ExtractEdges(statement) + call map(edges, 's:Edge2string(v:val)') + call sj#ReplaceMotion('V', join(edges, "\n")) + return 1 +endfunction + +function! sj#dot#JoinChainedEdge() + " TODO initial guard + let [edges, ate] = s:ParseConsecutiveLines() + let edges = s:ChainTransitiveEdges(edges) + " should not be more than one, but also not zero + if len(edges) != 1 | return 0 | endif + let edge_string = s:Edge2string(edges[0]) + call sj#ReplaceMotion(ate ? 'Vj' : 'V', edge_string) + return 1 +endfunction + +function! sj#dot#SplitMultiEdge() + " chop off potential trailing ';' + let statement = substitute(getline('.'), ';$', '', '') + let edges = s:ExtractEdges(statement) + if !len(edges) | return 0 | endif + " Note that this is something else than applying map -> Edge2string + " since we need to expand all-to-all property of multi-edges + let new_edges = [] + for edge in edges + let [lhs, rhs] = edge + for source_node in lhs + for dest_node in rhs + let new_edges += [s:Edge2string([[source_node], [dest_node]])] + endfor + endfor + endfor + let body = join(new_edges, "\n") + call sj#ReplaceMotion('V', body) + return 1 +endfunction + +function! sj#dot#JoinMultiEdge() + " TODO guard for comments or blank lines + " Check whether two lines are + let [edges, ate] = s:ParseConsecutiveLines() + if len(edges) < 2 | return 0 | endif + let edges = s:MergeEdges(edges) + if len(edges) != 1 | return 0 | endif + call sj#ReplaceMotion(ate ? 'Vj' : 'V', s:Edge2string(edges[0])) + return 1 +endfunction +" }}} + " Helper functions {{{ -function! sj#dot#ExtractNodes(side) +function! s:ExtractNodes(side) " Split multiple nodes into single elements " INPUT: 'A, B, C' " OUTPUT: ['A', 'B', 'C'] @@ -18,7 +87,7 @@ function! s:TrimSemicolon(statement) return substitute(a:statement, ';$', '', '') endfunction -function! sj#dot#ExtractEdges(statement) +function! s:ExtractEdges(statement) " Extract elements of potentially chained edges as [src,dst] pairs " INPUT: 'A, B -> C -> D' " OUTPUT: [[[A, B], [C]], [[C], [D]]] @@ -30,8 +99,8 @@ function! sj#dot#ExtractEdges(statement) while idx < len(sides) - 1 " handling of chained expressions " such as A -> B -> C - let edges += [[sj#dot#ExtractNodes(get(sides, idx)), - \ sj#dot#ExtractNodes(get(sides, idx + 1))]] + let edges += [[s:ExtractNodes(get(sides, idx)), + \ s:ExtractNodes(get(sides, idx + 1))]] let idx = idx + 1 endwhile return edges @@ -47,8 +116,8 @@ function! s:ParseConsecutiveLines(...) return [[], 0] elseif len(statements) == 2 " only if exactly 2 edges in one line, else replacemotion fails (atm) - let edges = sj#dot#ExtractEdges(statements[0]) + - \ sj#dot#ExtractEdges(statements[1]) + let edges = s:ExtractEdges(statements[0]) + + \ s:ExtractEdges(statements[1]) return [edges, 0] elseif len(statements) == 0 return [[], 0] @@ -63,8 +132,8 @@ function! s:ParseConsecutiveLines(...) if len(statements2) > 1 return [[], 1] endif - let edges = sj#dot#ExtractEdges(statements[0]) + - \ sj#dot#ExtractEdges(statements2[0]) + let edges = s:ExtractEdges(statements[0]) + + \ s:ExtractEdges(statements2[0]) call sj#PopCursor() return [edges, 1] endfunction @@ -146,71 +215,3 @@ function! s:ChainTransitiveEdges(edges) endfunction " }}} -" Callback functions {{{ -function! sj#dot#SplitStatement() - let statements = split(getline('.'), ';') - if len(statements) < 2 | return 0 | endif - call map(statements, 'v:val . ";"') - call sj#ReplaceMotion('V', join(statements, "\n")) - return 1 -endfunction - -function! sj#dot#JoinStatement() - " TODO guard for comments etc - normal! J - return 1 -endfunction - -function! sj#dot#SplitChainedEdge() - let line = getline('.') - if line !~ s:edge . '.*' . s:edge | return 0 | endif - let statement = s:TrimSemicolon(line) - let edges = sj#dot#ExtractEdges(statement) - call map(edges, 's:Edge2string(v:val)') - call sj#ReplaceMotion('V', join(edges, "\n")) - return 1 -endfunction - -function! sj#dot#JoinChainedEdge() - " TODO initial guard - let [edges, ate] = s:ParseConsecutiveLines() - let edges = s:ChainTransitiveEdges(edges) - " should not be more than one, but also not zero - if len(edges) != 1 | return 0 | endif - let edge_string = s:Edge2string(edges[0]) - call sj#ReplaceMotion(ate ? 'Vj' : 'V', edge_string) - return 1 -endfunction - -function! sj#dot#SplitMultiEdge() - " chop off potential trailing ';' - let statement = substitute(getline('.'), ';$', '', '') - let edges = sj#dot#ExtractEdges(statement) - if !len(edges) | return 0 | endif - " Note that this is something else than applying map -> Edge2string - " since we need to expand all-to-all property of multi-edges - let new_edges = [] - for edge in edges - let [lhs, rhs] = edge - for source_node in lhs - for dest_node in rhs - let new_edges += [s:Edge2string([[source_node], [dest_node]])] - endfor - endfor - endfor - let body = join(new_edges, "\n") - call sj#ReplaceMotion('V', body) - return 1 -endfunction - -function! sj#dot#JoinMultiEdge() - " TODO guard for comments or blank lines - " Check whether two lines are - let [edges, ate] = s:ParseConsecutiveLines() - if len(edges) < 2 | return 0 | endif - let edges = s:MergeEdges(edges) - if len(edges) != 1 | return 0 | endif - call sj#ReplaceMotion(ate ? 'Vj' : 'V', s:Edge2string(edges[0])) - return 1 -endfunction -" }}} From 2b9adf6aa5c2bacbc106e54d85d105ca260e478b Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Sat, 13 May 2017 21:21:10 +0200 Subject: [PATCH 17/20] EOF safe --- autoload/sj/dot.vim | 1 + 1 file changed, 1 insertion(+) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index c9c271fa..09c6eaa2 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -127,6 +127,7 @@ function! s:ParseConsecutiveLines(...) call sj#PushCursor() " FIXME Dangerous on EOF? + if line('.') + 1 == line('$') | return [[], 0] | endif normal! j let statements2 = split(getline('.'), ';') if len(statements2) > 1 From 49beb62223fff84458fc0373712aeb0af5d9a498 Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Sat, 13 May 2017 21:26:33 +0200 Subject: [PATCH 18/20] a very minor thing --- autoload/sj/dot.vim | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index 09c6eaa2..be0df8a6 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -72,10 +72,10 @@ endfunction " }}} " Helper functions {{{ +" Split multiple nodes into single elements +" INPUT: 'A, B, C' +" OUTPUT: ['A', 'B', 'C'] function! s:ExtractNodes(side) - " Split multiple nodes into single elements - " INPUT: 'A, B, C' - " OUTPUT: ['A', 'B', 'C'] " FIXME will fail on 'A, B, "some,label"' let nodes = split(a:side, ',') call sj#TrimList(nodes) @@ -87,10 +87,10 @@ function! s:TrimSemicolon(statement) return substitute(a:statement, ';$', '', '') endfunction +" Extract elements of potentially chained edges as [src,dst] pairs +" INPUT: 'A, B -> C -> D' +" OUTPUT: [[[A, B], [C]], [[C], [D]]] function! s:ExtractEdges(statement) - " Extract elements of potentially chained edges as [src,dst] pairs - " INPUT: 'A, B -> C -> D' - " OUTPUT: [[[A, B], [C]], [[C], [D]]] let statement = s:TrimSemicolon(a:statement) " FIXME will fail if '->' inside "s let sides = split(statement, s:edge) @@ -106,10 +106,9 @@ function! s:ExtractEdges(statement) return edges endfunction +" OUTPUT: Either [edges, 0] when 2 statements on first line, else [edges, 1] +" when two statements on two lines function! s:ParseConsecutiveLines(...) - " OUTPUT: Either [edges, 0] when 2 statements on first line, else [edges, 1] - " when two statements on two lines - " Safety guard, because multiple statements are not handled at the moment let statements = split(getline('.'), ';') if len(statements) > 2 @@ -126,7 +125,6 @@ function! s:ParseConsecutiveLines(...) " Try to eat the next line call sj#PushCursor() - " FIXME Dangerous on EOF? if line('.') + 1 == line('$') | return [[], 0] | endif normal! j let statements2 = split(getline('.'), ';') @@ -139,9 +137,9 @@ function! s:ParseConsecutiveLines(...) return [edges, 1] endfunction +" INPUT: [[src_nodes], [dst_nodes]] +" OUTPUT: string representation of the aequivalent statement function! s:Edge2string(edge) - " INPUT: [[src_nodes], [dst_nodes]] - " OUTPUT: string representation of the aequivalent statement let edge = copy(a:edge) let edge = map(edge, 'join(v:val, ", ")') let edge = join(edge, ' -> ') @@ -149,9 +147,9 @@ function! s:Edge2string(edge) return edge endfunction +" INPUT: Set of potentially mergable edges +" OUTPUT: Set of edges containing multi-edges function! s:MergeEdges(edges) - " INPUT: Set of potentially mergable edges - " OUTPUT: Set of edges containing multi-edges let edges = copy(a:edges) let finished = 0 for [src_nodes, dst_nodes] in edges @@ -190,9 +188,9 @@ function! s:MergeEdges(edges) return edges endfunction +" INPUT: set of potentially transitive edges +" OUTPUT: all transitive edges are merged into chained edges function! s:ChainTransitiveEdges(edges) - " INPUT: set of potentially transitive edges - " OUTPUT: all transitive edges are merged into chained edges let edges = copy(a:edges) let finished = 0 while !finished From 7728b66881df7a8f98273e6949a8bc85970d58a2 Mon Sep 17 00:00:00 2001 From: Lukas Galke Date: Sun, 14 May 2017 03:00:21 +0200 Subject: [PATCH 19/20] buffer work starting point --- autoload/sj/dot.vim | 92 +++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index be0df8a6..9f29afcd 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -1,14 +1,15 @@ +let s:skip = sj#SkipSyntax(['dotString','dotComment']) let s:edge = '->' -" node regexp unused -let s:node = '\("*[^\"]\{-}"\|\i\+\)' + " Callback functions {{{ function! sj#dot#SplitStatement() - let statements = split(getline('.'), ';') - if len(statements) < 2 | return 0 | endif - call map(statements, 'v:val . ";"') - call sj#ReplaceMotion('V', join(statements, "\n")) - return 1 + if sj#SearchSkip(';\s*\S', s:skip, '', line('.')) + execute "normal! a\" + return 1 + else + return 0 + endif endfunction function! sj#dot#JoinStatement() @@ -18,12 +19,14 @@ function! sj#dot#JoinStatement() endfunction function! sj#dot#SplitChainedEdge() - let line = getline('.') - if line !~ s:edge . '.*' . s:edge | return 0 | endif - let statement = s:TrimSemicolon(line) - let edges = s:ExtractEdges(statement) - call map(edges, 's:Edge2string(v:val)') - call sj#ReplaceMotion('V', join(edges, "\n")) + " FIXME Now sj#dot#SplitStatement does not assert only single line statements afterwards, + " so there might occur an error here. let line = getline('.') + let l:line = getline('.') + if l:line !~ s:edge . '.*' . s:edge | return 0 | endif + let l:statement = s:TrimSemicolon(l:line) + let l:edges = s:ExtractEdges(l:statement) + call map(l:edges, 's:Edge2string(v:val)') + call sj#ReplaceMotion('V', join(l:edges, "\n")) return 1 endfunction @@ -77,10 +80,11 @@ endfunction " OUTPUT: ['A', 'B', 'C'] function! s:ExtractNodes(side) " FIXME will fail on 'A, B, "some,label"' - let nodes = split(a:side, ',') - call sj#TrimList(nodes) - call uniq(sort(nodes)) - return nodes + let l:nodes = split(a:side, ',') + call sj#TrimList(l:nodes) + call uniq(sort(l:nodes)) + echo l:nodes + return l:nodes endfunction function! s:TrimSemicolon(statement) @@ -89,36 +93,36 @@ endfunction " Extract elements of potentially chained edges as [src,dst] pairs " INPUT: 'A, B -> C -> D' -" OUTPUT: [[[A, B], [C]], [[C], [D]]] +" OUTPUT: List of edges [[[A, B], [C]], [[C], [D]]] function! s:ExtractEdges(statement) - let statement = s:TrimSemicolon(a:statement) + let l:statement = s:TrimSemicolon(a:statement) " FIXME will fail if '->' inside "s - let sides = split(statement, s:edge) - if len(sides) < 2 | return [] | endif - let [edges, idx] = [[], 0] - while idx < len(sides) - 1 + let l:sides = split(l:statement, s:edge) + if len(l:sides) < 2 | return [] | endif + let [l:edges, l:idx] = [[], 0] + while l:idx < len(l:sides) - 1 " handling of chained expressions " such as A -> B -> C - let edges += [[s:ExtractNodes(get(sides, idx)), - \ s:ExtractNodes(get(sides, idx + 1))]] - let idx = idx + 1 + let l:edges += [[s:ExtractNodes(get(l:sides, l:idx)), + \ s:ExtractNodes(get(l:sides, l:idx + 1))]] + let l:idx = l:idx + 1 endwhile - return edges + return l:edges endfunction " OUTPUT: Either [edges, 0] when 2 statements on first line, else [edges, 1] " when two statements on two lines function! s:ParseConsecutiveLines(...) " Safety guard, because multiple statements are not handled at the moment - let statements = split(getline('.'), ';') - if len(statements) > 2 + let l:statements = split(getline('.'), ';') + if len(l:statements) > 2 return [[], 0] - elseif len(statements) == 2 + elseif len(l:statements) == 2 " only if exactly 2 edges in one line, else replacemotion fails (atm) - let edges = s:ExtractEdges(statements[0]) + - \ s:ExtractEdges(statements[1]) - return [edges, 0] - elseif len(statements) == 0 + let l:edges = s:ExtractEdges(l:statements[0]) + + \ s:ExtractEdges(l:statements[1]) + return [l:edges, 0] + elseif len(l:statements) == 0 return [[], 0] endif " Exactly one statement found on the first lien @@ -127,24 +131,24 @@ function! s:ParseConsecutiveLines(...) call sj#PushCursor() if line('.') + 1 == line('$') | return [[], 0] | endif normal! j - let statements2 = split(getline('.'), ';') - if len(statements2) > 1 + let l:statements2 = split(getline('.'), ';') + if len(l:statements2) > 1 return [[], 1] endif - let edges = s:ExtractEdges(statements[0]) + - \ s:ExtractEdges(statements2[0]) + let l:edges = s:ExtractEdges(l:statements[0]) + + \ s:ExtractEdges(l:statements2[0]) call sj#PopCursor() - return [edges, 1] + return [l:edges, 1] endfunction " INPUT: [[src_nodes], [dst_nodes]] " OUTPUT: string representation of the aequivalent statement function! s:Edge2string(edge) - let edge = copy(a:edge) - let edge = map(edge, 'join(v:val, ", ")') - let edge = join(edge, ' -> ') - let edge = edge . ';' - return edge + let l:edge = copy(a:edge) + let l:edge = map(l:edge, 'join(v:val, ", ")') + let l:edge = join(l:edge, ' -> ') + let l:edge = l:edge . ';' + return l:edge endfunction " INPUT: Set of potentially mergable edges From 8f61383a20e491b496aadfd52921e6f8139e4e29 Mon Sep 17 00:00:00 2001 From: Andrew Radev Date: Sun, 2 Jul 2017 10:18:51 +0300 Subject: [PATCH 20/20] Specs for dot support (failing for now) --- autoload/sj/dot.vim | 19 +++++++++--------- spec/plugin/dot_spec.rb | 43 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 spec/plugin/dot_spec.rb diff --git a/autoload/sj/dot.vim b/autoload/sj/dot.vim index 9f29afcd..b3d7a132 100644 --- a/autoload/sj/dot.vim +++ b/autoload/sj/dot.vim @@ -4,8 +4,8 @@ let s:edge = '->' " Callback functions {{{ function! sj#dot#SplitStatement() - if sj#SearchSkip(';\s*\S', s:skip, '', line('.')) - execute "normal! a\" + if sj#SearchSkip(';\s*\S', s:skip, 'e', line('.')) + execute "normal! dT;i\" return 1 else return 0 @@ -31,19 +31,19 @@ function! sj#dot#SplitChainedEdge() endfunction function! sj#dot#JoinChainedEdge() - " TODO initial guard + " TODO initial guard let [edges, ate] = s:ParseConsecutiveLines() let edges = s:ChainTransitiveEdges(edges) " should not be more than one, but also not zero if len(edges) != 1 | return 0 | endif let edge_string = s:Edge2string(edges[0]) - call sj#ReplaceMotion(ate ? 'Vj' : 'V', edge_string) + call sj#ReplaceMotion(ate ? 'Vj' : 'V', edge_string) return 1 endfunction function! sj#dot#SplitMultiEdge() " chop off potential trailing ';' - let statement = substitute(getline('.'), ';$', '', '') + let statement = substitute(getline('.'), ';$', '', '') let edges = s:ExtractEdges(statement) if !len(edges) | return 0 | endif " Note that this is something else than applying map -> Edge2string @@ -64,7 +64,7 @@ endfunction function! sj#dot#JoinMultiEdge() " TODO guard for comments or blank lines - " Check whether two lines are + " Check whether two lines are let [edges, ate] = s:ParseConsecutiveLines() if len(edges) < 2 | return 0 | endif let edges = s:MergeEdges(edges) @@ -83,12 +83,11 @@ function! s:ExtractNodes(side) let l:nodes = split(a:side, ',') call sj#TrimList(l:nodes) call uniq(sort(l:nodes)) - echo l:nodes return l:nodes endfunction function! s:TrimSemicolon(statement) - return substitute(a:statement, ';$', '', '') + return substitute(a:statement, ';$', '', '') endfunction " Extract elements of potentially chained edges as [src,dst] pairs @@ -97,7 +96,7 @@ endfunction function! s:ExtractEdges(statement) let l:statement = s:TrimSemicolon(a:statement) " FIXME will fail if '->' inside "s - let l:sides = split(l:statement, s:edge) + let l:sides = split(l:statement, s:edge) if len(l:sides) < 2 | return [] | endif let [l:edges, l:idx] = [[], 0] while l:idx < len(l:sides) - 1 @@ -135,7 +134,7 @@ function! s:ParseConsecutiveLines(...) if len(l:statements2) > 1 return [[], 1] endif - let l:edges = s:ExtractEdges(l:statements[0]) + + let l:edges = s:ExtractEdges(l:statements[0]) + \ s:ExtractEdges(l:statements2[0]) call sj#PopCursor() return [l:edges, 1] diff --git a/spec/plugin/dot_spec.rb b/spec/plugin/dot_spec.rb new file mode 100644 index 00000000..7c616a70 --- /dev/null +++ b/spec/plugin/dot_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe "dot" do + let(:filename) { 'test.dot' } + + specify "statements" do + set_file_contents "A, B -> C -> D -> E; X -> Y;" + + split + + assert_file_contents <<-EOF + A, B -> C -> D -> E; + X -> Y; + EOF + + join + + assert_file_contents "A, B -> C -> D -> E; X -> Y;" + end + + specify "edges" do + set_file_contents "A, B -> C -> D -> E;" + + split + + assert_file_contents <<-EOF + A, B -> C; + C -> D; + D -> E; + EOF + + join + + assert_file_contents <<-EOF + A, B -> C -> D; + D -> E; + EOF + + join + + assert_file_contents "A, B -> C -> D -> E;" + end +end