diff runtime/indent/clojure.vim @ 9644:9f7bcc2c3b97

commit https://github.com/vim/vim/commit/6f1d9a096bf22d50c727dca73abbfb8e3ff55176 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Jul 24 14:12:38 2016 +0200 Updated runtime files.
author Christian Brabandt <cb@256bit.org>
date Sun, 24 Jul 2016 14:15:06 +0200
parents c52a655d927d
children 63b0b7b79b25
line wrap: on
line diff
--- a/runtime/indent/clojure.vim
+++ b/runtime/indent/clojure.vim
@@ -1,17 +1,12 @@
 " Vim indent file
-" Language:	Clojure
-" Author:	Meikel Brandmeyer <mb@kotka.de>
-" URL:		http://kotka.de/projects/clojure/vimclojure.html
+" Language:     Clojure
+" Author:       Meikel Brandmeyer <mb@kotka.de>
+" URL:          http://kotka.de/projects/clojure/vimclojure.html
 "
-" Maintainer:	Sung Pae <self@sungpae.com>
-" URL:		https://github.com/guns/vim-clojure-static
-" License:	Same as Vim
-" Last Change:	27 March 2014
-
-" TODO: Indenting after multibyte characters is broken:
-"       (let [Δ (if foo
-"                bar    ; Indent error
-"                baz)])
+" Maintainer:   Sung Pae <self@sungpae.com>
+" URL:          https://github.com/guns/vim-clojure-static
+" License:      Same as Vim
+" Last Change:  18 July 2016
 
 if exists("b:did_indent")
 	finish
@@ -57,36 +52,39 @@ if exists("*searchpairpos")
 		let g:clojure_align_subforms = 0
 	endif
 
-	function! s:SynIdName()
+	function! s:syn_id_name()
 		return synIDattr(synID(line("."), col("."), 0), "name")
 	endfunction
 
-	function! s:CurrentChar()
+	function! s:ignored_region()
+		return s:syn_id_name() =~? '\vstring|regex|comment|character'
+	endfunction
+
+	function! s:current_char()
 		return getline('.')[col('.')-1]
 	endfunction
 
-	function! s:CurrentWord()
+	function! s:current_word()
 		return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2]
 	endfunction
 
-	function! s:IsParen()
-		return s:CurrentChar() =~# '\v[\(\)\[\]\{\}]' &&
-		     \ s:SynIdName() !~? '\vstring|regex|comment|character'
+	function! s:is_paren()
+		return s:current_char() =~# '\v[\(\)\[\]\{\}]' && !s:ignored_region()
 	endfunction
 
 	" Returns 1 if string matches a pattern in 'patterns', which may be a
 	" list of patterns, or a comma-delimited string of implicitly anchored
 	" patterns.
-	function! s:MatchesOne(patterns, string)
+	function! s:match_one(patterns, string)
 		let list = type(a:patterns) == type([])
-			   \ ? a:patterns
-			   \ : map(split(a:patterns, ','), '"^" . v:val . "$"')
+		           \ ? a:patterns
+		           \ : map(split(a:patterns, ','), '"^" . v:val . "$"')
 		for pat in list
 			if a:string =~# pat | return 1 | endif
 		endfor
 	endfunction
 
-	function! s:MatchPairs(open, close, stopat)
+	function! s:match_pairs(open, close, stopat)
 		" Stop only on vector and map [ resp. {. Ignore the ones in strings and
 		" comments.
 		if a:stopat == 0
@@ -95,11 +93,11 @@ if exists("*searchpairpos")
 			let stopat = a:stopat
 		endif
 
-		let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:IsParen()", stopat)
-		return [pos[0], virtcol(pos)]
+		let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:is_paren()", stopat)
+		return [pos[0], col(pos)]
 	endfunction
 
-	function! s:ClojureCheckForStringWorker()
+	function! s:clojure_check_for_string_worker()
 		" Check whether there is the last character of the previous line is
 		" highlighted as a string. If so, we check whether it's a ". In this
 		" case we have to check also the previous character. The " might be the
@@ -113,17 +111,17 @@ if exists("*searchpairpos")
 
 		call cursor(nb, 0)
 		call cursor(0, col("$") - 1)
-		if s:SynIdName() !~? "string"
+		if s:syn_id_name() !~? "string"
 			return -1
 		endif
 
 		" This will not work for a " in the first column...
-		if s:CurrentChar() == '"'
+		if s:current_char() == '"'
 			call cursor(0, col("$") - 2)
-			if s:SynIdName() !~? "string"
+			if s:syn_id_name() !~? "string"
 				return -1
 			endif
-			if s:CurrentChar() != '\\'
+			if s:current_char() != '\\'
 				return -1
 			endif
 			call cursor(0, col("$") - 1)
