Skip to content

Instantly share code, notes, and snippets.

@morefun2compute
Last active January 30, 2017 17:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save morefun2compute/bdc6ee6d8b9add60a03b318bd91ba570 to your computer and use it in GitHub Desktop.
Save morefun2compute/bdc6ee6d8b9add60a03b318bd91ba570 to your computer and use it in GitHub Desktop.
Vim indent file for Swift
" Vim indent file
" Language: swift
" Author: Aaron Bohannon <bohannon@cis.upenn.edu>
" DESCRIPTION:
"
" Provides indentation support for the Swift programming language. This
" plugin cannot stand entirely on its own. It depends upon having support for
" syntax highlighting so that it can determine whether any given bracket
" character in the file should affect the indentation. It will ignore bracket
" characters that are in a syntax group with a name matching one of these
" patterns: `swiftComment`, `swiftLineComment`, `swiftString`,
" `swiftInterpolated*`.
" CONFIGURATION:
"
" The following variables can be defined in order to control the indentation:
"
" - g:swiftIndentAfterBrace (default: 1 * shiftwidth())
" - g:swiftIndentAfterSquareBracket (default: 1 * shiftwidth())
" - g:swiftIndentAfterParenthesis (default: 2 * shiftwidth())
" - g:swiftIndentAfterAngleBracket (default: 4 * shiftwidth())
" - g:swiftIndentSwitchCasePattern (default: 0 * shiftwidth())
" - g:swiftIndentSwitchCaseBody (default: 1 * shiftwidth())
" - g:swiftIndentStatementContinuation (default: 2 * shiftwidth())
"
" The values for `g:swiftIndentSwitchCasePattern` and for
" `g:swiftIndentSwitchCaseBody` are both interpreted as relative to
" the indentation of the `switch` keyword. The value for
" `g:swiftIndentStatementContinuation` applies to situations like those where
" a line break occurs before the `where` keyword within a `case` statement or
" `for` statement.
" KNOWN ISSUES:
"
" - A line beginning with `#line` is always interpreted as a line control
" statement (and assigned an indent of 0). This is not necessarily correct
" in every situation since `#line` can also be used as a literal within an
" expression.
"
" - No additional indentation is added as the result of a binary operator
" occurring before or after a line break. You can ensure that additional
" indentation will be added after the line breaks within an expression by
" wrapping the entire expression in parentheses.
"
" - This plugin is designed to add extra indentation after line breaks that
" occur between a pair of opening and closing angle brackets. To achieve
" this behavior, it is necessary for this plugin to determine whether a
" given occurrence of `<` or `>` is functioning as a bracket around a type
" parameter or as part of an operator token. This ambiguity is
" troublesome even for full-scale parser implementations, so this plugin
" cannot be expected to correctly handle every case that is theoretically
" possible. However, you should always be able to get this plugin to
" correctly interpret the meaning of the `<` and `>` symbols by adding or
" removing whitespace or parentheses in an appropriate manner, and
" alterations of that sort shouldn't be necessary for code that follows
" common conventions. You will not encounter any problems at all if you
" follow three simple rules: (1) put whitespace on both sides of binary
" operators, (2) don't begin a line with a binary operator, and (3) never
" put whitespace before a closing `>` bracket. However, there happen to be
" a couple of common idioms in Swift that violate those rules (for instance,
" expressions like `0..<5` or `a..<b`). This plugin can be expected to
" handle those idioms properly, but having that capability makes the
" behavior of the plugin far more difficult to describe. Here is a precise
" description of Swift code that will be properly understood by this plugin:
"
" * If the symbol `<` is intended to serve as a bracket, then one of the
" following conditions must be met:
"
" - it occurs at the end of a line beginning with the keyword `func`
" - it is immediately preceded by the pattern `[A-Za-z0-9_]`
" - it is immediately followed by the pattern `[A-Z_([]`
"
" Otherwise, the symbol will be interpreted as part of an operator.
"
" * If the symbol `>` is intended to serve as a bracket and it is *not*
" the first character on a line, then it must either be immediately
" preceded by a character matching `[A-Za-z0-9_>?!)\]]`.
"
" * If the symbol `>` is the first character on a line and is intended to
" serve as part of an operator, then one of the following conditions
" must be met:
"
" - it is followed by the pattern `>*[^>([:blank:]]` (i.e., it is part
" of an operator that contains a character other than `>`)
" - the previous line of code matches `^.*([^()]*$` (i.e., the
" previous line of code contains an unclosed parenthesis)
" - the previous line of code matches this regex: '^\s*>\+[^>,]\s*\S'
" (i.e. the previous line of code also begins with an operator whose
" first character is `>`)
"
" Otherwise, the symbol will be interpreted as a bracket.
"
" - When a closing `>` bracket appears at the start of a line, it will always
" be indented as though it were an operator rather than indented in a manner
" that matches situations in which other sorts of brackets appear at the
" beginning of a line.
"
" - This plugin might help you detect syntactic errors in your code, but it is
" not designed for that purpose -- nor is it designed with error recovery in
" mind. It is designed to provide useful behavior only when the buffer
" being edited contains the prefix of some valid Swift file.
" LICENSE:
"
" This work is licensed under the Creative Commons Attribution 4.0
" International License. To view a copy of this license, visit
" http://creativecommons.org/licenses/by/4.0/ or send a letter to Creative
" Commons, PO Box 1866, Mountain View, CA 94042, USA.
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
if exists('b:did_indent')
finish
endif
let b:did_indent = 1
let s:save_cpo = &cpo
set cpo&vim
setlocal nosmartindent
setlocal indentkeys-=e
setlocal indentkeys-=:
setlocal indentkeys+=0]
setlocal indentkeys+=0)
setlocal indentkeys+=0>
setlocal indentkeys+==case
setlocal indentkeys+==default
setlocal indentexpr=GetSwiftIndent()
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Configuration
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let s:indentAfterBrace = 1 * shiftwidth()
let s:indentAfterSquareBracket = 1 * shiftwidth()
let s:indentAfterParenthesis = 2 * shiftwidth()
let s:indentAfterAngleBracket = 4 * shiftwidth()
let s:indentSwitchCasePattern = 0 * shiftwidth()
let s:indentSwitchCaseBody = 1 * shiftwidth()
let s:indentStatementContinuation = 2 * shiftwidth()
function! s:GetRelativeIndent(type)
return get(g:, 'swiftIndent' . a:type, s:indent{a:type})
endfunction
function! s:ExtraIndentForUnclosedBrackets(unclosedBrackets)
let extraIndent = 0
for bracket in a:unclosedBrackets
if bracket == '{'
let extraIndent += s:GetRelativeIndent('AfterBrace')
elseif bracket == '['
let extraIndent += s:GetRelativeIndent('AfterSquareBracket')
elseif bracket == '('
let extraIndent += s:GetRelativeIndent('AfterParenthesis')
elseif bracket == '<'
let extraIndent += s:GetRelativeIndent('AfterAngleBracket')
endif
endfor
return extraIndent
endfunction
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Memoization
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Performance improves dramatically if results from frequently called functions
" are memoized. However, we want the results of memoization to persist across
" multiple independent invocations of the `GetSwiftIndent()` function if the
" buffer has not been changed. This is a common situation since it occurs
" whenever the user reformats multiple lines at once. So, we will store the
" memoized results of the function in a buffer-local cache that will be
" considered invalid after any change to `b:changedtick` or after we have
" entered insert mode. (This latter case must be considered separately since
" entering insert mode and typing characters does not increment `b:changedtick`
" -- it is typically updated upon leaving insert mode.)
let b:swiftIndentResultCache = {}
let b:swiftIndentResultCacheValidThroughTick = -1
function! s:ResultCacheHasExpired()
return (mode() =~# 'i\|R')
\ || (b:swiftIndentResultCacheValidThroughTick < b:changedtick)
endfunction
function! s:PrepareResultCache()
if s:ResultCacheHasExpired()
let b:swiftIndentResultCache = {}
let b:swiftIndentResultCacheValidThroughTick = b:changedtick
endif
endfunction
" This function will be used to cache values associated with the position of a
" character in the buffer. It describes the position in a way that is valid
" even after a line's indentation has has changed.
function! s:TextLocationID(lnum, cnum)
return string([a:lnum, a:cnum - indent(a:lnum)])
endfunction
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Character classification
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let s:TEXT_TYPE_NORMAL = 0
let s:TEXT_TYPE_BRACKET = 1
let s:TEXT_TYPE_STRING = 2
let s:TEXT_TYPE_CONFIGURATION = 3
let s:TEXT_TYPE_WHITESPACE = 4
let s:TEXT_TYPE_COMMENT = 5
function! s:TextTypeString(textType)
if a:textType == s:TEXT_TYPE_NORMAL
return "NORMAL"
elseif a:textType == s:TEXT_TYPE_BRACKET
return "BRACKET"
elseif a:textType == s:TEXT_TYPE_STRING
return "STRING"
elseif a:textType == s:TEXT_TYPE_CONFIGURATION
return "CONFIGURATION"
elseif a:textType == s:TEXT_TYPE_WHITESPACE
return "WHITESPACE"
elseif a:textType == s:TEXT_TYPE_COMMENT
return "COMMENT"
else
return "<unknown>"
endif
endfunction
function! s:GetCharacter(lnum, cnum)
return getline(a:lnum)[a:cnum - 1]
endfunction
function! s:SyntaxName(lnum, cnum)
return synIDattr(synID(a:lnum, a:cnum, 0), 'name')
endfunction
function! s:SearchLineAtColumn(lnum, cnum, patternBefore, patternAt)
let pattern = printf('\C%s\%%%dc%s', a:patternBefore, a:cnum, a:patternAt)
return match(getline(a:lnum), pattern) >= 0
endfunction
function! s:SearchStartOfLineForOneOf(lnum, patterns)
for pattern in a:patterns
if getline(a:lnum) =~# ('^\s*' . pattern)
return 1
endif
endfor
return 0
endfunction
function! s:ComputeTextType(lnum, cnum)
let character = s:GetCharacter(a:lnum, a:cnum)
call s:DebugMsg('character at (%d, %d) is "%s"', a:lnum, a:cnum, character)
let syntaxName = s:SyntaxName(a:lnum, a:cnum)
call s:DebugMsg('syntax name is "%s"', syntaxName)
if s:SearchStartOfLineForOneOf(a:lnum,
\ ['#if\>', '#elseif\>', '#else\>', '#endif\>', '#line\>'])
return s:TEXT_TYPE_CONFIGURATION
endif
if syntaxName =~# 'swift\%(Line\)\?Comment'
return s:TEXT_TYPE_COMMENT
elseif syntaxName =~# 'swiftString'
return s:TEXT_TYPE_STRING
elseif syntaxName =~# 'swiftInterpolated*'
return s:TEXT_TYPE_STRING
elseif character ==# '<'
if s:SearchLineAtColumn(a:lnum, a:cnum, '\s*func\s.*', '<\s*\%(//.*\)\?$')
return s:TEXT_TYPE_BRACKET
elseif s:SearchLineAtColumn(a:lnum, a:cnum, '[A-Za-z0-9_]', '<')
return s:TEXT_TYPE_BRACKET
elseif s:SearchLineAtColumn(a:lnum, a:cnum, '', '<[A-Z_([]')
return s:TEXT_TYPE_BRACKET
else
return s:TEXT_TYPE_NORMAL
endif
elseif character ==# '>'
if s:SearchLineAtColumn(a:lnum, a:cnum, '[A-Za-z0-9_?!)\]]>*', '>')
return s:TEXT_TYPE_BRACKET
elseif s:SearchLineAtColumn(a:lnum, a:cnum, '^\s*>*', '>>*[^>([:blank:]]')
return s:TEXT_TYPE_NORMAL
elseif s:SearchLineAtColumn(a:lnum, a:cnum, '^\s*>*', '>')
" Note: this next line can call this function recusively but will do so
" only with a value of `a:lnum` that is strictly smaller.
let previousLineText = getline(s:PreviousNormalLine(a:lnum))
if previousLineText =~# '^.*([^()]*$'
return s:TEXT_TYPE_NORMAL
elseif previousLineText =~# '^\s*>\+[^>,]\s*\S'
return s:TEXT_TYPE_NORMAL
else
return s:TEXT_TYPE_BRACKET
endif
else
return s:TEXT_TYPE_NORMAL
endif
elseif stridx('[({})]', character) >= 0
return s:TEXT_TYPE_BRACKET
elseif character =~# '\s'
return s:TEXT_TYPE_WHITESPACE
else
return s:TEXT_TYPE_NORMAL
endif
endfunction
function! s:GetTextType(lnum, cnum)
let key = s:TextLocationID(a:lnum, a:cnum)
if has_key(b:swiftIndentResultCache, key)
return b:swiftIndentResultCache[key]
endif
let lineKey = string(a:lnum)
if has_key(b:swiftIndentResultCache, lineKey)
\ && (b:swiftIndentResultCache[lineKey] != s:TEXT_TYPE_NORMAL)
let result = b:swiftIndentResultCache[lineKey]
else
let result = s:ComputeTextType(a:lnum, a:cnum)
if result == s:TEXT_TYPE_CONFIGURATION
" In this one case, we know the result applies to the entire line.
let b:swiftIndentResultCache[lineKey] = result
endif
endif
let b:swiftIndentResultCache[key] = result
return result
endfunction
function! s:GetLineType(lnum)
let lineKey = string(a:lnum)
if has_key(b:swiftIndentResultCache, lineKey)
return b:swiftIndentResultCache[lineKey]
endif
if getline(a:lnum) =~# '^\s*$'
let result = s:TEXT_TYPE_WHITESPACE
else
let type = s:GetTextType(a:lnum, indent(a:lnum) + 1)
if (type == s:TEXT_TYPE_CONFIGURATION) || (type == s:TEXT_TYPE_COMMENT)
let result = type
else
let result = s:TEXT_TYPE_NORMAL
endif
endif
let b:swiftIndentResultCache[lineKey] = result
return result
endfunction
" Returns the largest line number that is strictly smaller than `a:lnum` such
" that `s:GetLineType(a:lnum) == s:TEXT_TYPE_NORMAL`. Returns 0 if no such line
" exists.
function! s:PreviousNormalLine(lnum)
let l:lnum = a:lnum - 1
while (l:lnum > 0) && (s:GetLineType(l:lnum) != s:TEXT_TYPE_NORMAL)
let l:lnum -= 1
endwhile
return l:lnum
endfunction
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Text inspection functions
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
function! s:GetCharacterUnderCursor()
return getline('.')[col('.') - 1]
endfunction
function! s:CharacterUnderCursorIsNotBracket()
return s:GetTextType(line('.'), col('.')) != s:TEXT_TYPE_BRACKET
endfunction
function! s:LineOfCodeEndsWithCharacter(lnum, character)
let line = getline(a:lnum)
" The regex below accounts for both trailing whitespace and line comments.
let matchPos = match(line, '\S\s*\%(//.*\)\?$')
if matchPos < 0
return 0
else
return line[matchPos] ==# a:character
endif
endfunction
function! s:PreviousLineWithSmallerIndent(lnum)
if indent(a:lnum) == 0
return 0
endif
let l:lnum = s:PreviousNormalLine(a:lnum)
while indent(l:lnum) >= indent(a:lnum)
let l:lnum = s:PreviousNormalLine(l:lnum)
endwhile
return l:lnum
endfunction
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Code processing functions
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Returns non-zero if successful. Finds opening brackets regardless of whether
" the buffer contains a matching closing bracket.
function! s:MoveCursorBackToEnclosingOpeningBracket(
\ lnumStop, allowMatchAtCursor)
let flags = a:allowMatchAtCursor ? 'bcW' : 'bW'
return searchpair('[<([{]', '', '[}\])>]', flags,
\ 's:CharacterUnderCursorIsNotBracket()', a:lnumStop)
endfunction
" Returns non-zero if successful.
function! s:MoveCursorBackToNearestClosingBracket(lnumStop, allowMatchAtCursor)
let flags = a:allowMatchAtCursor ? 'bcW' : 'bW'
" The implementation of this function is counter-intuitive. One would expect
" to see the `search()` function used here; however, using that function would
" be difficult since it is crucial that the cursor not be moved if no
" acceptable match is found. Unlike the `search()` function, the
" `searchpair()` function can be supplied with a pattern specifying which
" matches should be ignored. Thus, we (ab)use the `searchpair()` function
" here, tricking it into thinking that closing brackets are actually unmatched
" opening brackets.
return searchpair('[}\])>]', '', '$^', flags,
\ 's:CharacterUnderCursorIsNotBracket()', a:lnumStop)
endfunction
" Returns non-zero if successful. This function is intended to be called only
" when the character under the cursor is a closing bracket.
function! s:MoveCursorBackToMatchingOpeningBracket()
return searchpair('[<([{]', '', '[}\])>]', 'bW',
\ 's:CharacterUnderCursorIsNotBracket()')
endfunction
" The term "clause" will be used to denote any contiguous sequence of lines in
" that contains a well-balanced sequence of brackets. This function moves the
" cursor to the first line of the smallest clause that includes the cursor's
" initial location. As it does this, every unmatched opening bracket that
" is encountered between the cursor's initial location and the beginning of the
" clause will be recorded.
function! s:MoveCursorToFirstLineOfClause(unclosedBrackets, firstInvocation)
if s:MoveCursorBackToEnclosingOpeningBracket(line('.'), a:firstInvocation)
call add(a:unclosedBrackets, s:GetCharacterUnderCursor())
return s:MoveCursorToFirstLineOfClause(a:unclosedBrackets, 0)
elseif s:MoveCursorBackToNearestClosingBracket(line('.'), a:firstInvocation)
if s:MoveCursorBackToMatchingOpeningBracket()
return s:MoveCursorToFirstLineOfClause(a:unclosedBrackets, 0)
else
" Indicates mismatched brackets.
return 0
endif
else
return line('.')
endif
endfunction
" For the purposes of this plugin, a "statement" is a clause that meets four
" conditions:
" A. with respect to its indentation, the first line of the clause is at the
" top level of a block of code -- i.e., its nearest enclosing brackets (if
" it has any) are braces (`{`/`}`), and no line between it and the previous
" unclosed `{` (or the beginning of the file) has strictly less indentation
" B. none of the lines of the clause is indented less than the first
" C. the line immediately after the clause is not indented more than the last
" line of the clause -- unless the line ends with a `:`
" D. the line immediately after the clause does not begin with an opening
" brace
"
" As an example, consider these four lines of code:
"
" 1: for i in 0 ..< 10
" 2: where (i % 2) == 0 {
" 3: f(i)
" 4: }
"
" Line 1 alone is a clause, and lines 2-4 are also a clause; however, neither of
" those two clauses are statements. There are only two statements that can be
" identified: line 3 alone and lines 1-4 together.
"
" The function below is designed to find the first line of the smallest
" statement that includes the cursor's initial location -- and will return that
" line number -- but only in cases where the end of the smallest clause that
" includes the cursor's location is the suffix of some statement, as would be
" the case in the example above if the cursor began on line 2. This function
" must only search backward from the cursor location, though. So, it does not
" return 0 unless it is clear that the ending of the immediate clause could not
" somehow be made into the ending of a statement by adjusting the indentation of
" the following lines. Regardless of the return value, the cursor will be moved
" to the nearest enclosing opening bracket relative to its initial location (if
" there is such an enclosing opening bracket).
function! s:FindFirstLineOfStatement()
" We begin by addressing condition D.
let lnumPrevCode = s:PreviousNormalLine(line('.'))
while s:SearchStartOfLineForOneOf(line('.'), ['{'])
call s:DebugMsg(
\ 'searching upward past line %d for start of statement',
\ line('.'))
call cursor(lnumPrevCode, col([lnumPrevCode, '$']))
if !s:MoveCursorToFirstLineOfClause([], 1)
return 0
endif
let lnumPrevCode = s:PreviousNormalLine(line('.'))
endwhile
" Now we address condition A.
let lnumCurrent = line('.')
if s:MoveCursorBackToEnclosingOpeningBracket(1, 0)
let bracket = s:GetCharacterUnderCursor()
if bracket !=# '{'
call s:DebugMsg('clause is enclosed by %s', bracket)
return 0
endif
let lnumEnclosingScopeBoundary = line('.')
else
" We're already at the top level of the file.
let lnumEnclosingScopeBoundary = 0
endif
" Now we address conditions B and C.
let lnumParent = s:PreviousLineWithSmallerIndent(lnumCurrent)
while lnumParent > lnumEnclosingScopeBoundary
\ && !s:LineOfCodeEndsWithCharacter(lnumParent, ':')
let lnumCurrent = lnumParent
let lnumParent = s:PreviousLineWithSmallerIndent(lnumCurrent)
endwhile
return lnumCurrent
endfunction
" Returns a dictionary with the following fields:
" - `unclosedBrackets`: List of unbalanced opening bracket characters
" encountered between the cursor and the first line of the smallest clause
" that includes the cursor's initial position.
" - `lnumClause`: First line of the smallest clause that encompassed the
" cursor's initial position. If obvious syntax errors are encountered, this
" will be 0.
" - `lnumStatement`: First line of the smallest statement that encompassed the
" cursor's initial position. If the buffer contains no syntax errors and
" this value is 0, it means that the cursor began in a context where
" indentation can be determined without examining that statement.
" - `lnumEnclosingStatement`: First line of the smallest statement that
" includes the nearest enclosing brackets around `lnumStatement`. If the
" buffer contains no syntax errors and this value is 0, it means that the
" cursor began in a context where indentation can be determined without
" examining this statement. The only reason to examine this statement is to
" know whether we are inside a `switch` body, where special rules for
" indentation are used.
function! s:GatherCursorContext()
let result = {
\ 'unclosedBrackets': [],
\ 'lnumClause': 0,
\ 'lnumStatement': 0,
\ 'lnumEnclosingStatement': 0
\ }
let result.lnumClause = s:MoveCursorToFirstLineOfClause(
\ result.unclosedBrackets, 1)
if result.lnumClause > 0
let result.lnumStatement = s:FindFirstLineOfStatement()
endif
if result.lnumStatement > 0
let result.lnumEnclosingStatement = s:FindFirstLineOfStatement()
endif
call s:DebugMsg('unclosedBrackets = %s', string(result.unclosedBrackets))
call s:DebugMsg('lnumClause = %d', result.lnumClause)
call s:DebugMsg('lnumStatement = %d', result.lnumStatement)
call s:DebugMsg('lnumEnclosingStatement = %d', result.lnumEnclosingStatement)
return result
endfunction
function! s:FindCasePatternTerminationLine(lnumStart, lnumMax)
if a:lnumStart >= a:lnumMax
return -1
elseif s:LineOfCodeEndsWithCharacter(a:lnumStart, ':')
return a:lnumStart
else
return s:FindCasePatternTerminationLine(a:lnumStart + 1, a:lnumMax)
endif
endfunction
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" GetSwiftIndent()
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
function! GetSwiftIndent()
call s:PrepareResultCache()
let lnumCurrent = line('.')
let lineType = s:GetLineType(lnumCurrent)
if lineType == s:TEXT_TYPE_COMMENT
call s:DebugMsg('---- line is a comment ----')
return -1
elseif lineType == s:TEXT_TYPE_CONFIGURATION
call s:DebugMsg('---- line is a build configuration statement ----')
return 0
endif
" In the test below, we ignore the possibility of a `>` bracket for simplicity
" since a `>` may also appear at the start of a line as part of an operator.
if s:SearchStartOfLineForOneOf(lnumCurrent, ['}', ']', ')'])
normal! ^
if s:MoveCursorBackToMatchingOpeningBracket() == 0
return -1
endif
let context = s:GatherCursorContext()
if context.lnumClause == 0
return -1
endif
" We remove the first unclosed bracket from the list since it will be
" matched by the closing bracket at the beginning of the line `lnumCurrent`.
call remove(context.unclosedBrackets, 0)
if s:SearchStartOfLineForOneOf(lnumCurrent, ['}'])
\ && empty(context.unclosedBrackets)
\ && (context.lnumStatement != 0)
call s:DebugMsg('---- line terminates code block ----')
return indent(context.lnumStatement)
else
call s:DebugMsg('---- line begins with }/]/) ----')
return indent(context.lnumClause)
\ + s:ExtraIndentForUnclosedBrackets(context.unclosedBrackets)
endif
endif
if s:SearchStartOfLineForOneOf(lnumCurrent, ['\<case\>', '\<default\>'])
normal! $
let context = s:GatherCursorContext()
if context.lnumClause == 0
return -1
endif
if s:SearchStartOfLineForOneOf(
\ context.lnumEnclosingStatement, ['\<switch\>'])
call s:DebugMsg('---- line begins switch case pattern ----')
return indent(context.lnumEnclosingStatement)
\ + s:GetRelativeIndent('SwitchCasePattern')
endif
endif
let lnumPrevCode = s:PreviousNormalLine(lnumCurrent)
call cursor(lnumPrevCode, col([lnumPrevCode, '$']))
let context = s:GatherCursorContext()
if context.lnumClause == 0
return -1
endif
if s:SearchStartOfLineForOneOf(lnumCurrent, ['{'])
if empty(context.unclosedBrackets) && (context.lnumStatement != 0)
call s:DebugMsg('---- line begins with { (statement level) ----')
return indent(context.lnumStatement)
else
call s:DebugMsg('---- line begins with { (expression level) ----')
return indent(context.lnumClause)
\ + s:ExtraIndentForUnclosedBrackets(context.unclosedBrackets)
endif
endif
if context.unclosedBrackets == ['{']
if context.lnumStatement != 0
call s:DebugMsg('---- first line of code block (statement level) ----')
return indent(context.lnumStatement) + s:GetRelativeIndent('AfterBrace')
else
call s:DebugMsg('---- first line of code block (expression level) ----')
return indent(context.lnumClause) + s:GetRelativeIndent('AfterBrace')
endif
endif
if s:LineOfCodeEndsWithCharacter(lnumPrevCode, '}')
if empty(context.unclosedBrackets)
\ && (context.lnumStatement != 0)
call s:DebugMsg('---- line follows code block (statement level) ----')
return indent(context.lnumStatement)
else
" This would be quite peculiar.
call s:DebugMsg('---- line follows code block (expression level) ----')
return indent(context.lnumClause)
\ + s:ExtraIndentForUnclosedBrackets(context.unclosedBrackets)
endif
endif
if s:SearchStartOfLineForOneOf(context.lnumStatement, [
\ '\<func\>',
\ '\<for\>',
\ '\<while\>',
\ '\<repeat\>',
\ '\<if\>',
\ '\<else\>',
\ '\<guard\>',
\ '\<switch\>',
\ '\<defer\>',
\ '\<do\>',
\ '\<catch\>',
\ '\<deinit\>'
\ ])
\ && empty(context.unclosedBrackets)
" This applies to lines of code that begin in a context where the
" opening `{` for a code block is expected but has not yet occurred.
call s:DebugMsg('---- line continues unfinished statement ----')
return indent(context.lnumStatement)
\ + s:GetRelativeIndent('StatementContinuation')
endif
if s:SearchStartOfLineForOneOf(
\ context.lnumStatement, ['\<case\>', '\<default\>'])
\ && s:SearchStartOfLineForOneOf(context.lnumEnclosingStatement,
\ ['\<switch\>'])
\ && empty(context.unclosedBrackets)
if s:FindCasePatternTerminationLine(context.lnumStatement, lnumCurrent) < 0
call s:DebugMsg('---- line continues an unfinished case pattern ----')
return indent(context.lnumStatement)
\ + s:GetRelativeIndent('StatementContinuation')
else
call s:DebugMsg('---- line in a switch case body ----')
return indent(context.lnumEnclosingStatement)
\ + s:GetRelativeIndent('SwitchCaseBody')
endif
endif
" At this point, we should have exhausted all of the situations in which the
" the surrounding statement differs from the surrounding clause and is
" relevant to the indentation of this line.
call s:DebugMsg('---- line continues previous clause (default) ----')
return indent(context.lnumClause)
\ + s:ExtraIndentForUnclosedBrackets(context.unclosedBrackets)
endfunction
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Debugging
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
function! s:DebugMsg(...)
if exists('s:debug') && s:debug
if a:0 > 1
echo call('printf', a:000)
else
echo a:1
endif
endif
endfunction
function! DebugSwiftTextType()
let s:debug = 1
let b:swiftIndentResultCache = {}
let result = s:TextTypeString(s:GetTextType(line('.'), col('.')))
unlet s:debug
return result
endfunction
function! DebugSwiftIndent(...)
let s:debug = 1
let savedPosition = getcurpos()
if a:0 > 0
let lnumCurrent = a:1
call cursor(lnumCurrent, 1)
endif
let result = GetSwiftIndent()
call setpos('.', savedPosition)
unlet s:debug
return result
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment