4
* author: Alex Turbov <i.zaufi@gmail.com>
7
* required-syntax-style: CMake
8
* indent-languages: CMake
10
* This file is part of the Kate Project.
12
* This library is free software; you can redistribute it and/or
13
* modify it under the terms of the GNU Library General Public
14
* License as published by the Free Software Foundation; either
15
* version 2 of the License, or (at your option) any later version.
17
* This library is distributed in the hope that it will be useful,
18
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20
* Library General Public License for more details.
22
* You should have received a copy of the GNU Library General Public License
23
* along with this library; see the file COPYING.LIB. If not, write to
24
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25
* Boston, MA 02110-1301, USA.
29
* Some settings it assumes being in effect:
33
* \todo Better to check (assert) some of that modelines...
36
// required katepart js libraries
37
require ("cmake_indenter_config.js")
39
require ("string.js");
41
require ("underscore.js")
43
triggerCharacters = "()$<{\"";
46
var debugMode = false;
49
/// Global var to store indentation width for current document type
51
/// Map of CMake calls where a key is the \e end of a corresponding \e start call
52
var END_BEGIN_CALL_PAIRS = {
53
"endfunction": "function"
55
, "endforeach" : "foreach"
56
, "endif" : {target: ["if", "else", "elseif"], push: ["endif"], pop: ["if"]}
57
, "elseif" : {target: ["if", "elseif"], push: ["endif"], pop: ["if"]}
58
, "else" : {target: ["if", "elseif"], push: ["endif"], pop: ["if"]}
59
, "endwhile" : "while"
61
/// List of CMake commands which introduce indentation decrease
62
var CONTROL_FLOW_CALLS_TO_UNINDENT_AFTER = [
65
/// List of CMake commands which introduce indentation increase
66
var CONTROL_FLOW_CALLS_TO_INDENT_AFTER = [
67
"function", "macro", "foreach", "if", "else", "elseif", "while"
69
/// List of CMake calls w/o (and/or all optional) parameters
70
var PARAMETERLESS_CALLS = [
71
"break", "else", "elseif", "enable_testing", "endforeach", "endif", "endmacro", "endwhile", "return"
73
/// List of CMake command options which have parameter(s)
74
var INDENT_AFTER_OPTIONS = [
75
"OUTPUT" // add_custom_command
76
, "COMMAND" // add_custom_command, add_custom_target, add_test, execute_process, if
77
, "ARGS" // add_custom_command, try_run
78
, "MAIN_DEPENDENCY" // add_custom_command
79
, "DEPENDS" // add_custom_command, add_custom_target
80
, "IMPLICIT_DEPENDS" // add_custom_command
81
, "WORKING_DIRECTORY" // add_custom_command, add_custom_target, add_test, execute_process
82
, "COMMENT" // add_custom_command, add_custom_target
83
, "TARGET" // add_custom_command, build_command, get_property, if, set_property
84
, "SOURCES" // add_custom_target, try_compile
85
, "ALIAS" // add_executable
86
, "OBJECT" // add_library, add_library
88
, "CONFIGURATIONS" // add_test, install
89
, "CONFIGURATION" // build_command
90
, "PROJECT_NAME" // build_command
91
, "RESULT" // cmake_host_system_information
92
, "QUERY" // cmake_host_system_information
93
, "VERSION" // cmake_minimum_required, cmake_policy
94
, "SET" // cmake_policy
95
, "GET" // cmake_policy, list
96
, "NEWLINE_STYLE" // configure_file
97
, "EXTRA_INCLUDE" // create_test_sourcelist
98
, "FUNCTION" // create_test_sourcelist
99
, "PROPERTY" // define_property, get_property, set_property
100
, "BRIEF_DOCS" // define_property
101
, "FULL_DOCS" // define_property
102
, "TIMEOUT" // execute_process, file
103
, "RESULT_VARIABLE" // execute_process, include
104
, "OUTPUT_VARIABLE" // execute_process, try_compile, try_run
105
, "ERROR_VARIABLE" // execute_process
106
, "INPUT_FILE" // execute_process
107
, "OUTPUT_FILE" // execute_process
108
, "ERROR_FILE" // execute_process
109
, "TARGETS" // export, install
110
, "NAMESPACE" // export, install
111
, "FILE" // export, install
112
, "PACKAGE" // export
114
, "APPEND" // file, list
119
, "LIMIT_COUNT" // file
120
, "LIMIT_INPUT" // file
121
, "LIMIT_OUTPUT" // file
122
, "LENGTH_MINIMUM" // file
123
, "LENGTH_MAXIMUM" // file
124
, "REGEX" // file, string
127
, "GLOB_RECURSE" // file
128
, "RENAME" // file, install
130
, "REMOVE_RECURSE" // file
131
, "MAKE_DIRECTORY" // file
132
, "RELATIVE_PATH" // file
133
, "TO_CMAKE_PATH" // file
134
, "TO_NATIVE_PATH" // file
136
, "INACTIVITY_TIMEOUT" // file
139
, "EXPECTED_HASH" // file
140
, "EXPECTED_MD5" // file
141
, "TLS_VERIFY" // file
142
, "TLS_CAINFO" // file
144
, "TIMESTAMP" // file, string
148
, "CONDITION" // file
151
, "DESTINATION" // file, install
152
, "FILE_PERMISSIONS" // file, install
153
, "DIRECTORY_PERMISSIONS" // file, install
154
, "FILES_MATCHING" // file, install
155
, "PATTERN" // file, install
156
, "PERMISSIONS" // file, install
157
, "EXCLUDE" // file, install, load_cache
158
, "NAMES" // find_file, find_library, find_package, find_path, find_program
159
, "HINTS" // find_file, find_library, find_package, find_path, find_program
160
, "ENV" // find_file, find_library, find_path, find_program
161
, "PATHS" // find_file, find_library, find_package, find_path, find_program
162
, "PATH_SUFFIXES" // find_file, find_library, find_package, find_path, find_program
163
, "DOC" // find_file, find_library, find_path, find_program
164
, "REQUIRED" // find_package
165
, "COMPONENTES" // find_package
166
, "OPTIONAL_COMPONENTES" // find_package
167
, "CONFIGS" // find_package
168
, "DIRECTORY" // get_directory_property, get_property, install, set_property
169
, "DEFINITION" // get_directory_property
170
, "PROGRAM" // get_filename_component
171
, "PROGRAM_ARGS" // get_filename_component
172
, "SOURCE" // get_property, set_property
173
, "TEST" // get_property, set_property
174
, "CACHE" // get_property, set, set_property
180
, "IS_NEWER_THAN" // if
181
, "IS_DIRECTORY" // if
183
, "IS_ABSOLUTE" // if
185
, "LESS" // if, string
186
, "GREATER" // if, string
187
, "EQUAL" // if, string
191
, "VERSION_LESS" // if
192
, "VERSION_GREATER" // if
193
, "VERSION_EQUAL" // if
195
, "TYPE" // include_external_msproject
196
, "GUID" // include_external_msproject
197
, "PLATFORM" // include_external_msproject
198
, "EXPORT" // install
199
, "ARCHIVE" // install
200
, "LIBRARY" // install
201
, "RUNTIME" // install
202
, "FRAMEWORK" // install
203
, "BUNDLE" // install
204
, "PRIVATE_HEADER" // install
205
, "PUBLIC_HEADER" // install
206
, "RESOURCE" // install
207
, "COMPONENT" // install
209
, "PROGRAMS" // install
210
, "SCRIPT" // install
212
, "LENGTH" // list, string
213
, "FIND" // list, string
215
, "REMOVE_ITEM" // list
216
, "REMOVE_AT" // list
217
, "REMOVE_DUPLICATES" // list
220
, "READ_WITH_PREFIX" // load_cache
221
, "INCLUDE_INTERNALS" // load_cache
223
, "UNIX_COMMAND" // separate_arguments
224
, "WINDOWS_COMMAND" // separate_arguments
225
// set_directory_properties, set_source_files_properties, set_target_properties, set_tests_properties
227
, "REGULAR_EXPRESSION" // source_group
228
, "FILES" // source_group
230
, "MATCHALL" // string
231
, "REPLACE" // string
232
, "COMPARE" // string
233
, "NOTEQUAL" // string
235
, "CONFIGURE" // string
236
, "TOUPPER" // string
237
, "TOLOWER" // string
238
, "SUBSTRING" // string
241
, "ALPHABET" // string
242
, "RANDOM_SEED" // string
243
, "MAKE_C_IDENTIFIER" // string
244
, "INTERFACE" // target_compile_definitions, target_compile_options, target_include_directories
245
, "PUBLIC" // target_compile_definitions, target_compile_options, target_include_directories
246
, "PRIVATE" // target_compile_definitions, target_compile_options, target_include_directories
247
, "LINK_PRIVATE" // target_link_libraries
248
, "LINK_PUBLIC" // target_link_libraries
249
, "CMAKE_FLAGS" // try_compile, try_run
250
, "COMPILE_DEFINITIONS" // try_compile, try_run
251
, "LINK_LIBRARIES" // try_compile
252
, "COPY_FILE" // try_compile
253
, "COPY_FILE_ERROR" // try_compile
254
, "COMPILE_OUTPUT_VARIABLE" // try_run
255
, "RUN_OUTPUT_VARIABLE" // try_run
260
* \brief Handle \c ENTER between parenthesis
262
function tryParenthesisSplit_ch(cursor)
266
if (isStringOrComment(cursor.line - 1, cursor.column))
267
return result; // Do nothing for comments and strings
269
// Is ENTER was pressed right after '('?
270
if (document.lastChar(cursor.line - 1) == '(')
272
// Yep, lets get align of the previous line
273
var prev_line_indent = document.firstColumn(cursor.line - 1);
274
result = prev_line_indent + gIndentWidth;
275
// Indent a closing ')' if ENTER was pressed between '(|)'
276
if (document.firstChar(cursor.line) == ')')
281
, "\n" + String().fill(' ', prev_line_indent + gIndentWidth / 2)
283
view.setCursorPosition(cursor.line, cursor.column);
289
dbg("tryParenthesisSplit_ch: result =", result);
295
* \brief Indent after some control flow CMake calls
297
function tryIndentAfterControlFlowCalls_ch(cursor)
301
if (isStringOrComment(cursor.line - 1, cursor.column))
302
return result; // Do nothing for comments and strings
304
var prev_line_indent = document.firstColumn(cursor.line - 1);
305
var first_prev_line_word = document.wordAt(cursor.line - 1, prev_line_indent).toLowerCase();
306
dbg("tryControlFlowCalls_ch: first_prev_line_word =", first_prev_line_word);
307
if (CONTROL_FLOW_CALLS_TO_INDENT_AFTER.indexOf(first_prev_line_word) != -1)
309
result = prev_line_indent + gIndentWidth;
311
else if (CONTROL_FLOW_CALLS_TO_UNINDENT_AFTER.indexOf(first_prev_line_word) != -1)
313
result = prev_line_indent - gIndentWidth;
318
dbg("tryControlFlowCalls_ch: result =", result);
324
* \brief Unindent after dandling ')'
326
function tryAfterClosingParensis_ch(cursor)
330
if (isStringOrComment(cursor.line - 1, cursor.column))
331
return result; // Do nothing for comments and strings
333
// Check if dandling ')' present on a previous line
334
var prev_line = document.prevNonEmptyLine(cursor.line - 1);
335
dbg("tryAfterClosingParensis_ch: prev_line =", prev_line);
336
var first_column = document.firstColumn(prev_line);
337
if (document.charAt(prev_line, first_column) == ')')
338
result = first_column - (gIndentWidth / 2);
342
dbg("tryAfterClosingParensis_ch: result =", result);
348
* \brief Indent after parameterless command options
350
* It is common way to format options w/ parameters like this:
354
* DESTINATION ${DATA_INSTALL_DIR}/kate/pate
359
* PATTERN "__pycache__*" EXCLUDE
363
* I.e. do indent for long parameter lists.
364
* This function recognize parameterless options (and do not indent after them),
365
* for everything else it adds an extra indentation...
367
function tryIndentCommandOptions_ch(cursor)
371
if (isStringOrComment(cursor.line - 1, cursor.column))
372
return result; // Do nothing for comments and strings
374
// Get last word from a previous line
375
var last_word = document.wordAt(cursor.line - 1, document.lastColumn(cursor.line - 1));
377
// ATTENTION Kate will return the last word "TARGET" for text like this: "TARGET)",
378
// which is not what we've wanted... so before continue lets make sure that we've
379
// got that we wanted...
380
if (last_word[last_word.length - 1] != document.lastChar(cursor.line - 1))
383
dbg("tryIndentCommandOptions_ch: last_word =", last_word);
384
result = document.firstColumn(cursor.line - 1)
385
+ (INDENT_AFTER_OPTIONS.indexOf(last_word) != -1 ? gIndentWidth : 0)
390
dbg("tryIndentCommandOptions_ch: result =", result);
396
* \brief Handle \c ENTER key
398
function caretPressed(cursor)
401
var line = cursor.line;
403
// Dunno what to do if previous line isn't available
405
return result; // Nothing (dunno) to do if no previous line...
407
// Register all indent functions
409
tryParenthesisSplit_ch // Handle ENTER between parenthesis
410
, tryIndentAfterControlFlowCalls_ch // Indent after some control flow CMake calls
411
, tryAfterClosingParensis_ch // Unindent after dandling ')'
412
, tryIndentCommandOptions_ch // Indent after parameterless command options
415
// Apply all all functions until result gets changed
418
; i < handlers.length && result == -1
419
; result = handlers[i++](cursor)
426
* \brief Try to unindent current call when <tt>(</tt> has pressed
428
function tryUnindentCall(cursor)
432
if (isStringOrComment(cursor.line, cursor.column))
433
return result; // Do nothing for comments and strings
435
// Check the word before current cursor position
436
var call_name = document.wordAt(cursor.line, cursor.column - 1).toLowerCase();
437
dbg("tryUnindentCall: call_name =", call_name);
438
var end_calls = _.keys(END_BEGIN_CALL_PAIRS);
440
if (_.indexOf(end_calls, call_name) != -1)
442
var lookup_call_name = END_BEGIN_CALL_PAIRS[call_name];
443
dbg("tryUnindentCall: typeof lookup_call_name =", typeof lookup_call_name);
445
if (typeof lookup_call_name === "string")
447
matcher = function(word)
449
return word == lookup_call_name;
452
else if (typeof lookup_call_name === "object")
454
matcher = function(word)
456
// dbg("tryUnindentCall: stack_level =", stack_level, ", word =", word);
457
if (_.indexOf(lookup_call_name.push, word) != -1)
462
if (stack_level != 0)
464
if (_.indexOf(lookup_call_name.pop, word) != -1)
468
return _.indexOf(lookup_call_name.target, word) != -1;
473
/// \todo Is there any kind of \c assert() in JavaScript at all?
474
dbg("tryUnindentCall: Unknown type of END_BEGIN_CALL_PAIRS value! Code review required!");
478
// Ok, lets find the corresponding start call towards document's start...
481
var line = document.prevNonEmptyLine(cursor.line - 1)
482
; 0 <= line && found_line == -1
483
; line = document.prevNonEmptyLine(line - 1)
486
var first_word = document.wordAt(line, document.firstColumn(line)).toLowerCase();
487
// dbg("tryUnindentCall: line =", line, ", word =", first_word);
488
if (matcher(first_word))
490
dbg("tryUnindentCall: Found ", first_word);
495
if (found_line != -1)
497
result = document.firstColumn(found_line);
498
// Add closing parenthesis if none yet
499
// addCharOrJumpOverIt(cursor.line, cursor.column, ')');
503
dbg("tryUnindentCall: Not Found!");
509
dbg("tryUnindentCall: result =", result);
515
* Align closing parenthesis if it is a first character on a line
517
function tryAlignCloseParenthesis(cursor)
521
if (isStringOrComment(cursor.line, cursor.column))
522
return result; // Do nothing for comments and strings
524
if (justEnteredCharIsFirstOnLine(cursor.line, cursor.column, ')'))
526
// Try to find corresponding open parenthesis
527
var open = document.anchor(cursor.line, cursor.column - 1, '(');
528
dbg("tryAlignCloseParenthesis: Found open brace at", open);
530
result = document.firstColumn(open.line) + (gIndentWidth / 2);
535
dbg("tryAlignCloseParenthesis: result =", result);
541
* \brief Move cursor out of <tt>'()'</tt> if no parameters required for a given call
543
function tryJumpOutOfParenthesis(cursor)
545
if (isStringOrComment(cursor.line, cursor.column))
546
return; // Do nothing for comments and strings
548
var first_word = document.wordAt(cursor.line, document.firstColumn(cursor.line)).toLowerCase();
549
dbg("tryJumpOutOfParenthesis: @"+cursor.line+","+cursor.column+", char="+document.charAt(cursor));
550
if (PARAMETERLESS_CALLS.indexOf(first_word) != -1)
551
addCharOrJumpOverIt(cursor.line, cursor.column, ')')
555
* \brief Append <tt>'{}'</tt> after \c '$'
557
function insertVariableExpansion(cursor)
559
if (!cmi_cfg_vgShortcut || isComment(cursor.line, cursor.column))
560
return; // Do nothing for comments
562
var next_ch = document.charAt(cursor);
563
dbg("insertVariableExpansion: next_ch ='"+next_ch+"'");
564
if (!next_ch.match(/[A-Za-z_]/))
567
document.insertText(cursor, "{}");
568
view.setCursorPosition(cursor.line, cursor.column + 1);
573
* \brief Handle <tt>'$'</tt> character
575
* Here is possble few transformations: if \c '<' pressed after \c '$',
576
* transform <tt>'${<}'</tt> to <tt>'$<>'</tt> to be ready for
577
* generator expression and vise versa.
579
function tryVariableOrGeneratorExpression(cursor, ch)
581
if (!cmi_cfg_vgShortcut || isComment(cursor.line, cursor.column))
582
return; // Do nothing for comments and strings
584
var tail = document.text(
586
, document.firstColumn(cursor.line)
590
var next_ch = document.charAt(cursor);
591
dbg("tryVariableOrGeneratorExpression: tail ='"+tail+"'");
592
var fix_text = function(text)
594
document.removeText(cursor.line, cursor.column - 2, cursor.line, cursor.column + 1);
595
document.insertText(cursor.line, cursor.column - 2, text);
596
view.setCursorPosition(cursor.line, cursor.column - 1);
598
if (tail.endsWith("${<") && next_ch == '}')
600
fix_text("<>"); // Transform '${}' -> '$<>'
602
else if (tail.endsWith("$<{") && next_ch == '>')
604
fix_text("{}"); // Transform '$<>' -> '${}'
606
else if (tail.endsWith("${{") && next_ch == '}')
608
// Transform '${{|}' --> '${|}'
609
document.removeText(cursor.line, cursor.column - 1, cursor.line, cursor.column);
611
else if (tail.endsWith("$<<") && next_ch == '>')
613
// Transform '$<<|>' --> '$<|>'
614
document.removeText(cursor.line, cursor.column - 1, cursor.line, cursor.column);
619
* \brief Open/close string literal
621
* \attention Autobracket extension w/ add quote char doesn't work
622
* for CMake files (file a BUG?)...
624
function tryString(cursor)
626
if (isComment(cursor.line, cursor.column))
627
return; // Do nothing for comments
629
// NOTE If cursor has a string attribute, then it was an open quote
630
// character just entered...
631
// ATTENTION If cursor positioned at the end of a line,
632
// isString() returns 'false' for some unknown reason...
633
// so check the attribute for the just entered quote symbol...
634
if (isString(cursor.line, cursor.column - 1))
636
// Check if next char is not a quote already
637
// and/or maybe some punctualtion... Particularly
638
// ')' remains after some function call -- i.e. smth like
639
// message(STATUS "|)
640
// or just entered quot char is a first on a line and
641
// no other chars are here...
642
var ch = document.charAt(cursor);
643
if ((ch != '"' && (ch == ')' || ch == ' '))
644
|| (justEnteredCharIsFirstOnLine(cursor.line, cursor.column, '"')
645
&& document.lineLength(cursor.line) == cursor.column
649
document.insertText(cursor, '"')
650
view.setCursorPosition(cursor);
652
} // Do nothing for closing quote char
656
* \brief Process one character
658
* NOTE Cursor positioned right after just entered character and has \c +1 in column.
660
function processChar(line, ch)
662
var result = -2; // By default, do nothing...
663
var cursor = view.cursorPosition();
667
document.editBegin();
668
// Check if char under cursor is the same as just entered,
669
// and if so, remove it... to make it behave like "overwrite" mode
670
if (ch != ' ' && document.charAt(cursor) == ch)
671
document.removeText(line, cursor.column, line, cursor.column + 1);
676
result = caretPressed(cursor);
679
result = tryUnindentCall(cursor);
680
tryJumpOutOfParenthesis(cursor);
683
result = tryAlignCloseParenthesis(cursor);
686
insertVariableExpansion(cursor);
690
tryVariableOrGeneratorExpression(cursor, ch);
696
break; // Nothing to do...
704
* Try to align a given line
707
function indentLine(line)
709
dbg(">> Going to indent line ", line);
710
var cursor = new Cursor(line, document.firstColumn(line) + 1);
711
var result = tryUnindentCall(cursor);
713
result = tryAlignCloseParenthesis(cursor);
714
cursor = new Cursor(line, document.firstColumn(line));
716
result = caretPressed(cursor);
717
dbg("indentLine: result =", result);
719
if (result == -2) // Still dunno what to do?
720
result = -1; // ... just align according a previous non empty line
725
* \brief Process a newline or one of \c triggerCharacters character.
727
* This function is called whenever the user hits \c ENTER key.
729
* It gets three arguments: \c line, \c indentwidth in spaces and typed character
731
* Called for each newline (<tt>ch == \n</tt>) and all characters specified in
732
* the global variable \c triggerCharacters. When calling \e Tools->Align
733
* the variable \c ch is empty, i.e. <tt>ch == ''</tt>.
735
function indent(line, indentWidth, ch)
737
// NOTE Update some global variables
738
gIndentWidth = indentWidth;
740
dbg("indentWidth: " + indentWidth);
741
dbg(" gMode: " + document.highlightingModeAt(view.cursorPosition()));
742
dbg(" gAttr: " + document.attributeName(view.cursorPosition()));
743
dbg(" line: " + line);
744
dbg(" ch: '" + ch + "'");
747
return processChar(line, ch);
749
return indentLine(line);
752
// kate: space-indent on; indent-width 4; replace-tabs on;
755
* \todo Move string/comment checks to \c caretPressed() instead of particular function.