mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-02-03 07:50:05 +08:00
542 lines
13 KiB
VimL
542 lines
13 KiB
VimL
" Array Callbacks:
|
|
" ================
|
|
|
|
function! sj#yaml#SplitArray()
|
|
let [line, line_no, whitespace] = s:ReadCurrentLine()
|
|
|
|
let prefix = ''
|
|
let array_part = ''
|
|
let indent = 1
|
|
let end_offset = 0
|
|
|
|
let nestedExp = '\v^\s*((-\s+)+)(\[.*\])$'
|
|
let line = s:StripComment(line)
|
|
|
|
" Split arrays which are map properties
|
|
" E.g.
|
|
" prop: [1, 2]
|
|
if line =~ ':\s*\[.*\]$'
|
|
let [key, array_part] = s:SplitKeyValue(line)
|
|
let prefix = key . ":\n"
|
|
|
|
" Split nested arrays
|
|
" E.g.
|
|
" - [1, 2]
|
|
elseif line =~ nestedExp
|
|
let prefix = substitute(line, nestedExp, '\1', '')
|
|
let array_part = substitute(line, nestedExp, '\3', '')
|
|
let indent = len(substitute(line, '\v[^-]', '', 'g'))
|
|
let end_offset = -1
|
|
endif
|
|
|
|
if array_part != ''
|
|
let body = substitute(array_part, '\v^\s*\[(.*)\]\s*$', '\1', '')
|
|
let array_items = s:SplitArrayBody(body)
|
|
|
|
call sj#ReplaceMotion('V', prefix . '- ' . join(array_items, "\n- "))
|
|
silent! normal! zO
|
|
call s:SetIndentWhitespace(line_no, whitespace)
|
|
call s:IncreaseIndentWhitespace(line_no + 1, line_no + len(array_items) + end_offset, whitespace, indent)
|
|
|
|
return 1
|
|
endif
|
|
|
|
return 0
|
|
endfunction
|
|
|
|
function! sj#yaml#JoinArray()
|
|
let [line, line_no, whitespace] = s:ReadCurrentLine()
|
|
|
|
let lines = []
|
|
let first_line = s:StripComment(line)
|
|
|
|
let nestedExp = '\v^(\s*(-\s+)+)(-\s+.*)$'
|
|
let join_type = ''
|
|
|
|
" Nested arrays
|
|
" E.g.
|
|
" - - 'one'
|
|
" - 'two'
|
|
if first_line =~ nestedExp && s:IsValidLineNo(line_no)
|
|
let join_type = 'nested'
|
|
let [lines, last_line_no] = s:GetChildren(line_no)
|
|
let lines = map(lines, 's:StripComment(v:val)')
|
|
let lines = [substitute(first_line, nestedExp, '\3', '')] + lines
|
|
let first_line = sj#Rtrim(substitute(first_line, nestedExp, '\1', ''))
|
|
|
|
" Normal arrays
|
|
" E.g.
|
|
" list:
|
|
" - 'one'
|
|
" - 'two'
|
|
elseif first_line =~ ':$' && s:IsValidLineNo(line_no + 1)
|
|
let join_type = 'normal'
|
|
let [lines, last_line_no] = s:GetChildren(line_no)
|
|
let lines = map(lines, 's:StripComment(v:val)')
|
|
endif
|
|
|
|
if !empty(lines)
|
|
if join_type == 'nested'
|
|
let body_lines = lines[1:len(lines)]
|
|
else
|
|
let body_lines = lines
|
|
endif
|
|
|
|
for line in body_lines
|
|
if line !~ '^\s*$' && line !~ '^\s*-'
|
|
" one non-blank line is not part of the array, it must be a nested
|
|
" construct, can't handle that
|
|
return 0
|
|
endif
|
|
|
|
if line =~ nestedExp
|
|
" can't handle nested subexpressions
|
|
return 0
|
|
endif
|
|
endfor
|
|
|
|
let lines = map(lines, 'sj#Trim(substitute(v:val, "^\\s*-", "", ""))')
|
|
let lines = filter(lines, '!sj#BlankString(v:val)')
|
|
let replacement = first_line . ' [' . s:JoinArrayItems(lines) . ']'
|
|
|
|
call sj#ReplaceLines(line_no, last_line_no, replacement)
|
|
silent! normal! zO
|
|
call s:SetIndentWhitespace(line_no, whitespace)
|
|
|
|
return 1
|
|
endif
|
|
|
|
" then there's nothing to join
|
|
return 0
|
|
endfunction
|
|
|
|
" Map Callbacks:
|
|
" ================
|
|
|
|
function! sj#yaml#SplitMap()
|
|
let [from, to] = sj#LocateBracesOnLine('{', '}')
|
|
|
|
if from >= 0 && to >= 0
|
|
let [line, line_no, whitespace] = s:ReadCurrentLine()
|
|
let line = s:StripComment(line)
|
|
let pairs = sj#ParseJsonObjectBody(from + 1, to - 1)
|
|
let body = join(pairs, "\n")
|
|
let body_start = line_no
|
|
|
|
let indent_level = 0
|
|
let end_offset = -1
|
|
|
|
" Increase indention if the map is inside a nested array.
|
|
" E.g.
|
|
" - - { one: 1 }
|
|
if line =~ '^\s*-\s'
|
|
let indent_level = s:NestedArrayLevel(line)
|
|
endif
|
|
|
|
" Move body into next line if it is a map property.
|
|
" E.g.
|
|
" prop: { one: 1 }
|
|
" - prop: { one: 1 }
|
|
if line =~ '^\v\s*(-\s+)*[^{]*:\s+\{.*'
|
|
let body = "\n" . body
|
|
let indent_level += 1
|
|
let end_offset = 0
|
|
let body_start = line_no + 1
|
|
endif
|
|
|
|
call sj#ReplaceMotion('Va{', body)
|
|
silent! normal! zO
|
|
call s:SetIndentWhitespace(line_no, whitespace)
|
|
call s:IncreaseIndentWhitespace(line_no + 1, line_no + len(pairs) + end_offset, whitespace, indent_level)
|
|
call sj#Keeppatterns(line_no . 's/\s*$//e')
|
|
|
|
if sj#settings#Read('align')
|
|
let body_end = body_start + len(pairs) - 1
|
|
call sj#Align(body_start, body_end, 'json_object')
|
|
endif
|
|
|
|
return 1
|
|
endif
|
|
|
|
return 0
|
|
endfunction
|
|
|
|
function! sj#yaml#JoinMap()
|
|
let [line, line_no, whitespace] = s:ReadCurrentLine()
|
|
|
|
if !s:IsValidLineNo(line_no + 1)
|
|
return 0
|
|
endif
|
|
|
|
let first_line = s:StripComment(line)
|
|
let lines = []
|
|
let last_line_no = 0
|
|
let join_type = ''
|
|
|
|
let nestedExp = '\v^(\s*(-\s+)+)(.*)$'
|
|
let nestedPropExp = '\v^(\s*(-\s+)+.+:)$'
|
|
|
|
" Nested in a map inside an array.
|
|
" E.g.
|
|
" - prop:
|
|
" one: 1
|
|
if first_line =~ nestedPropExp
|
|
let join_type = 'nested_in_map_in_array'
|
|
let [lines, last_line_no] = s:GetChildren(line_no)
|
|
let first_line = sj#Rtrim(substitute(first_line, nestedPropExp, '\1', ''))
|
|
|
|
" Map inside an array.
|
|
" E.g.
|
|
" - one: 1
|
|
" two: 2
|
|
elseif first_line =~ nestedExp
|
|
let join_type = 'nested_in_array'
|
|
let [lines, last_line_no] = s:GetChildren(line_no)
|
|
let lines = [substitute(first_line, nestedExp, '\3', '')] + lines
|
|
let first_line = sj#Rtrim(substitute(first_line, nestedExp, '\1', ''))
|
|
|
|
if len(lines) <= 1
|
|
" only 1 line means nothing to join in this case
|
|
return 0
|
|
endif
|
|
|
|
" Normal map
|
|
" E.g.
|
|
" map:
|
|
" one: 1
|
|
" two: 2
|
|
elseif first_line =~ '\k\+:\s*$'
|
|
let join_type = 'normal'
|
|
let [lines, last_line_no] = s:GetChildren(line_no)
|
|
endif
|
|
|
|
if len(lines) > 0
|
|
if join_type == 'nested_in_array'
|
|
let body_lines = lines[1:len(lines)]
|
|
else
|
|
let body_lines = lines
|
|
endif
|
|
|
|
if len(body_lines) > 0
|
|
let base_indent = len(matchstr(body_lines[0], '^\s*'))
|
|
endif
|
|
|
|
for line in body_lines
|
|
if line =~ '^\s*-'
|
|
" one of the lines is a part of an array, we can't handle nested subexpressions
|
|
return 0
|
|
endif
|
|
|
|
if len(matchstr(line, '^\s*')) != base_indent
|
|
" a nested map, can't handle that
|
|
return 0
|
|
endif
|
|
endfor
|
|
|
|
let lines = sj#TrimList(lines)
|
|
let lines = s:NormalizeWhitespace(lines)
|
|
let lines = map(lines, 's:StripComment(v:val)')
|
|
|
|
if sj#settings#Read('curly_brace_padding')
|
|
let replacement = first_line . ' { '. join(lines, ', ') . ' }'
|
|
else
|
|
let replacement = first_line . ' {'. join(lines, ', ') . '}'
|
|
endif
|
|
|
|
call sj#ReplaceLines(line_no, last_line_no, replacement)
|
|
silent! normal! zO
|
|
call s:SetIndentWhitespace(line_no, whitespace)
|
|
|
|
return 1
|
|
endif
|
|
|
|
return 0
|
|
endfunction
|
|
|
|
" Helper Functions:
|
|
" =================
|
|
|
|
" Reads line, line number and indention
|
|
function! s:ReadCurrentLine()
|
|
let line_no = line('.')
|
|
let line = getline(line_no)
|
|
let whitespace = s:GetIndentWhitespace(line_no)
|
|
|
|
return [line, line_no, whitespace]
|
|
endfunction
|
|
|
|
" Strip comments from string starting with a #
|
|
function! s:StripComment(s)
|
|
return substitute(a:s, '\v\s+#.*$', '', '')
|
|
endfunction
|
|
|
|
" Check if current buffer has the line number
|
|
function! s:IsValidLineNo(no)
|
|
return a:no >= 0 && a:no <= line('$')
|
|
endfunction
|
|
|
|
" Normalize whitespace, if enabled
|
|
function! s:NormalizeWhitespace(lines)
|
|
if sj#settings#Read('normalize_whitespace')
|
|
return map(a:lines, 'substitute(v:val, ":\\s\\+", ": ", "")')
|
|
endif
|
|
return a:lines
|
|
endfunction
|
|
|
|
function! s:GetIndentWhitespace(line_no)
|
|
return substitute(getline(a:line_no), '^\(\s*\).*$', '\1', '')
|
|
endfunction
|
|
|
|
function! s:SetIndentWhitespace(line_no, whitespace)
|
|
silent call sj#Keeppatterns(a:line_no . 's/^\s*/' . a:whitespace)
|
|
endfunction
|
|
|
|
function! s:IncreaseIndentWhitespace(from, to, whitespace, level)
|
|
if a:whitespace =~ "\t"
|
|
let new_whitespace = a:whitespace . repeat("\t", a:level)
|
|
else
|
|
let new_whitespace = a:whitespace . repeat(' ', &sw * a:level)
|
|
endif
|
|
|
|
for line_no in range(a:from, a:to)
|
|
call s:SetIndentWhitespace(line_no, new_whitespace)
|
|
endfor
|
|
endfunction
|
|
|
|
" Get following lines with a greater indent than the current line
|
|
function! s:GetChildren(line_no)
|
|
let line_no = a:line_no
|
|
let next_line_no = line_no + 1
|
|
let indent = indent(line_no)
|
|
let next_line = getline(next_line_no)
|
|
|
|
" Count '- ' as indent, if an object is in an array
|
|
" E.g. (GetChildren for prop_a)
|
|
" list:
|
|
" - prop_a:
|
|
" - 1
|
|
" prop_b
|
|
" - 2
|
|
let line = getline(line_no)
|
|
if line =~ '^\s*\(\-\s\s*\)..*:$'
|
|
let prefix = substitute(getline(a:line_no), '^\s*\(\-\s\s*\)..*:$', '\1', '')
|
|
let indent += len(prefix)
|
|
end
|
|
|
|
while s:IsValidLineNo(next_line_no) &&
|
|
\ (sj#BlankString(next_line) || indent(next_line_no) > indent)
|
|
let next_line_no = next_line_no + 1
|
|
let next_line = getline(next_line_no)
|
|
endwhile
|
|
let next_line_no = next_line_no - 1
|
|
|
|
" Preserve trailing empty lines
|
|
while sj#BlankString(getline(next_line_no)) && next_line_no > line_no
|
|
let next_line_no = next_line_no - 1
|
|
endwhile
|
|
|
|
return [sj#GetLines(line_no + 1, next_line_no), next_line_no]
|
|
endfunction
|
|
|
|
" Split a string into individual array items.
|
|
" E.g.
|
|
" 'one, two' => ['one', 'two']
|
|
" '{ one: 1 }, { two: 2 }' => ['{ one: 1 }', '{ two: 2 }']
|
|
function! s:SplitArrayBody(body)
|
|
let items = []
|
|
|
|
let partial_item = ''
|
|
let rest = sj#Ltrim(a:body)
|
|
|
|
while !empty(rest)
|
|
let char = rest[0]
|
|
|
|
if char == '{'
|
|
let [item, rest] = s:ReadMap(rest)
|
|
let rest = s:SkipWhitespaceUntilComma(rest)
|
|
|
|
call add(items, s:StripCurlyBrackets(item))
|
|
|
|
elseif char == '['
|
|
let [item, rest] = s:ReadArray(rest)
|
|
let rest = s:SkipWhitespaceUntilComma(rest)
|
|
|
|
call add(items, sj#Trim(item))
|
|
|
|
elseif char == '"' || char == "'"
|
|
let [item, rest] = s:ReadString(rest)
|
|
let rest = s:SkipWhitespaceUntilComma(rest)
|
|
|
|
call add(items, sj#Trim(item))
|
|
|
|
else
|
|
let [item, rest] = s:ReadUntil(rest, ',')
|
|
call add(items, sj#Trim(item))
|
|
endif
|
|
|
|
let rest = sj#Ltrim(rest[1:])
|
|
endwhile
|
|
|
|
return items
|
|
endfunction
|
|
|
|
" Read string until occurence of end_char
|
|
function! s:ReadUntil(str, end_char)
|
|
let idx = 0
|
|
while idx < len(a:str)
|
|
if a:str[idx] == a:end_char
|
|
return idx == 0
|
|
\ ? ['', a:str[1:]]
|
|
\ : [a:str[:idx-1], a:str[idx+1:]]
|
|
endif
|
|
|
|
let idx += 1
|
|
endwhile
|
|
|
|
return [a:str, '']
|
|
endfunction
|
|
|
|
" Read the next string fenced by " or '
|
|
function! s:ReadString(str)
|
|
if len(a:str) > 0
|
|
let fence = a:str[0]
|
|
if fence == '"' || fence == "'"
|
|
let [str, rest] = s:ReadUntil(a:str[1:], fence)
|
|
return [fence . str . fence, rest]
|
|
endif
|
|
endif
|
|
|
|
return ['', a:str]
|
|
endfunction
|
|
|
|
" Read the next array, including nested arrays.
|
|
" E.q.
|
|
" '[[1, 2]], [1]' => ['[[1, 2]], ', [1]']
|
|
function! s:ReadArray(str)
|
|
return s:ReadContainer(a:str, '[', ']')
|
|
endfunction
|
|
|
|
" Read the next map, including nested maps.
|
|
" E.q.
|
|
" '{ one: 1, foo: { two: 2 } }, {}' => ['{ one: 1, foo: { two: 2 } }, ', {}']
|
|
function! s:ReadMap(str)
|
|
return s:ReadContainer(a:str, '{', '}')
|
|
endfunction
|
|
|
|
function! s:ReadContainer(str, start_char, end_char)
|
|
let content = ''
|
|
let rest = a:str
|
|
let depth = 0
|
|
|
|
while !empty(rest)
|
|
let char = rest[0]
|
|
let rest = rest[1:]
|
|
|
|
let content .= char
|
|
|
|
if char == a:start_char
|
|
let depth += 1
|
|
elseif char == a:end_char
|
|
let depth -= 1
|
|
|
|
if depth == 0 | break | endif
|
|
endif
|
|
endwhile
|
|
|
|
return [content, rest]
|
|
endfunction
|
|
|
|
" skip whitespace and next comma
|
|
function! s:SkipWhitespaceUntilComma(str)
|
|
let [space, rest] = s:ReadUntil(a:str, ',')
|
|
|
|
if !sj#BlankString(space)
|
|
throw '"' . space . '" is not whitespace!'
|
|
end
|
|
return rest
|
|
endfunction
|
|
|
|
function! s:JoinArrayItems(items)
|
|
return join(map(a:items, 's:AddCurlyBrackets(v:val)'), ', ')
|
|
endfunction
|
|
|
|
" Add curly brackets if required for joining
|
|
" E.g.
|
|
" 'one: 1' => '{ one: 1 }'
|
|
" 'one' => 'one'
|
|
function! s:AddCurlyBrackets(line)
|
|
let line = sj#Trim(a:line)
|
|
|
|
if line !~ '^\v\[.*\]$' && line !~ '^\v\{.*\}$'
|
|
let [key, value] = s:SplitKeyValue(line)
|
|
if key != ''
|
|
return '{ ' . a:line . ' }'
|
|
endif
|
|
endif
|
|
|
|
return a:line
|
|
endfunction
|
|
|
|
" Strip curly brackets if possible
|
|
" E.g.
|
|
" '{ one: 1 }' => 'one: 1'
|
|
" '{ one: 1, two: 2 }' => '{ one: 1, two: 2 }'
|
|
function! s:StripCurlyBrackets(item)
|
|
let item = sj#Trim(a:item)
|
|
|
|
if item =~ '^{.*}$'
|
|
let parser = sj#argparser#js#Construct(2, len(item) - 1, item)
|
|
call parser.Process()
|
|
|
|
if len(parser.args) == 1
|
|
let item = substitute(item, '^{\s*', '', '')
|
|
let item = substitute(item, '\s*}$', '', '')
|
|
endif
|
|
endif
|
|
|
|
return item
|
|
endfunction
|
|
|
|
" Split a string into key and value
|
|
" E.g.
|
|
" 'one: 1' => ['one', '1']
|
|
" 'one' => ['', 'one']
|
|
" 'one:' => ['one', '']
|
|
" 'a:val' => ['', 'a:val']
|
|
function! s:SplitKeyValue(line)
|
|
let line = sj#Trim(a:line)
|
|
let parts = []
|
|
|
|
let first_char = line[0]
|
|
|
|
let key = ''
|
|
let value = ''
|
|
|
|
" Read line starts with a fenced string. E.g
|
|
" 'one': 1
|
|
" 'one'
|
|
if first_char == '"' || first_char == "'"
|
|
let [key, rest] = s:ReadString(line)
|
|
let [_, value] = s:ReadUntil(rest, ':')
|
|
else
|
|
let parts = split(line . ' ', ': ')
|
|
let [key, value] = [parts[0], join(parts[1:], ': ')]
|
|
endif
|
|
|
|
if value == '' && a:line !~ '\s*:$'
|
|
let [key, value] = ['', key]
|
|
endif
|
|
|
|
return [sj#Trim(key), sj#Trim(value)]
|
|
endfunction
|
|
|
|
" Calculate the nesting level of an array item
|
|
" E.g.
|
|
" - foo => 1
|
|
" - - bar => 2
|
|
function! s:NestedArrayLevel(line)
|
|
let prefix = substitute(a:line, '\v^\s*((-\s+)+).*', '\1', '')
|
|
let levels = substitute(prefix, '[^-]', '', 'g')
|
|
return len(levels)
|
|
endfunction
|