Bram Moolenaar | 293ee4d | 2004-12-09 21:34:53 +0000 | [diff] [blame^] | 1 | " Vim indent file generic utility functions |
| 2 | " Language: * (various) |
| 3 | " Maintainer: Dave Silvia <dsilvia@mchsi.com> |
| 4 | " Date: 6/30/2004 |
| 5 | |
| 6 | " SUMMARY: To use GenericIndent, indent/<your_filename>.vim would have the |
| 7 | " following general format: |
| 8 | " |
| 9 | " if exists("b:did_indent") | finish | endif |
| 10 | " let b:did_indent = 1 |
| 11 | " runtime indent/GenericIndent.vim |
| 12 | " let b:indentStmts='' |
| 13 | " let b:dedentStmts='' |
| 14 | " let b:allStmts='' |
| 15 | " setlocal indentexpr=GenericIndent() |
| 16 | " setlocal indentkeys=<your_keys> |
| 17 | " call GenericIndentStmts(<your_stmts>) |
| 18 | " call GenericDedentStmts(<your_stmts>) |
| 19 | " call GenericAllStmts() |
| 20 | " |
| 21 | " END SUMMARY: |
| 22 | |
| 23 | " NOTE: b:indentStmts, b:dedentStmts, and b:allStmts need to be initialized |
| 24 | " to '' before callin the functions because 'indent.vim' explicitly |
| 25 | " 'unlet's b:did_indent. This means that the lists will compound if |
| 26 | " you change back and forth between buffers. This is true as of |
| 27 | " version 6.3, 6/23/2004. |
| 28 | " |
| 29 | " NOTE: By default, GenericIndent is case sensitive. |
| 30 | " let b:case_insensitive=1 if you want to ignore case, e.g. DOS batch files |
| 31 | |
| 32 | " The function 'GenericIndent' is data driven and handles most all cases of |
| 33 | " indent checking if you first set up the data. To use this function follow |
| 34 | " the example below (taken from the file indent/MuPAD_source.vim) |
| 35 | " |
| 36 | " Before you start, source this file in indent/<your_script>.vim to have it |
| 37 | " define functions for your use. |
| 38 | " |
| 39 | "runtime indent/GenericIndent.vim |
| 40 | " |
| 41 | " The data is in 5 sets: |
| 42 | " |
| 43 | " First, set the data set 'indentexpr' to GenericIndent(). |
| 44 | " |
| 45 | "setlocal indentexpr=GenericIndent() |
| 46 | " |
| 47 | " Second, set the data set 'indentkeys' to the keywords/expressions that need |
| 48 | " to be checked for 'indenting' _as_ they typed. |
| 49 | " |
| 50 | "setlocal indentkeys==end_proc,=else,=then,=elif,=end_if,=end_case,=until,=end_repeat,=end_domain,=end_for,=end_while,=end,o,O |
| 51 | " |
| 52 | " NOTE: 'o,O' at the end of the previous line says you wish to be called |
| 53 | " whenever a newline is placed in the buffer. This allows the previous line |
| 54 | " to be checked for indentation parameters. |
| 55 | " |
| 56 | " Third, set the data set 'b:indentStmts' to the keywords/expressions that, when |
| 57 | " they are on a line _when_ you _press_ the _<Enter>_ key, |
| 58 | " you wish to have the next line indented. |
| 59 | " |
| 60 | "call GenericIndentStmts('begin,if,then,else,elif,case,repeat,until,domain,do') |
| 61 | " |
| 62 | " Fourth, set the data set 'b:dedentStmts' to the keywords/expressions that, when |
| 63 | " they are on a line you are currently typing, you wish to have that line |
| 64 | " 'dedented' (having already been indented because of the previous line's |
| 65 | " indentation). |
| 66 | " |
| 67 | "call GenericDedentStmts('end_proc,then,else,elif,end_if,end_case,until,end_repeat,end_domain,end_for,end_while,end') |
| 68 | " |
| 69 | " Fifth, set the data set 'b:allStmts' to the concatenation of the third and |
| 70 | " fourth data sets, used for checking when more than one keyword/expression |
| 71 | " is on a line. |
| 72 | " |
| 73 | "call GenericAllStmts() |
| 74 | " |
| 75 | " NOTE: GenericIndentStmts uses two variables: 'b:indentStmtOpen' and |
| 76 | " 'b:indentStmtClose' which default to '\<' and '\>' respectively. You can |
| 77 | " set (let) these to any value you wish before calling GenericIndentStmts with |
| 78 | " your list. Similarly, GenericDedentStmts uses 'b:dedentStmtOpen' and |
| 79 | " 'b:dedentStmtClose'. |
| 80 | " |
| 81 | " NOTE: Patterns may be used in the lists passed to Generic[In|De]dentStmts |
| 82 | " since each element in the list is copied verbatim. |
| 83 | " |
| 84 | " Optionally, you can set the DEBUGGING flag within your script to have the |
| 85 | " debugging messages output. See below for description. This can also be set |
| 86 | " (let) from the command line within your editing buffer. |
| 87 | " |
| 88 | "let b:DEBUGGING=1 |
| 89 | " |
| 90 | " See: |
| 91 | " :h runtime |
| 92 | " :set runtimepath ? |
| 93 | " to familiarize yourself with how this works and where you should have this |
| 94 | " file and your file(s) installed. |
| 95 | " |
| 96 | " For help with setting 'indentkeys' see: |
| 97 | " :h indentkeys |
| 98 | " Also, for some good examples see 'indent/sh.vim' and 'indent/vim.vim' as |
| 99 | " well as files for other languages you may be familiar with. |
| 100 | " |
| 101 | " |
| 102 | " Alternatively, if you'd rather specify yourself, you can enter |
| 103 | " 'b:indentStmts', 'b:dedentStmts', and 'b:allStmts' 'literally': |
| 104 | " |
| 105 | "let b:indentStmts='\<begin\>\|\<if\>\|\<then\>\|\<else\>\|\<elif\>\|\<case\>\|\<repeat\>\|\<until\>\|\<domain\>\|\<do\>' |
| 106 | "let b:dedentStmts='\<end_proc\>\|\<else\>\|\<elif\>\|\<end_if\>\|\<end_case\>\|\<until\>\|\<end_repeat\>\|\<end_domain\>\|\<end_for\>\|\<end_while\>\|\<end\>' |
| 107 | "let b:allStmts=b:indentStmts.'\|'.b:dedentStmts |
| 108 | " |
| 109 | " This is only useful if you have particularly different parameters for |
| 110 | " matching each statement. |
| 111 | |
| 112 | " RECAP: From indent/MuPAD_source.vim |
| 113 | " |
| 114 | "if exists("b:did_indent") | finish | endif |
| 115 | " |
| 116 | "let b:did_indent = 1 |
| 117 | " |
| 118 | "runtime indent/GenericIndent.vim |
| 119 | " |
| 120 | "setlocal indentexpr=GenericIndent() |
| 121 | "setlocal indentkeys==end_proc,=then,=else,=elif,=end_if,=end_case,=until,=end_repeat,=end_domain,=end_for,=end_while,=end,o,O |
| 122 | "call GenericIndentStmts('begin,if,then,else,elif,case,repeat,until,domain,do') |
| 123 | "call GenericDedentStmts('end_proc,then,else,elif,end_if,end_case,until,end_repeat,end_domain,end_for,end_while,end') |
| 124 | "call GenericAllStmts() |
| 125 | " |
| 126 | " END RECAP: |
| 127 | |
| 128 | let s:hit=0 |
| 129 | let s:lastVlnum=0 |
| 130 | let s:myScriptName=expand("<sfile>:t") |
| 131 | |
| 132 | if exists("*GenericIndent") |
| 133 | finish |
| 134 | endif |
| 135 | |
| 136 | function GenericAllStmts() |
| 137 | let b:allStmts=b:indentStmts.'\|'.b:dedentStmts |
| 138 | call DebugGenericIndent(expand("<sfile>").": "."b:indentStmts: ".b:indentStmts.", b:dedentStmts: ".b:dedentStmts.", b:allStmts: ".b:allStmts) |
| 139 | endfunction |
| 140 | |
| 141 | function GenericIndentStmts(stmts) |
| 142 | let Stmts=a:stmts |
| 143 | let Comma=match(Stmts,',') |
| 144 | if Comma == -1 || Comma == strlen(Stmts)-1 |
| 145 | echoerr "Must supply a comma separated list of at least 2 entries." |
| 146 | echoerr "Supplied list: <".Stmts.">" |
| 147 | return |
| 148 | endif |
| 149 | |
| 150 | if !exists("b:indentStmtOpen") |
| 151 | let b:indentStmtOpen='\<' |
| 152 | endif |
| 153 | if !exists("b:indentStmtClose") |
| 154 | let b:indentStmtClose='\>' |
| 155 | endif |
| 156 | if !exists("b:indentStmts") |
| 157 | let b:indentStmts='' |
| 158 | endif |
| 159 | if b:indentStmts != '' |
| 160 | let b:indentStmts=b:indentStmts.'\|' |
| 161 | endif |
| 162 | call DebugGenericIndent(expand("<sfile>").": "."b:indentStmtOpen: ".b:indentStmtOpen.", b:indentStmtClose: ".b:indentStmtClose.", b:indentStmts: ".b:indentStmts.", Stmts: ".Stmts) |
| 163 | let stmtEntryBegin=0 |
| 164 | let stmtEntryEnd=Comma |
| 165 | let stmtEntry=strpart(Stmts,stmtEntryBegin,stmtEntryEnd-stmtEntryBegin) |
| 166 | let Stmts=strpart(Stmts,Comma+1) |
| 167 | let Comma=match(Stmts,',') |
| 168 | let b:indentStmts=b:indentStmts.b:indentStmtOpen.stmtEntry.b:indentStmtClose |
| 169 | while Comma != -1 |
| 170 | let stmtEntryEnd=Comma |
| 171 | let stmtEntry=strpart(Stmts,stmtEntryBegin,stmtEntryEnd-stmtEntryBegin) |
| 172 | let Stmts=strpart(Stmts,Comma+1) |
| 173 | let Comma=match(Stmts,',') |
| 174 | let b:indentStmts=b:indentStmts.'\|'.b:indentStmtOpen.stmtEntry.b:indentStmtClose |
| 175 | endwhile |
| 176 | let stmtEntry=Stmts |
| 177 | let b:indentStmts=b:indentStmts.'\|'.b:indentStmtOpen.stmtEntry.b:indentStmtClose |
| 178 | endfunction |
| 179 | |
| 180 | function GenericDedentStmts(stmts) |
| 181 | let Stmts=a:stmts |
| 182 | let Comma=match(Stmts,',') |
| 183 | if Comma == -1 || Comma == strlen(Stmts)-1 |
| 184 | echoerr "Must supply a comma separated list of at least 2 entries." |
| 185 | echoerr "Supplied list: <".Stmts.">" |
| 186 | return |
| 187 | endif |
| 188 | |
| 189 | if !exists("b:dedentStmtOpen") |
| 190 | let b:dedentStmtOpen='\<' |
| 191 | endif |
| 192 | if !exists("b:dedentStmtClose") |
| 193 | let b:dedentStmtClose='\>' |
| 194 | endif |
| 195 | if !exists("b:dedentStmts") |
| 196 | let b:dedentStmts='' |
| 197 | endif |
| 198 | if b:dedentStmts != '' |
| 199 | let b:dedentStmts=b:dedentStmts.'\|' |
| 200 | endif |
| 201 | call DebugGenericIndent(expand("<sfile>").": "."b:dedentStmtOpen: ".b:dedentStmtOpen.", b:dedentStmtClose: ".b:dedentStmtClose.", b:dedentStmts: ".b:dedentStmts.", Stmts: ".Stmts) |
| 202 | let stmtEntryBegin=0 |
| 203 | let stmtEntryEnd=Comma |
| 204 | let stmtEntry=strpart(Stmts,stmtEntryBegin,stmtEntryEnd-stmtEntryBegin) |
| 205 | let Stmts=strpart(Stmts,Comma+1) |
| 206 | let Comma=match(Stmts,',') |
| 207 | let b:dedentStmts=b:dedentStmts.b:dedentStmtOpen.stmtEntry.b:dedentStmtClose |
| 208 | while Comma != -1 |
| 209 | let stmtEntryEnd=Comma |
| 210 | let stmtEntry=strpart(Stmts,stmtEntryBegin,stmtEntryEnd-stmtEntryBegin) |
| 211 | let Stmts=strpart(Stmts,Comma+1) |
| 212 | let Comma=match(Stmts,',') |
| 213 | let b:dedentStmts=b:dedentStmts.'\|'.b:dedentStmtOpen.stmtEntry.b:dedentStmtClose |
| 214 | endwhile |
| 215 | let stmtEntry=Stmts |
| 216 | let b:dedentStmts=b:dedentStmts.'\|'.b:dedentStmtOpen.stmtEntry.b:dedentStmtClose |
| 217 | endfunction |
| 218 | |
| 219 | " Debugging function. Displays messages in the command area which can be |
| 220 | " reviewed using ':messages'. To turn it on use ':let b:DEBUGGING=1'. Once |
| 221 | " on, turn off by using ':let b:DEBUGGING=0. If you don't want it at all and |
| 222 | " feel it's slowing down your editing (you must have an _awfully_ slow |
| 223 | " machine!;-> ), you can just comment out the calls to it from 'GenericIndent' |
| 224 | " below. No need to remove the function or the calls, tho', as you never can |
| 225 | " tell when they might come in handy!;-) |
| 226 | function DebugGenericIndent(msg) |
| 227 | if exists("b:DEBUGGING") && b:DEBUGGING |
| 228 | echomsg '['.s:hit.']'.s:myScriptName."::".a:msg |
| 229 | endif |
| 230 | endfunction |
| 231 | |
| 232 | function GenericIndent() |
| 233 | " save ignore case option. Have to set noignorecase for the match |
| 234 | " functions to do their job the way we want them to! |
| 235 | " NOTE: if you add a return to this function be sure you do |
| 236 | " if IgnoreCase | set ignorecase | endif |
| 237 | " before returning. You can just cut and paste from here. |
| 238 | let IgnoreCase=&ignorecase |
| 239 | " let b:case_insensitive=1 if you want to ignore case, e.g. DOS batch files |
| 240 | if !exists("b:case_insensitive") |
| 241 | set noignorecase |
| 242 | endif |
| 243 | " this is used to let DebugGenericIndent display which invocation of the |
| 244 | " function goes with which messages. |
| 245 | let s:hit=s:hit+1 |
| 246 | let lnum=v:lnum |
| 247 | let cline=getline(lnum) |
| 248 | let lnum=prevnonblank(lnum) |
| 249 | if lnum==0 | if IgnoreCase | set ignorecase | endif | return 0 | endif |
| 250 | let pline=getline(lnum) |
| 251 | let ndnt=indent(lnum) |
| 252 | if !exists("b:allStmts") |
| 253 | call GenericAllStmts() |
| 254 | endif |
| 255 | |
| 256 | call DebugGenericIndent(expand("<sfile>").": "."cline=<".cline.">, pline=<".pline.">, lnum=".lnum.", v:lnum=".v:lnum.", ndnt=".ndnt) |
| 257 | if lnum==v:lnum |
| 258 | " current line, only check dedent |
| 259 | " |
| 260 | " just dedented this line, don't need to do it again. |
| 261 | " another dedentStmts was added or an end%[_*] was completed. |
| 262 | if s:lastVlnum==v:lnum |
| 263 | if IgnoreCase | set ignorecase | endif |
| 264 | return ndnt |
| 265 | endif |
| 266 | let s:lastVlnum=v:lnum |
| 267 | call DebugGenericIndent(expand("<sfile>").": "."Checking dedent") |
| 268 | let srcStr=cline |
| 269 | let dedentKeyBegin=match(srcStr,b:dedentStmts) |
| 270 | if dedentKeyBegin != -1 |
| 271 | let dedentKeyEnd=matchend(srcStr,b:dedentStmts) |
| 272 | let dedentKeyStr=strpart(srcStr,dedentKeyBegin,dedentKeyEnd-dedentKeyBegin) |
| 273 | "only dedent if it's the beginning of the line |
| 274 | if match(srcStr,'^\s*\<'.dedentKeyStr.'\>') != -1 |
| 275 | call DebugGenericIndent(expand("<sfile>").": "."It's the beginning of the line, dedent") |
| 276 | let ndnt=ndnt-&shiftwidth |
| 277 | endif |
| 278 | endif |
| 279 | call DebugGenericIndent(expand("<sfile>").": "."dedent - returning ndnt=".ndnt) |
| 280 | else |
| 281 | " previous line, only check indent |
| 282 | call DebugGenericIndent(expand("<sfile>").": "."Checking indent") |
| 283 | let srcStr=pline |
| 284 | let indentKeyBegin=match(srcStr,b:indentStmts) |
| 285 | if indentKeyBegin != -1 |
| 286 | " only indent if it's the last indentStmts in the line |
| 287 | let allKeyBegin=match(srcStr,b:allStmts) |
| 288 | let allKeyEnd=matchend(srcStr,b:allStmts) |
| 289 | let allKeyStr=strpart(srcStr,allKeyBegin,allKeyEnd-allKeyBegin) |
| 290 | let srcStr=strpart(srcStr,allKeyEnd) |
| 291 | let allKeyBegin=match(srcStr,b:allStmts) |
| 292 | if allKeyBegin != -1 |
| 293 | " not the end of the line, check what is and only indent if |
| 294 | " it's an indentStmts |
| 295 | call DebugGenericIndent(expand("<sfile>").": "."Multiple words in line, checking if last is indent") |
| 296 | while allKeyBegin != -1 |
| 297 | let allKeyEnd=matchend(srcStr,b:allStmts) |
| 298 | let allKeyStr=strpart(srcStr,allKeyBegin,allKeyEnd-allKeyBegin) |
| 299 | let srcStr=strpart(srcStr,allKeyEnd) |
| 300 | let allKeyBegin=match(srcStr,b:allStmts) |
| 301 | endwhile |
| 302 | if match(b:indentStmts,allKeyStr) != -1 |
| 303 | call DebugGenericIndent(expand("<sfile>").": "."Last word in line is indent") |
| 304 | let ndnt=ndnt+&shiftwidth |
| 305 | endif |
| 306 | else |
| 307 | " it's the last indentStmts in the line, go ahead and indent |
| 308 | let ndnt=ndnt+&shiftwidth |
| 309 | endif |
| 310 | endif |
| 311 | call DebugGenericIndent(expand("<sfile>").": "."indent - returning ndnt=".ndnt) |
| 312 | endif |
| 313 | if IgnoreCase | set ignorecase | endif |
| 314 | return ndnt |
| 315 | endfunction |
| 316 | |
| 317 | |
| 318 | " TODO: I'm open! |
| 319 | " |
| 320 | " BUGS: You tell me! Probably. I just haven't found one yet or haven't been |
| 321 | " told about one. |
| 322 | " |