@@ -138,40 +136,40 @@ if exists("*searchpairpos")
 		return indent(".")
 	endfunction
 
-	function! s:CheckForString()
+	function! s:check_for_string()
 		let pos = getpos('.')
 		try
-			let val = s:ClojureCheckForStringWorker()
+			let val = s:clojure_check_for_string_worker()
 		finally
 			call setpos('.', pos)
 		endtry
 		return val
 	endfunction
 
-	function! s:StripNamespaceAndMacroChars(word)
+	function! s:strip_namespace_and_macro_chars(word)
 		return substitute(a:word, "\\v%(.*/|[#'`~@^,]*)(.*)", '\1', '')
 	endfunction
 
-	function! s:ClojureIsMethodSpecialCaseWorker(position)
+	function! s:clojure_is_method_special_case_worker(position)
 		" Find the next enclosing form.
 		call search('\S', 'Wb')
 
 		" Special case: we are at a '(('.
-		if s:CurrentChar() == '('
+		if s:current_char() == '('
 			return 0
 		endif
 		call cursor(a:position)
 
-		let nextParen = s:MatchPairs('(', ')', 0)
+		let next_paren = s:match_pairs('(', ')', 0)
 
 		" Special case: we are now at toplevel.
-		if nextParen == [0, 0]
+		if next_paren == [0, 0]
 			return 0
 		endif
-		call cursor(nextParen)
+		call cursor(next_paren)
 
 		call search('\S', 'W')
-		let w = s:StripNamespaceAndMacroChars(s:CurrentWord())
+		let w = s:strip_namespace_and_macro_chars(s:current_word())
 		if g:clojure_special_indent_words =~# '\V\<' . w . '\>'
 			return 1
 		endif
@@ -179,27 +177,43 @@ if exists("*searchpairpos")
 		return 0
 	endfunction
 
-	function! s:IsMethodSpecialCase(position)
+	function! s:is_method_special_case(position)
 		let pos = getpos('.')
 		try
-			let val = s:ClojureIsMethodSpecialCaseWorker(a:position)
+			let val = s:clojure_is_method_special_case_worker(a:position)
 		finally
 			call setpos('.', pos)
 		endtry
 		return val
 	endfunction
 
-	function! GetClojureIndent()
+	" Check if form is a reader conditional, that is, it is prefixed by #?
+	" or @#?
+	function! s:is_reader_conditional_special_case(position)
+		if getline(a:position[0])[a:position[1] - 3 : a:position[1] - 2] == "#?"
+			return 1
+		endif
+
+		return 0
+	endfunction
+
+	" Returns 1 for opening brackets, -1 for _anything else_.
+	function! s:bracket_type(char)
+		return stridx('([{', a:char) > -1 ? 1 : -1
+	endfunction
+
+	" Returns: [opening-bracket-lnum, indent]
+	function! s:clojure_indent_pos()
 		" Get rid of special case.
 		if line(".") == 1
-			return 0
+			return [0, 0]
 		endif
 
 		" We have to apply some heuristics here to figure out, whether to use
 		" normal lisp indenting or not.
-		let i = s:CheckForString()
+		let i = s:check_for_string()
 		if i > -1
-			return i + !!g:clojure_align_multiline_strings
+			return [0, i + !!g:clojure_align_multiline_strings]
 		endif
 
 		call cursor(0, 1)
@@ -207,28 +221,28 @@ if exists("*searchpairpos")
 		" Find the next enclosing [ or {. We can limit the second search
 		" to the line, where the [ was found. If no [ was there this is
 		" zero and we search for an enclosing {.
-		let paren = s:MatchPairs('(', ')', 0)
-		let bracket = s:MatchPairs('\[', '\]', paren[0])
-		let curly = s:MatchPairs('{', '}', bracket[0])
+		let paren = s:match_pairs('(', ')', 0)
+		let bracket = s:match_pairs('\[', '\]', paren[0])
+		let curly = s:match_pairs('{', '}', bracket[0])
 
 		" In case the curly brace is on a line later then the [ or - in
 		" case they are on the same line - in a higher column, we take the
 		" curly indent.
 		if curly[0] > bracket[0] || curly[1] > bracket[1]
 			if curly[0] > paren[0] || curly[1] > paren[1]
-				return curly[1]
+				return curly
 			endif
 		endif
 
 		" If the curly was not chosen, we take the bracket indent - if
 		" there was one.
 		if bracket[0] > paren[0] || bracket[1] > paren[1]
-			return bracket[1]
+			return bracket
 		endif
 
 		" There are neither { nor [ nor (, ie. we are at the toplevel.
 		if paren == [0, 0]
