blob: dc0769b9a8124dc4ab7b73cf99a3a6c359789ae7 [file] [log] [blame]
Jon Parise4ce1cb52024-12-23 09:57:42 +01001" Vim indent file
2" Language: graphql
3" Maintainer: Jon Parise <jon@indelible.org>
4" Filenames: *.graphql *.graphqls *.gql
5" URL: https://github.com/jparise/vim-graphql
6" License: MIT <https://opensource.org/license/mit>
7" Last Change: 2024 Dec 21
8
9" Set our local options if indentation hasn't already been set up.
10" This generally means we've been detected as the primary filetype.
11if !exists('b:did_indent')
12 setlocal autoindent
13 setlocal nocindent
14 setlocal nolisp
15 setlocal nosmartindent
16
17 setlocal indentexpr=GetGraphQLIndent()
18 setlocal indentkeys=0{,0},0),0[,0],0#,!^F,o,O
19
20 let b:did_indent = 1
21endif
22
23" If our indentation function already exists, we have nothing more to do.
24if exists('*GetGraphQLIndent')
25 finish
26endif
27
28let s:cpo_save = &cpoptions
29set cpoptions&vim
30
31" searchpair() skip expression that matches in comments and strings.
32let s:pair_skip_expr =
33 \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "comment\\|string"'
34
35" Check if the character at lnum:col is inside a string.
36function s:InString(lnum, col)
37 return synIDattr(synID(a:lnum, a:col, 1), 'name') ==# 'graphqlString'
38endfunction
39
40function GetGraphQLIndent()
41 " If this is the first non-blank line, we have nothing more to do because
42 " all of our indentation rules are based on matching against earlier lines.
43 let l:prevlnum = prevnonblank(v:lnum - 1)
44 if l:prevlnum == 0
45 return 0
46 endif
47
48 " If the previous line isn't GraphQL, assume we're part of a template
49 " string and indent this new line within it.
50 let l:stack = map(synstack(l:prevlnum, 1), "synIDattr(v:val, 'name')")
51 if get(l:stack, -1) !~# '^graphql'
52 return indent(l:prevlnum) + shiftwidth()
53 endif
54
55 let l:line = getline(v:lnum)
56
57 " If this line contains just a closing bracket, find its matching opening
58 " bracket and indent the closing bracket to match.
59 let l:col = matchend(l:line, '^\s*[]})]')
60 if l:col > 0 && !s:InString(v:lnum, l:col)
61 call cursor(v:lnum, l:col)
62
63 let l:bracket = l:line[l:col - 1]
64 if l:bracket ==# '}'
65 let l:matched = searchpair('{', '', '}', 'bW', s:pair_skip_expr)
66 elseif l:bracket ==# ']'
67 let l:matched = searchpair('\[', '', '\]', 'bW', s:pair_skip_expr)
68 elseif l:bracket ==# ')'
69 let l:matched = searchpair('(', '', ')', 'bW', s:pair_skip_expr)
70 else
71 let l:matched = -1
72 endif
73
74 return l:matched > 0 ? indent(l:matched) : virtcol('.') - 1
75 endif
76
77 " If we're inside of a multiline string, continue with the same indentation.
78 if s:InString(v:lnum, matchend(l:line, '^\s*') + 1)
79 return indent(v:lnum)
80 endif
81
82 " If the previous line ended with an opening bracket, indent this line.
83 if getline(l:prevlnum) =~# '\%(#.*\)\@<![[{(]\s*$'
84 return indent(l:prevlnum) + shiftwidth()
85 endif
86
87 " Default to the existing indentation level.
88 return indent(l:prevlnum)
89endfunction
90
91let &cpoptions = s:cpo_save
92unlet s:cpo_save