-			return 0
+			return paren
 		endif
 
 		" Now we have to reimplement lispindent. This is surprisingly easy, as
@@ -246,58 +260,120 @@ if exists("*searchpairpos")
 		" - In any other case we use the column of the end of the word + 2.
 		call cursor(paren)
 
-		if s:IsMethodSpecialCase(paren)
-			return paren[1] + &shiftwidth - 1
+		if s:is_method_special_case(paren)
+			return [paren[0], paren[1] + &shiftwidth - 1]
+		endif
+
+		if s:is_reader_conditional_special_case(paren)
+			return paren
 		endif
 
 		" In case we are at the last character, we use the paren position.
 		if col("$") - 1 == paren[1]
-			return paren[1]
+			return paren
 		endif
 
 		" In case after the paren is a whitespace, we search for the next word.
 		call cursor(0, col('.') + 1)
-		if s:CurrentChar() == ' '
+		if s:current_char() == ' '
 			call search('\v\S', 'W')
 		endif
 
 		" If we moved to another line, there is no word after the (. We
 		" use the ( position for indent.
 		if line(".") > paren[0]
-			return paren[1]
+			return paren
 		endif
 
 		" We still have to check, whether the keyword starts with a (, [ or {.
 		" In that case we use the ( position for indent.
-		let w = s:CurrentWord()
-		if stridx('([{', w[0]) > -1
-			return paren[1]
+		let w = s:current_word()
+		if s:bracket_type(w[0]) == 1
+			return paren
 		endif
 
 		" Test words without namespace qualifiers and leading reader macro
 		" metacharacters.
 		"
 		" e.g. clojure.core/defn and #'defn should both indent like defn.
-		let ww = s:StripNamespaceAndMacroChars(w)
+		let ww = s:strip_namespace_and_macro_chars(w)
 
 		if &lispwords =~# '\V\<' . ww . '\>'
-			return paren[1] + &shiftwidth - 1
+			return [paren[0], paren[1] + &shiftwidth - 1]
 		endif
 
 		if g:clojure_fuzzy_indent
-			\ && !s:MatchesOne(g:clojure_fuzzy_indent_blacklist, ww)
-			\ && s:MatchesOne(g:clojure_fuzzy_indent_patterns, ww)
-			return paren[1] + &shiftwidth - 1
+			\ && !s:match_one(g:clojure_fuzzy_indent_blacklist, ww)
+			\ && s:match_one(g:clojure_fuzzy_indent_patterns, ww)
+			return [paren[0], paren[1] + &shiftwidth - 1]
 		endif
 
 		call search('\v\_s', 'cW')
 		call search('\v\S', 'W')
 		if paren[0] < line(".")
-			return paren[1] + (g:clojure_align_subforms ? 0 : &shiftwidth - 1)
+			return [paren[0], paren[1] + (g:clojure_align_subforms ? 0 : &shiftwidth - 1)]
 		endif
 
 		call search('\v\S', 'bW')
-		return virtcol(".") + 1
+		return [line('.'), col('.') + 1]
+	endfunction
+
+	function! GetClojureIndent()
+		let lnum = line('.')
+		let orig_lnum = lnum
+		let orig_col = col('.')
+		let [opening_lnum, indent] = s:clojure_indent_pos()
+
+		" Account for multibyte characters
+		if opening_lnum > 0
+			let indent -= indent - virtcol([opening_lnum, indent])
+		endif
+
+		" Return if there are no previous lines to inherit from
+		if opening_lnum < 1 || opening_lnum >= lnum - 1
+			call cursor(orig_lnum, orig_col)
+			return indent
+		endif
+
+		let bracket_count = 0
+
+		" Take the indent of the first previous non-white line that is
+		" at the same sexp level. cf. src/misc1.c:get_lisp_indent()
+		while 1
+			let lnum = prevnonblank(lnum - 1)
+			let col = 1
+
+			if lnum <= opening_lnum
+				break
+			endif
+
+			call cursor(lnum, col)
+
+			" Handle bracket counting edge case
+			if s:is_paren()
+				let bracket_count += s:bracket_type(s:current_char())
+			endif
+
+			while 1
+				if search('\v[(\[{}\])]', '', lnum) < 1
+					break
+				elseif !s:ignored_region()
+					let bracket_count += s:bracket_type(s:current_char())
+				endif
+			endwhile
+
+			if bracket_count == 0
+				" Check if this is part of a multiline string
+				call cursor(lnum, 1)
+				if s:syn_id_name() !~? '\vstring|regex'
+					call cursor(orig_lnum, orig_col)
+					return indent(lnum)
+				endif
+			endif
+		endwhile
+
+		call cursor(orig_lnum, orig_col)
+		return indent
 	endfunction
 
 	setlocal indentexpr=GetClojureIndent()