From 20a833325f8f4d7d80c903912bce048d1fbfd5a8 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Thu, 29 Nov 2012 09:01:20 -0500 Subject: [PATCH 01/63] [vim keymap] Fix IE incompatibilities --- keymap/vim.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 596753bff0..7fa604b7f6 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -881,7 +881,7 @@ var toSwap = cm.getRange(curStart, curEnd); var swapped = ''; for (var i = 0; i < toSwap.length; i++) { - var character = toSwap[i]; + var character = toSwap.charAt(i); swapped += isUpperCase(character) ? character.toLowerCase() : character.toUpperCase(); } @@ -1340,10 +1340,10 @@ function findMatchedSymbol(cm, cur, symb) { var line = cur.line; - symb = symb ? symb : cm.getLine(line)[cur.ch]; + symb = symb ? symb : cm.getLine(line).charAt(cur.ch); // Are we at the opening or closing char - var forwards = (['(', '[', '{'].indexOf(symb) != -1); + var forwards = inArray(symb, ['(', '[', '{']); var reverseSymb = (function(sym) { switch (sym) { From 436abcc1585146c8b8dced7c1e241149425d960d Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Thu, 29 Nov 2012 09:43:18 -0500 Subject: [PATCH 02/63] [vim keymap] Enable C --- keymap/vim.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/keymap/vim.js b/keymap/vim.js index 7fa604b7f6..e09fe6c0c2 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -19,7 +19,7 @@ * >, <, >>, << * * Operator-Motion: - * x, X, D, Y, ~ + * x, X, D, Y, C, ~ * * Action: * a, i, s, A, I, S, o, O @@ -164,6 +164,9 @@ motion: 'moveToEol' , operatorMotionArgs: { visualLine: true }}, { keys: ['Y'], type: 'operatorMotion', operator: 'yank', motion: 'moveToEol' , operatorMotionArgs: { visualLine: true }}, + { keys: ['C'], type: 'operatorMotion', operator: 'change', + motion: 'moveToEol' , operatorArgs: { enterInsertMode: true }, + operatorMotionArgs: { visualLine: true }}, { keys: ['~'], type: 'operatorMotion', operator: 'swapcase', motion: 'moveByCharacters', motionArgs: { forward: true }}, // Actions From 04c88fffe3333f8a3f603edf9dc2148aeb03cbe9 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Thu, 29 Nov 2012 10:15:30 -0500 Subject: [PATCH 03/63] [vim keymap] Lots of cursor positioning tweaks + unit tests! --- keymap/vim.js | 52 +++++++++++++++++++++++++++++++++------- test/vim_test.js | 62 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 91 insertions(+), 23 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index e09fe6c0c2..126094cf8d 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -286,6 +286,15 @@ // Store instance state in the CodeMirror object. cm.vimState = { inputState: new InputState(), + // When using jk for navigation, if you move from a longer line to a + // shorter line, the cursor may clip to the end of the shorter line. + // If j is pressed again and cursor goes to the next line, the + // cursor should go back to its horizontal position on the longer + // line if it can. This is to keep track of the horizontal position. + lastHPos: -1, + // The last motion command run. Cleared if a non-motion command gets + // executed in between. + lastMotion: null, marks: {}, registerController: new RegisterController({}), visualMode: false, @@ -599,6 +608,7 @@ // The difference between cur and selection cursors are that cur is // being operated on and ignores that there is a selection. var curStart = copyCursor(selectionEnd); + var curOriginal = copyCursor(curStart); var curEnd; var repeat = inputState.getRepeat(); if (repeat > 0 && motionArgs.explicitRepeat) { @@ -617,6 +627,7 @@ inputState.reset(); if (motion) { var motionResult = motions[motion](cm, motionArgs, vim); + vim.lastMotion = motions[motion]; if (!motionResult) { return; } @@ -659,12 +670,13 @@ // CodeMirror can't figure out that we changed directions... cm.setCursor(selectionStart); cm.setSelection(selectionStart, selectionEnd); - } else { + } else if (!operator) { cm.setCursor(curEnd.line, curEnd.ch); } } if (operator) { + vim.lastMotion = null; operatorArgs.repeat = repeat; // Indent in visual mode needs this. if (vim.visualMode) { curStart = selectionStart; @@ -696,7 +708,7 @@ // Keep track of linewise as it affects how paste and change behave. operatorArgs.linewise = linewise; operators[operator](cm, operatorArgs, vim, curStart, - curEnd); + curEnd, curOriginal); if (vim.visualMode) { exitVisualMode(cm, vim); } @@ -739,14 +751,22 @@ return { line: cursor.line, ch: Math.max(0, cursor.ch - repeat) }; } }, - moveByLines: function(cm, motionArgs) { + moveByLines: function(cm, motionArgs, vim) { + var endCh = cm.getCursor().ch; + // Either get the last horizontal position from the vim state, or push + // this one onto the state. + if (vim.lastMotion == this.moveByLines) { + endCh = vim.lastHPos; + } else { + vim.lastHPos = endCh; + } var cursor = cm.getCursor(); var repeat = motionArgs.repeat; if (motionArgs.forward) { return { line: Math.min(cm.lineCount(), cursor.line + repeat), - ch: cursor.ch }; + ch: endCh }; } else { - return { line: Math.max(0, cursor.line - repeat), ch: cursor.ch }; + return { line: Math.max(0, cursor.line - repeat), ch: endCh }; } }, moveByPage: function(cm, motionArgs) { @@ -787,7 +807,7 @@ var cursor = cm.getCursor(); var line = Math.min(cursor.line + motionArgs.repeat - 1, cm.lineCount()); - return { line: line, ch: cm.getLine(line).length }; + return { line: line, ch: cm.getLine(line).length - 1 }; }, moveToFirstNonWhiteSpaceCharacter: function(cm) { // Go to the start of the line where the text begins, or the end for @@ -846,6 +866,15 @@ vim.registerController.pushText(operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd), operatorArgs.linewise); if (operatorArgs.linewise) { + // Delete starting at the first nonwhitespace character of the first + // line, instead of from the start of the first line. This way we get + // an indent when we get into insert mode. This behavior isn't quite + // correct because we should treat this as a completely new line, and + // indent should be whatever codemirror thinks is the right indent. + // But cm.indentLine doesn't seem work on empty lines. + // TODO: Fix the above. + curStart.ch = + findFirstNonWhiteSpaceCharacter(cm.getLine(curStart.line)); // Insert an additional newline so that insert mode can start there. // curEnd should be on the first character of the new line. cm.replaceRange('\n', curStart, curEnd); @@ -859,6 +888,11 @@ vim.registerController.pushText(operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd), operatorArgs.linewise); cm.replaceRange('', curStart, curEnd); + if (operatorArgs.linewise) { + cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); + } else { + cm.setCursor(curStart); + } }, indent: function(cm, operatorArgs, vim, curStart, curEnd) { var startLine = curStart.line; @@ -880,7 +914,7 @@ cm.setCursor(curStart); cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); }, - swapcase: function(cm, operatorArgs, vim, curStart, curEnd) { + swapcase: function(cm, operatorArgs, vim, curStart, curEnd, curOriginal) { var toSwap = cm.getRange(curStart, curEnd); var swapped = ''; for (var i = 0; i < toSwap.length; i++) { @@ -889,10 +923,12 @@ character.toUpperCase(); } cm.replaceRange(swapped, curStart, curEnd); + cm.setCursor(curOriginal); }, - yank: function(cm, operatorArgs, vim, curStart, curEnd) { + yank: function(cm, operatorArgs, vim, curStart, curEnd, curOriginal) { vim.registerController.pushText(operatorArgs.registerName, 'yank', cm.getRange(curStart, curEnd), operatorArgs.linewise); + cm.setCursor(curOriginal); } }; diff --git a/test/vim_test.js b/test/vim_test.js index 5b1307ddfb..53b3c18de9 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -193,8 +193,8 @@ testMotion('G_repeat', ['3', 'G'], makeCursor(lines[2].line, // TODO: Make the test code long enough to test Ctrl-F and Ctrl-B. testMotion('0', '0', makeCursor(0, 0), makeCursor(0, 8)); testMotion('^', '^', makeCursor(0, lines[0].textStart), makeCursor(0, 8)); -testMotion('$', '$', makeCursor(0, lines[0].length - 1), makeCursor(0, 1)); -testMotion('$_repeat', ['2', '$'], makeCursor(1, lines[1].length - 1), +testMotion('$', '$', makeCursor(0, lines[0].length - 2), makeCursor(0, 1)); +testMotion('$_repeat', ['2', '$'], makeCursor(1, lines[1].length - 2), makeCursor(0, 3)); testMotion('f', ['f', 'p'], pChars[0], makeCursor(charLine.line, 0)); testMotion('f_repeat', ['2', 'f', 'p'], pChars[2], pChars[0]); @@ -213,60 +213,72 @@ testMotion('%_braces', ['%'], curlys1.end, curlys1.start); // Operator tests testVim('dw_space', function(cm, vim, helpers) { - cm.setCursor(0, 0); + var curStart = makeCursor(0, 0); + cm.setCursor(curStart); helpers.doKeys('d', 'w'); eq('word1 ', cm.getValue()); var register = vim.registerController.getRegister(); eq(' ', register.text); is(!register.linewise); + eqPos(curStart, cm.getCursor()); }, { value: ' word1 ' }); testVim('dw_word', function(cm, vim, helpers) { - cm.setCursor(0, 1); + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); helpers.doKeys('d', 'w'); eq(' word2', cm.getValue()); var register = vim.registerController.getRegister(); eq('word1 ', register.text); is(!register.linewise); + eqPos(curStart, cm.getCursor()); }, { value: ' word1 word2' }); testVim('dw_only_word', function(cm, vim, helpers) { // Test that if there is only 1 word left, dw deletes till the end of the // line. - cm.setCursor(0, 1); + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); helpers.doKeys('d', 'w'); eq(' ', cm.getValue()); var register = vim.registerController.getRegister(); eq('word1 ', register.text); is(!register.linewise); + eqPos(curStart, cm.getCursor()); }, { value: ' word1 ' }); testVim('dw_eol', function(cm, vim, helpers) { // Assert that dw does not delete the newline if last word to delete is at end // of line. - cm.setCursor(0, 1); + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); helpers.doKeys('d', 'w'); eq(' \nword2', cm.getValue()); var register = vim.registerController.getRegister(); eq('word1', register.text); is(!register.linewise); + eqPos(curStart, cm.getCursor()); }, { value: ' word1\nword2' }); testVim('dw_repeat', function(cm, vim, helpers) { // Assert that dw does delete newline if it should go to the next line, and // that repeat works properly. - cm.setCursor(0, 1); + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); helpers.doKeys('d', '2', 'w'); eq(' ', cm.getValue()); var register = vim.registerController.getRegister(); eq('word1\nword2', register.text); is(!register.linewise); + eqPos(curStart, cm.getCursor()); }, { value: ' word1\nword2' }); testVim('d_inclusive', function(cm, vim, helpers) { // Assert that when inclusive is set, the character the cursor is on gets // deleted too. - cm.setCursor(0, 1); + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); helpers.doKeys('d', 'e'); eq(' ', cm.getValue()); var register = vim.registerController.getRegister(); eq('word1', register.text); is(!register.linewise); + eqPos(curStart, cm.getCursor()); }, { value: ' word1 ' }); testVim('d_reverse', function(cm, vim, helpers) { // Test that deleting in reverse works. @@ -276,6 +288,7 @@ testVim('d_reverse', function(cm, vim, helpers) { var register = vim.registerController.getRegister(); eq('word1\n', register.text); is(!register.linewise); + eqPos(makeCursor(0, 1), cm.getCursor()); }, { value: ' word1\nword2 ' }); testVim('dd', function(cm, vim, helpers) { cm.setCursor(0, 3); @@ -287,6 +300,7 @@ testVim('dd', function(cm, vim, helpers) { var register = vim.registerController.getRegister(); eq(expectedBuffer, register.text); is(register.linewise); + eqPos(makeCursor(0, lines[1].textStart), cm.getCursor()); }); testVim('dd_prefix_repeat', function(cm, vim, helpers) { cm.setCursor(0, 3); @@ -298,6 +312,7 @@ testVim('dd_prefix_repeat', function(cm, vim, helpers) { var register = vim.registerController.getRegister(); eq(expectedBuffer, register.text); is(register.linewise); + eqPos(makeCursor(0, lines[2].textStart), cm.getCursor()); }); testVim('dd_motion_repeat', function(cm, vim, helpers) { cm.setCursor(0, 3); @@ -309,6 +324,7 @@ testVim('dd_motion_repeat', function(cm, vim, helpers) { var register = vim.registerController.getRegister(); eq(expectedBuffer, register.text); is(register.linewise); + eqPos(makeCursor(0, lines[2].textStart), cm.getCursor()); }); testVim('dd_multiply_repeat', function(cm, vim, helpers) { cm.setCursor(0, 3); @@ -320,21 +336,25 @@ testVim('dd_multiply_repeat', function(cm, vim, helpers) { var register = vim.registerController.getRegister(); eq(expectedBuffer, register.text); is(register.linewise); + eqPos(makeCursor(0, lines[6].textStart), cm.getCursor()); }); // Yank commands should behave the exact same as d commands, expect that nothing // gets deleted. testVim('yw_repeat', function(cm, vim, helpers) { - // Assert that dw does delete newline if it should go to the next line, and + // Assert that yw does yank newline if it should go to the next line, and // that repeat works properly. - cm.setCursor(0, 1); + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); helpers.doKeys('y', '2', 'w'); eq(' word1\nword2', cm.getValue()); var register = vim.registerController.getRegister(); eq('word1\nword2', register.text); is(!register.linewise); + eqPos(curStart, cm.getCursor()); }, { value: ' word1\nword2' }); testVim('yy_multiply_repeat', function(cm, vim, helpers) { - cm.setCursor(0, 3); + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); var expectedBuffer = cm.getRange({ line: 0, ch: 0 }, { line: 6, ch: 0 }); var expectedLineCount = cm.lineCount(); @@ -343,19 +363,22 @@ testVim('yy_multiply_repeat', function(cm, vim, helpers) { var register = vim.registerController.getRegister(); eq(expectedBuffer, register.text); is(register.linewise); + eqPos(curStart, cm.getCursor()); }); // Change commands behave like d commands except that it also enters insert // mode. In addition, when the change is linewise, an additional newline is // inserted so that insert mode starts on that line. testVim('cw_repeat', function(cm, vim, helpers) { - // Assert that dw does delete newline if it should go to the next line, and + // Assert that cw does delete newline if it should go to the next line, and // that repeat works properly. - cm.setCursor(0, 1); + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); helpers.doKeys('c', '2', 'w'); eq(' ', cm.getValue()); var register = vim.registerController.getRegister(); eq('word1\nword2', register.text); is(!register.linewise); + eqPos(curStart, cm.getCursor()); eq('vim-insert', cm.getOption('keyMap')); }, { value: ' word1\nword2' }); testVim('cc_multiply_repeat', function(cm, vim, helpers) { @@ -368,21 +391,25 @@ testVim('cc_multiply_repeat', function(cm, vim, helpers) { var register = vim.registerController.getRegister(); eq(expectedBuffer, register.text); is(register.linewise); + eqPos(makeCursor(0, lines[0].textStart), cm.getCursor()); eq('vim-insert', cm.getOption('keyMap')); }); // Swapcase commands edit in place and do not modify registers. testVim('g~w_repeat', function(cm, vim, helpers) { // Assert that dw does delete newline if it should go to the next line, and // that repeat works properly. - cm.setCursor(0, 1); + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); helpers.doKeys('g', '~', '2', 'w'); eq(' WORD1\nWORD2', cm.getValue()); var register = vim.registerController.getRegister(); eq('', register.text); is(!register.linewise); + eqPos(curStart, cm.getCursor()); }, { value: ' word1\nword2' }); testVim('g~g~', function(cm, vim, helpers) { - cm.setCursor(0, 3); + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); var expectedLineCount = cm.lineCount(); var expectedValue = cm.getValue().toUpperCase(); helpers.doKeys('2', 'g', '~', '3', 'g', '~'); @@ -390,6 +417,7 @@ testVim('g~g~', function(cm, vim, helpers) { var register = vim.registerController.getRegister(); eq('', register.text); is(!register.linewise); + eqPos(curStart, cm.getCursor()); }, { value: ' word1\nword2\nword3\nword4\nword5\nword6' }); testVim('>{motion}', function(cm, vim, helpers) { cm.setCursor(1, 3); @@ -400,6 +428,7 @@ testVim('>{motion}', function(cm, vim, helpers) { var register = vim.registerController.getRegister(); eq('', register.text); is(!register.linewise); + eqPos(makeCursor(0, 3), cm.getCursor()); }, { value: ' word1\nword2\nword3 ', indentUnit: 2 }); testVim('>>', function(cm, vim, helpers) { cm.setCursor(0, 3); @@ -410,6 +439,7 @@ testVim('>>', function(cm, vim, helpers) { var register = vim.registerController.getRegister(); eq('', register.text); is(!register.linewise); + eqPos(makeCursor(0, 3), cm.getCursor()); }, { value: ' word1\nword2\nword3 ', indentUnit: 2 }); testVim('<{motion}', function(cm, vim, helpers) { cm.setCursor(1, 3); @@ -420,6 +450,7 @@ testVim('<{motion}', function(cm, vim, helpers) { var register = vim.registerController.getRegister(); eq('', register.text); is(!register.linewise); + eqPos(makeCursor(0, 1), cm.getCursor()); }, { value: ' word1\n word2\nword3 ', indentUnit: 2 }); testVim('<<', function(cm, vim, helpers) { cm.setCursor(0, 3); @@ -430,4 +461,5 @@ testVim('<<', function(cm, vim, helpers) { var register = vim.registerController.getRegister(); eq('', register.text); is(!register.linewise); + eqPos(makeCursor(0, 1), cm.getCursor()); }, { value: ' word1\n word2\nword3 ', indentUnit: 2 }); From bbf49b15219bbcc199bf2116ec76b845b2e0a57a Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Thu, 29 Nov 2012 16:34:54 +0100 Subject: [PATCH 04/63] [php mode] Add a few more atom keywords --- mode/php/php.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/php/php.js b/mode/php/php.js index 2f5b1e7215..ea32fda72a 100644 --- a/mode/php/php.js +++ b/mode/php/php.js @@ -20,7 +20,7 @@ "die echo empty exit eval include include_once isset list require require_once return " + "print unset __halt_compiler self static parent"), blockKeywords: keywords("catch do else elseif for foreach if switch try while"), - atoms: keywords("true false null TRUE FALSE NULL"), + atoms: keywords("true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__"), builtin: keywords("func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport echo print global static exit array empty eval isset unset die include require include_once require_once"), multiLineStrings: true, hooks: { From 017dad450c232b6d005512a1bcb368f165103eea Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Thu, 29 Nov 2012 19:43:09 -0500 Subject: [PATCH 05/63] [vim keymap] Fix A and added test to prevent future regression. --- keymap/vim.js | 10 ++++++++-- test/vim_test.js | 32 +++++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 126094cf8d..5ad0445a6e 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -174,7 +174,7 @@ motion: 'moveByCharacters', motionArgs: { forward: true, noRepeat: true }}, { keys: ['A'], type: 'action', action: 'enterInsertMode', - motion: 'moveToEol' }, + actionArgs: { insertAt: 'eol' }}, { keys: ['i'], type: 'action', action: 'enterInsertMode' }, { keys: ['I'], type: 'action', action: 'enterInsertMode', motion: 'moveToFirstNonWhiteSpaceCharacter' }, @@ -933,7 +933,13 @@ }; var actions = { - enterInsertMode: function(cm) { + enterInsertMode: function(cm, actionArgs) { + var insertAt = (actionArgs) ? actionArgs.insertAt : null; + if (insertAt == 'eol') { + var cursor = cm.getCursor(); + cursor = { line: cursor.line, ch: lineLength(cm, cursor.line) }; + cm.setCursor(cursor); + } cm.setOption('keyMap', 'vim-insert'); }, toggleVisualMode: function(cm, actionArgs, vim) { diff --git a/test/vim_test.js b/test/vim_test.js index 53b3c18de9..e57b7ad404 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -19,7 +19,7 @@ var lines = (function() { for (var i = 0; i < lineText.length; i++) { ret[i] = { line: i, - length: lineText[i].length + 1, // We stripped out the \n + length: lineText[i].length, textStart: /^\s*/.exec(lineText[i])[0].length }; } @@ -193,8 +193,8 @@ testMotion('G_repeat', ['3', 'G'], makeCursor(lines[2].line, // TODO: Make the test code long enough to test Ctrl-F and Ctrl-B. testMotion('0', '0', makeCursor(0, 0), makeCursor(0, 8)); testMotion('^', '^', makeCursor(0, lines[0].textStart), makeCursor(0, 8)); -testMotion('$', '$', makeCursor(0, lines[0].length - 2), makeCursor(0, 1)); -testMotion('$_repeat', ['2', '$'], makeCursor(1, lines[1].length - 2), +testMotion('$', '$', makeCursor(0, lines[0].length - 1), makeCursor(0, 1)); +testMotion('$_repeat', ['2', '$'], makeCursor(1, lines[1].length - 1), makeCursor(0, 3)); testMotion('f', ['f', 'p'], pChars[0], makeCursor(charLine.line, 0)); testMotion('f_repeat', ['2', 'f', 'p'], pChars[2], pChars[0]); @@ -463,3 +463,29 @@ testVim('<<', function(cm, vim, helpers) { is(!register.linewise); eqPos(makeCursor(0, 1), cm.getCursor()); }, { value: ' word1\n word2\nword3 ', indentUnit: 2 }); + +// Action tests +testVim('a', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('a'); + eqPos(makeCursor(0, 2), cm.getCursor()); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('i', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('i'); + eqPos(makeCursor(0, 1), cm.getCursor()); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('A', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('A'); + eqPos(makeCursor(0, lines[0].length), cm.getCursor()); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('I', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('I'); + eqPos(makeCursor(0, lines[0].textStart), cm.getCursor()); + eq('vim-insert', cm.getOption('keyMap')); +}); From 5d22f9c30f41992a3866de37ef1e5896d5915c43 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Thu, 29 Nov 2012 21:47:03 -0500 Subject: [PATCH 06/63] [vim keymap] More unit tests and fixes. Most keys covered now. --- keymap/vim.js | 69 +++++++++++++++++--------- test/vim_test.js | 126 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 23 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 5ad0445a6e..2e70970769 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -73,6 +73,8 @@ { keys: ['Ctrl-p'], type: 'keyToKey', toKeys: ['k'] }, { keys: ['Ctrl-['], type: 'keyToKey', toKeys: ['Esc'] }, { keys: ['Ctrl-c'], type: 'keyToKey', toKeys: ['Esc'] }, + { keys: ['s'], type: 'keyToKey', toKeys: ['c', 'l'] }, + { keys: ['S'], type: 'keyToKey', toKeys: ['c', 'c'] }, // Motions { keys: ['h'], type: 'motion', motion: 'moveByCharacters', @@ -178,13 +180,6 @@ { keys: ['i'], type: 'action', action: 'enterInsertMode' }, { keys: ['I'], type: 'action', action: 'enterInsertMode', motion: 'moveToFirstNonWhiteSpaceCharacter' }, - { keys: ['s'], type: 'action', action: 'enterInsertMode', - motion: 'moveByCharacters', motionArgs: { forward: true }, - operator: 'delete' }, - { keys: ['S'], type: 'action', action: 'enterInsertMode', - motion: 'moveByLines', - motionArgs: { forward: true, linewise: true, explicitRepeat: true }, - operator: 'delete' }, { keys: ['o'], type: 'action', action: 'newLineAndEnterInsertMode', actionArgs: { after: true }}, { keys: ['O'], type: 'action', action: 'newLineAndEnterInsertMode', @@ -592,6 +587,7 @@ actionArgs.repeatIsExplicit = repeatIsExplicit; actionArgs.registerName = inputState.registerName; inputState.reset(); + vim.lastMotion = null, actions[command.action](cm, actionArgs, vim); }, evalInput: function(cm, vim) { @@ -993,9 +989,19 @@ var curEnd = { line: lineNumEnd, ch: lineLength(cm, lineNumEnd) - 1 }; } - var text = cm.getRange(curStart, curEnd).replace(/\n\s*/g, ' '); - cm.replaceRange(text, curStart, curEnd); - cm.setCursor(curStart); + var finalCh = 0; + cm.operation(function() { + for (var i = curStart.line; i < curEnd.line; i++) { + finalCh = lineLength(cm, curStart.line); + var tmp = { line: curStart.line + 1, + ch: lineLength(cm, curStart.line + 1) }; + var text = cm.getRange(curStart, tmp); + text = text.replace(/\n\s*/g, ' '); + cm.replaceRange(text, curStart, tmp); + } + var curFinalPos = { line: curStart.line, ch: finalCh }; + cm.setCursor(curFinalPos); + }); }, newLineAndEnterInsertMode: function(cm, actionArgs) { var insertAt = cm.getCursor(); @@ -1013,27 +1019,37 @@ for (var text = '', i = 0; i < actionArgs.repeat; i++) { text += register.text; } - var curChEnd = 0; var linewise = register.linewise; if (linewise) { - cur.line += actionArgs.after ? 1 : 0; - cur.ch = 0; - curChEnd = 0; + if (actionArgs.after) { + // Move the newline at the end to the start instead, and paste just + // before the newline character of the line we are on right now. + text = '\n' + text.slice(0, text.length - 1); + cur.ch = lineLength(cm, cur.line); + } else { + cur.ch = 0; + } } else { cur.ch += actionArgs.after ? 1 : 0; - curChEnd = cur.ch; } - // Set cursor in the right place to let CodeMirror handle moving it. - cm.setCursor(cur.line, curChEnd); cm.replaceRange(text, cur); // Now fine tune the cursor to where we want it. - if (linewise) { - cm.setCursor(cm.getCursor().line - 1, 0); - } - else { - cur = cm.getCursor(); - cm.setCursor(cur.line, cur.ch - 1); + var curPosFinal; + var idx; + if (linewise && actionArgs.after) { + curPosFinal = makeCursor(cur.line + 1, + findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1))); + } else if (linewise && !actionArgs.after) { + curPosFinal = makeCursor(cur.line, + findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line))); + } else if (!linewise && actionArgs.after) { + idx = cm.indexFromPos(cur); + curPosFinal = cm.posFromIndex(idx + text.length - 1); + } else { + idx = cm.indexFromPos(cur); + curPosFinal = cm.posFromIndex(idx + text.length); } + cm.setCursor(curPosFinal); }, undo: function(cm, actionArgs) { repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)(); @@ -1068,6 +1084,7 @@ replaceWithStr += replaceWith; } cm.replaceRange(replaceWithStr, curStart, curEnd); + cm.setCursor(offsetCursor(curEnd, 0, -1)); } }; @@ -1118,6 +1135,12 @@ } return ret; } + function makeCursor(line, ch) { + return { line: line, ch: ch }; + }; + function offsetCursor(cur, offsetLine, offsetCh) { + return { line: cur.line + offsetLine, ch: cur.ch + offsetCh }; + }; function arrayEq(a1, a2) { if (a1.length != a2.length) return false; for (var i = 0; i < a1.length; i++) { diff --git a/test/vim_test.js b/test/vim_test.js index e57b7ad404..c657fe7a79 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -212,6 +212,56 @@ testMotion('%_squares', ['%'], squares1.end, squares1.start); testMotion('%_braces', ['%'], curlys1.end, curlys1.start); // Operator tests +testVim('dl', function(cm, vim, helpers) { + var curStart = makeCursor(0, 0); + cm.setCursor(curStart); + helpers.doKeys('d', 'l'); + eq('word1 ', cm.getValue()); + var register = vim.registerController.getRegister(); + eq(' ', register.text); + is(!register.linewise); + eqPos(curStart, cm.getCursor()); +}, { value: ' word1 ' }); +testVim('dl_repeat', function(cm, vim, helpers) { + var curStart = makeCursor(0, 0); + cm.setCursor(curStart); + helpers.doKeys('2', 'd', 'l'); + eq('ord1 ', cm.getValue()); + var register = vim.registerController.getRegister(); + eq(' w', register.text); + is(!register.linewise); + eqPos(curStart, cm.getCursor()); +}, { value: ' word1 ' }); +testVim('dh', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'h'); + eq(' wrd1 ', cm.getValue()); + var register = vim.registerController.getRegister(); + eq('o', register.text); + is(!register.linewise); + eqPos(offsetCursor(curStart, 0 , -1), cm.getCursor()); +}, { value: ' word1 ' }); +testVim('dj', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'j'); + eq(' word3', cm.getValue()); + var register = vim.registerController.getRegister(); + eq(' word1\nword2\n', register.text); + is(register.linewise); + eqPos(makeCursor(0, 1), cm.getCursor()); +}, { value: ' word1\nword2\n word3' }); +testVim('dk', function(cm, vim, helpers) { + var curStart = makeCursor(1, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'k'); + eq(' word3', cm.getValue()); + var register = vim.registerController.getRegister(); + eq(' word1\nword2\n', register.text); + is(register.linewise); + eqPos(makeCursor(0, 1), cm.getCursor()); +}, { value: ' word1\nword2\n word3' }); testVim('dw_space', function(cm, vim, helpers) { var curStart = makeCursor(0, 0); cm.setCursor(curStart); @@ -489,3 +539,79 @@ testVim('I', function(cm, vim, helpers) { eqPos(makeCursor(0, lines[0].textStart), cm.getCursor()); eq('vim-insert', cm.getOption('keyMap')); }); +testVim('o', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('o'); + eq('word1\n\nword2', cm.getValue()); + eqPos(makeCursor(1, 0), cm.getCursor()); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: 'word1\nword2' }); +testVim('O', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('O'); + eq('\nword1\nword2', cm.getValue()); + eqPos(makeCursor(0, 0), cm.getCursor()); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: 'word1\nword2' }); +testVim('J', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('J'); + var expectedValue = 'word1 word2\nword3\n word4'; + eq(expectedValue, cm.getValue()); + eqPos(makeCursor(0, expectedValue.indexOf('word2') - 1), cm.getCursor()); +}, { value: 'word1 \n word2\nword3\n word4' }); +testVim('J_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('3', 'J'); + var expectedValue = 'word1 word2 word3\n word4'; + eq(expectedValue, cm.getValue()); + eqPos(makeCursor(0, expectedValue.indexOf('word3') - 1), cm.getCursor()); +}, { value: 'word1 \n word2\nword3\n word4' }); +testVim('p', function(cm, vim, helpers) { + cm.setCursor(0, 1); + vim.registerController.pushText('"', 'yank', 'abc\ndef', false); + helpers.doKeys('p'); + eq('__abc\ndef_', cm.getValue()); + eqPos(makeCursor(1, 2), cm.getCursor()); +}, { value: '___' }); +testVim('p_register', function(cm, vim, helpers) { + cm.setCursor(0, 1); + vim.registerController.getRegister('a').set('abc\ndef', false); + helpers.doKeys('"', 'a', 'p'); + eq('__abc\ndef_', cm.getValue()); + eqPos(makeCursor(1, 2), cm.getCursor()); +}, { value: '___' }); +testVim('p_wrong_register', function(cm, vim, helpers) { + cm.setCursor(0, 1); + vim.registerController.getRegister('a').set('abc\ndef', false); + helpers.doKeys('p'); + eq('___', cm.getValue()); + eqPos(makeCursor(0, 1), cm.getCursor()); +}, { value: '___' }); +testVim('p_line', function(cm, vim, helpers) { + cm.setCursor(0, 1); + vim.registerController.pushText('"', 'yank', ' a\nd\n', true); + helpers.doKeys('2', 'p'); + eq('___\n a\nd\n a\nd', cm.getValue()); + eqPos(makeCursor(1, 2), cm.getCursor()); +}, { value: '___' }); +testVim('P', function(cm, vim, helpers) { + cm.setCursor(0, 1); + vim.registerController.pushText('"', 'yank', 'abc\ndef', false); + helpers.doKeys('P'); + eq('_abc\ndef__', cm.getValue()); + eqPos(makeCursor(1, 3), cm.getCursor()); +}, { value: '___' }); +testVim('P_line', function(cm, vim, helpers) { + cm.setCursor(0, 1); + vim.registerController.pushText('"', 'yank', ' a\nd\n', true); + helpers.doKeys('2', 'P'); + eq(' a\nd\n a\nd\n___', cm.getValue()); + eqPos(makeCursor(0, 2), cm.getCursor()); +}, { value: '___' }); +testVim('r', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('3', 'r', 'u'); + eq('wuuuet', cm.getValue()); + eqPos(makeCursor(0, 3), cm.getCursor()); +}, { value: 'wordet' }); From 2e4dc84409bd268ebec66dc81350f621c29e2e5c Mon Sep 17 00:00:00 2001 From: Mason Malone Date: Thu, 29 Nov 2012 23:37:10 -0500 Subject: [PATCH 07/63] Support blank lines in CodeMirror.multiplexingMode This adds a blankLine method that calls the current mode's blankLine if it has one. It has allows switching on a blank line if "\n" is specified for "open" or "close". --- doc/manual.html | 2 ++ lib/util/multiplex.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/doc/manual.html b/doc/manual.html index 1f3691576f..9eef7f3b4a 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1150,6 +1150,8 @@

Add-ons

the open fields of the passed objects. When in a sub-mode, it will go back to the top mode again when the close string is encountered. + Pass "\n" for open or close + if you want to switch on a blank line. When delimStyle is specified, it will be the token style returned for the delimiter tokens. The outer mode will not see the content between the delimiters. diff --git a/lib/util/multiplex.js b/lib/util/multiplex.js index 214730839d..e77ff2a9c3 100644 --- a/lib/util/multiplex.js +++ b/lib/util/multiplex.js @@ -68,6 +68,24 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { return mode.indent(state.innerActive ? state.inner : state.outer, textAfter); }, + blankLine: function(state) { + var mode = state.innerActive ? state.innerActive.mode : outer; + if (mode.blankLine) { + mode.blankLine(state.innerActive ? state.inner : state.outer); + } + if (!state.innerActive) { + for (var i = 0; i < n_others; ++i) { + var other = others[i]; + if (other.open === "\n") { + state.innerActive = other; + state.inner = CodeMirror.startState(other.mode, mode.indent ? mode.indent(state.outer, "") : 0); + } + } + } else if (mode.close === "\n") { + state.innerActive = state.inner = null; + } + }, + electricChars: outer.electricChars, innerMode: function(state) { From 130f777d8a0c88d67dae74284a49cccf6f08159d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 30 Nov 2012 11:04:11 +0100 Subject: [PATCH 08/63] Add HaxPad to real-world uses --- doc/realworld.html | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/realworld.html b/doc/realworld.html index a24fe2c3b3..b5d2daa98f 100644 --- a/doc/realworld.html +++ b/doc/realworld.html @@ -43,6 +43,7 @@

{ } CodeMi
  • GitHub's Android app
  • Google Apps Script
  • Haxe (Haxe Playground)
  • +
  • HaxPad (editor for Win RT)
  • Histone template engine playground
  • ICEcoder (web IDE)
  • Joomla plugin
  • From db029a1d669ab63ce0246faea7bc9ec009f92f3f Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Fri, 30 Nov 2012 08:44:43 -0500 Subject: [PATCH 09/63] Fix D,C,Y off-by-one error. --- keymap/vim.js | 11 +++++++---- test/vim_test.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 2e70970769..53fa4e1d3a 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -163,11 +163,14 @@ motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }}, { keys: ['D'], type: 'operatorMotion', operator: 'delete', - motion: 'moveToEol' , operatorMotionArgs: { visualLine: true }}, + motion: 'moveToEol', motionArgs: { inclusive: true }, + operatorMotionArgs: { visualLine: true }}, { keys: ['Y'], type: 'operatorMotion', operator: 'yank', - motion: 'moveToEol' , operatorMotionArgs: { visualLine: true }}, - { keys: ['C'], type: 'operatorMotion', operator: 'change', - motion: 'moveToEol' , operatorArgs: { enterInsertMode: true }, + motion: 'moveToEol', motionArgs: { inclusive: true }, + operatorMotionArgs: { visualLine: true }}, + { keys: ['C'], type: 'operatorMotion', + operator: 'change', operatorArgs: { enterInsertMode: true }, + motion: 'moveToEol', motionArgs: { inclusive: true }, operatorMotionArgs: { visualLine: true }}, { keys: ['~'], type: 'operatorMotion', operator: 'swapcase', motion: 'moveByCharacters', motionArgs: { forward: true }}, diff --git a/test/vim_test.js b/test/vim_test.js index c657fe7a79..00771c29dd 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -514,6 +514,39 @@ testVim('<<', function(cm, vim, helpers) { eqPos(makeCursor(0, 1), cm.getCursor()); }, { value: ' word1\n word2\nword3 ', indentUnit: 2 }); +// Operator-motion tests +testVim('D', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('D'); + eq(' wo\nword2\n word3', cm.getValue()); + var register = vim.registerController.getRegister(); + eq('rd1', register.text); + is(!register.linewise); + eqPos(makeCursor(0, 3), cm.getCursor()); +}, { value: ' word1\nword2\n word3' }); +testVim('C', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('C'); + eq(' wo\nword2\n word3', cm.getValue()); + var register = vim.registerController.getRegister(); + eq('rd1', register.text); + is(!register.linewise); + eqPos(makeCursor(0, 3), cm.getCursor()); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: ' word1\nword2\n word3' }); +testVim('Y', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('Y'); + eq(' word1\nword2\n word3', cm.getValue()); + var register = vim.registerController.getRegister(); + eq('rd1', register.text); + is(!register.linewise); + eqPos(makeCursor(0, 3), cm.getCursor()); +}, { value: ' word1\nword2\n word3' }); + // Action tests testVim('a', function(cm, vim, helpers) { cm.setCursor(0, 1); From a863096af5598056e0a2376add08252c4d17f8d7 Mon Sep 17 00:00:00 2001 From: Jason Siefken Date: Sat, 1 Dec 2012 22:23:09 -0800 Subject: [PATCH 10/63] [vim keymap] added Home, End, PageUp, PageDown, | moveByLines was changed to better keep track of the previous action. For example, if moveToEol was the previous action, moving up or down should stay at Eol regardless of line length. --- keymap/vim.js | 73 +++++++++++++++++++++++++++++++++--------------- test/vim_test.js | 26 +++++++++++++++++ 2 files changed, 77 insertions(+), 22 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 53fa4e1d3a..660f43c7b3 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -75,6 +75,10 @@ { keys: ['Ctrl-c'], type: 'keyToKey', toKeys: ['Esc'] }, { keys: ['s'], type: 'keyToKey', toKeys: ['c', 'l'] }, { keys: ['S'], type: 'keyToKey', toKeys: ['c', 'c'] }, + { keys: ['Home'], type: 'keyToKey', toKeys: ['0'] }, + { keys: ['End'], type: 'keyToKey', toKeys: ['$'] }, + { keys: ['PageUp'], type: 'keyToKey', toKeys: ['Ctrl-b'] }, + { keys: ['PageDown'], type: 'keyToKey', toKeys: ['Ctrl-f'] }, // Motions { keys: ['h'], type: 'motion', motion: 'moveByCharacters', @@ -127,7 +131,9 @@ { keys: ['0'], type: 'motion', motion: 'moveToStartOfLine' }, { keys: ['^'], type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' }, - { keys: ['$'], type: 'motion', motion: 'moveToEol' }, + { keys: ['$'], type: 'motion', + motion: 'moveToEol', + motionArgs: {inclusive: true} }, { keys: ['%'], type: 'motion', motion: 'moveToMatchedSymbol', motionArgs: { inclusive: true }}, @@ -145,6 +151,9 @@ motionArgs: { forward: false }}, { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark' }, { keys: ['`', 'character'], type: 'motion', motion: 'goToMark' }, + { keys: ['|'], type: 'motion', + motion: 'moveToColumn', + motionArgs: { }}, // Operators { keys: ['d'], type: 'operator', operator: 'delete' }, { keys: ['y'], type: 'operator', operator: 'yank' }, @@ -226,7 +235,7 @@ var SPECIAL_SYMBOLS = '~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;\"\''; var specialSymbols = SPECIAL_SYMBOLS.split(''); var specialKeys = ['Left', 'Right', 'Up', 'Down', 'Space', 'Backspace', - 'Esc']; + 'Esc', 'Home', 'End', 'PageUp', 'PageDown']; var validMarks = upperCaseAlphabet.concat(lowerCaseAlphabet).concat( numbers); var validRegisters = upperCaseAlphabet.concat(lowerCaseAlphabet).concat( @@ -743,30 +752,35 @@ var cursor = cm.getCursor(); var line = cm.getLine(cursor.line); var repeat = motionArgs.repeat; - if (motionArgs.forward) { - return { line: cursor.line, - ch: Math.min(line.length, cursor.ch + repeat) }; - } else { - return { line: cursor.line, ch: Math.max(0, cursor.ch - repeat) }; - } + var ch = motionArgs.forward ? Math.min(line.length - 1, cursor.ch + repeat) : + Math.max(0, cursor.ch - repeat) + return { line: cursor.line, ch: ch }; }, moveByLines: function(cm, motionArgs, vim) { var endCh = cm.getCursor().ch; - // Either get the last horizontal position from the vim state, or push - // this one onto the state. - if (vim.lastMotion == this.moveByLines) { - endCh = vim.lastHPos; - } else { - vim.lastHPos = endCh; + // Depending what our last motion was, we may want to do different + // things. If our last motion was moving vertically, we want to + // preserve the HPos from our last horizontal move. If our last motion + // was going to the end of a line, moving vertically we should go to + // the end of the line, etc. + switch (vim.lastMotion) { + case this.moveByLines: + case this.moveToColumn: + case this.moveToEol: + endCh = vim.lastHPos; + break; + default: + vim.lastHPos = endCh; } var cursor = cm.getCursor(); var repeat = motionArgs.repeat; - if (motionArgs.forward) { - return { line: Math.min(cm.lineCount(), cursor.line + repeat), - ch: endCh }; - } else { - return { line: Math.max(0, cursor.line - repeat), ch: endCh }; - } + var line = motionArgs.forward ? Math.min(cm.lineCount() - 1, cursor.line + repeat) : + Math.max(0, cursor.line - repeat); + // Make sure our endCh isn't too far right. We need it to highlight + // the last char on the line, not the empty space to the right of it + endCh = Math.min(endCh, cm.getLine(line).length - 1); + + return { line: line, ch: endCh }; }, moveByPage: function(cm, motionArgs) { // CodeMirror only exposes functions that move the cursor page down, so @@ -802,10 +816,17 @@ return moveToCharacter(cm, repeat, motionArgs.forward, motionArgs.selectedCharacter); }, - moveToEol: function(cm, motionArgs) { + moveToColumn: function(cm, motionArgs, vim) { + var repeat = motionArgs.repeat; + // repeat is equivalent to which column we want to move to! + vim.lastHPos = repeat - 1; + return moveToColumn(cm, repeat); + }, + moveToEol: function(cm, motionArgs, vim) { var cursor = cm.getCursor(); var line = Math.min(cursor.line + motionArgs.repeat - 1, - cm.lineCount()); + cm.lineCount() - 1); + vim.lastHPos = Infinity; return { line: line, ch: cm.getLine(line).length - 1 }; }, moveToFirstNonWhiteSpaceCharacter: function(cm) { @@ -1388,6 +1409,14 @@ ch: idx }; } + function moveToColumn(cm, repeat) { + // repeat is always >= 1, so repeat - 1 alwasy corresponds + // to the column we want to go to. + var line = cm.getCursor().line; + var ch = Math.min(cm.getLine(line).length - 1, repeat - 1); + return { line: line, ch: ch }; + } + function charIdxInLine(start, line, character, forward, includeChar) { // Search for char in line. // motion_options: {forward, includeChar} diff --git a/test/vim_test.js b/test/vim_test.js index 00771c29dd..d03255ec3f 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -20,6 +20,7 @@ var lines = (function() { ret[i] = { line: i, length: lineText[i].length, + lineText: lineText[i], textStart: /^\s*/.exec(lineText[i])[0].length }; } @@ -159,6 +160,8 @@ function offsetCursor(cur, offsetLine, offsetCh) { }; // Motion tests +testMotion('|', '|', makeCursor(0, 0), makeCursor(0,4)); +testMotion('|_repeat', ['3', '|'], makeCursor(0, 2), makeCursor(0,4)); testMotion('h', 'h', makeCursor(0, 0), word1.start); testMotion('h_repeat', ['3', 'h'], offsetCursor(word1.end, 0, -3), word1.end); testMotion('l', 'l', makeCursor(0, 1)); @@ -210,6 +213,29 @@ testMotion('T_repeat', ['2', 'T', 'p'], offsetCursor(pChars[0], 0, 1), pChars[2] testMotion('%_parens', ['%'], parens1.end, parens1.start); testMotion('%_squares', ['%'], squares1.end, squares1.start); testMotion('%_braces', ['%'], curlys1.end, curlys1.start); +// Make sure that moving down after going to the end of a line always leaves you +// at the end of a line, but preserves the offset in other cases +testVim('Changing lines after Eol operation', function(cm, vim, helpers) { + var startPos = { line: 0, ch: 0 }; + cm.setCursor(startPos); + helpers.doKeys(['$']); + helpers.doKeys(['j']); + // After moving to Eol and then down, we should be at Eol of line 2 + helpers.assertCursorAt({ line: 1, ch: lines[1].length - 1 }); + helpers.doKeys(['j']); + // After moving down, we should be at Eol of line 3 + helpers.assertCursorAt({ line: 2, ch: lines[2].length - 1 }); + helpers.doKeys(['h']); + helpers.doKeys(['j']); + // After moving back one space and then down, since line 4 is shorter than line 2, we should + // be at Eol of line 2 - 1 + helpers.assertCursorAt({ line: 3, ch: lines[3].length - 1 }); + helpers.doKeys(['j']); + helpers.doKeys(['j']); + // After moving down again, since line 3 has enough characters, we should be back to the + // same place we were at on line 1 + helpers.assertCursorAt({ line: 5, ch: lines[2].length - 2 }); +}); // Operator tests testVim('dl', function(cm, vim, helpers) { From b6f4fdffc8fd453f5729e11a983acbe53561f078 Mon Sep 17 00:00:00 2001 From: Jason Siefken Date: Sun, 2 Dec 2012 13:05:58 -0800 Subject: [PATCH 11/63] [vim keymap] code cleanups, jsHint compliance, add clipCursorToContent --- keymap/vim.js | 256 ++++++++++++++++++++++---------------------------- 1 file changed, 113 insertions(+), 143 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 660f43c7b3..48cc49f564 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -133,7 +133,7 @@ motion: 'moveToFirstNonWhiteSpaceCharacter' }, { keys: ['$'], type: 'motion', motion: 'moveToEol', - motionArgs: {inclusive: true} }, + motionArgs: { inclusive: true }}, { keys: ['%'], type: 'motion', motion: 'moveToMatchedSymbol', motionArgs: { inclusive: true }}, @@ -260,7 +260,7 @@ return (/^[A-Z]$/).test(k); } function isAlphanumeric(k) { - return (/^[a-zA-Z-0-9]/).test(k); + return (/^[a-zA-Z0-9]/).test(k); } function isWhiteSpace(k) { return whiteSpaceRegex.test(k); @@ -313,6 +313,7 @@ // This is the outermost function called by CodeMirror, after keys have // been mapped to their Vim equivalents. handleKey: function(cm, key) { + var command; this.maybeInitState(cm); var vim = cm.vimState; if (key == 'Esc') { @@ -330,23 +331,22 @@ } if (key != '0' || (key == '0' && vim.inputState.getRepeat() === 0)) { // Have to special case 0 since it's both a motion and a number. - var command = commandDispatcher.matchCommand(key, defaultKeymap, - vim); + command = commandDispatcher.matchCommand(key, defaultKeymap, vim); } - if (!command && isNumber(key)) { - // Increment count unless count is 0 and key is 0. - vim.inputState.pushRepeatDigit(key); + if (!command) { + if (isNumber(key)) { + // Increment count unless count is 0 and key is 0. + vim.inputState.pushRepeatDigit(key); + } return; } - if (command) { - if (command.type == 'keyToKey') { - // TODO: prevent infinite recursion. - for (var i = 0; i < command.toKeys.length; i++) { - this.handleKey(cm, command.toKeys[i]); - } - } else { - commandDispatcher.processCommand(cm, vim, command); + if (command.type == 'keyToKey') { + // TODO: prevent infinite recursion. + for (var i = 0; i < command.toKeys.length; i++) { + this.handleKey(cm, command.toKeys[i]); } + } else { + commandDispatcher.processCommand(cm, vim, command); } } }; @@ -684,6 +684,7 @@ } if (operator) { + var inverted = false; vim.lastMotion = null; operatorArgs.repeat = repeat; // Indent in visual mode needs this. if (vim.visualMode) { @@ -696,7 +697,7 @@ var tmp = curStart; curStart = curEnd; curEnd = tmp; - var inverted = true; + inverted = true; } if (motionArgs.inclusive && !(vim.visualMode && inverted)) { // Move the selection end one to the right to include the last @@ -736,10 +737,9 @@ expandToLine: function(cm, motionArgs) { // Expands forward to end of line, and then to next line if repeat is > 1. // Does not handle backward motion! - var cursor = cm.getCursor(); - var endLine = Math.min(cm.lineCount(), - cursor.line + motionArgs.repeat - 1); - return { line: endLine, ch: lineLength(cm, endLine) }; + var cur = cm.getCursor(); + return clipCursorToContent(cm, { line: cur.line + motionArgs.repeat - 1, + ch: Infinity }); }, goToMark: function(cm, motionArgs, vim) { var mark = vim.marks[motionArgs.selectedCharacter]; @@ -749,12 +749,10 @@ return null; }, moveByCharacters: function(cm, motionArgs) { - var cursor = cm.getCursor(); - var line = cm.getLine(cursor.line); + var cur = cm.getCursor(); var repeat = motionArgs.repeat; - var ch = motionArgs.forward ? Math.min(line.length - 1, cursor.ch + repeat) : - Math.max(0, cursor.ch - repeat) - return { line: cursor.line, ch: ch }; + var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat; + return clipCursorToContent(cm, { line: cur.line, ch: ch }); }, moveByLines: function(cm, motionArgs, vim) { var endCh = cm.getCursor().ch; @@ -772,15 +770,10 @@ default: vim.lastHPos = endCh; } - var cursor = cm.getCursor(); + var cur = cm.getCursor(); var repeat = motionArgs.repeat; - var line = motionArgs.forward ? Math.min(cm.lineCount() - 1, cursor.line + repeat) : - Math.max(0, cursor.line - repeat); - // Make sure our endCh isn't too far right. We need it to highlight - // the last char on the line, not the empty space to the right of it - endCh = Math.min(endCh, cm.getLine(line).length - 1); - - return { line: line, ch: endCh }; + var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat; + return clipCursorToContent(cm, { line: line, ch: endCh }); }, moveByPage: function(cm, motionArgs) { // CodeMirror only exposes functions that move the cursor page down, so @@ -790,7 +783,7 @@ // need this ugliness. But it might make visual mode hard. var curStart = cm.getCursor(); var repeat = motionArgs.repeat; - cm.moveV(motionArgs.forward ? repeat : (-1 * repeat), 'page'); + cm.moveV((motionArgs.forward ? repeat : -repeat), 'page'); var curEnd = cm.getCursor(); cm.setCursor(curStart); return curEnd; @@ -803,12 +796,8 @@ var repeat = motionArgs.repeat; var curEnd = moveToCharacter(cm, repeat, motionArgs.forward, motionArgs.selectedCharacter); - if (motionArgs.forward) { - curEnd.ch--; - } - else { - curEnd.ch++; - } + var increment = motionArgs.forward ? -1 : 1; + curEnd.ch += increment; return curEnd; }, moveToCharacter: function(cm, motionArgs) { @@ -823,11 +812,10 @@ return moveToColumn(cm, repeat); }, moveToEol: function(cm, motionArgs, vim) { - var cursor = cm.getCursor(); - var line = Math.min(cursor.line + motionArgs.repeat - 1, - cm.lineCount() - 1); + var cur = cm.getCursor(); vim.lastHPos = Infinity; - return { line: line, ch: cm.getLine(line).length - 1 }; + return clipCursorToContent(cm, { line: cur.line + motionArgs.repeat - 1, + ch: Infinity }); }, moveToFirstNonWhiteSpaceCharacter: function(cm) { // Go to the start of the line where the text begins, or the end for @@ -835,8 +823,7 @@ var cursor = cm.getCursor(); var line = cm.getLine(cursor.line); return { line: cursor.line, - ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)), - user: true }; + ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)) }; }, moveToMatchedSymbol: function(cm, motionArgs) { var cursor = cm.getCursor(); @@ -854,13 +841,10 @@ moveToLineOrEdgeOfDocument: function(cm, motionArgs) { var lineNum = motionArgs.forward ? cm.lineCount() - 1 : 0; if (motionArgs.repeatIsExplicit) { - lineNum = Math.max(0, Math.min( - motionArgs.repeat - 1, - cm.lineCount() - 1)); + lineNum = motionArgs.repeat - 1; } - return { line: lineNum, - ch: findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)), - user: true }; + return clipCursorToContent(cm, { line: lineNum, + ch: findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)) }); }, textObjectManipulation: function(cm, motionArgs) { var character = motionArgs.selectedCharacter; @@ -974,16 +958,15 @@ vim.visualMode = true; if (vim.visualLine) { curStart.ch = 0; - curEnd = { - line: Math.min(curStart.line + repeat - 1, cm.lineCount()), + curEnd = clipCursorToContent(cm, { + line: curStart.line + repeat - 1, ch: lineLength(cm, curStart.line) - }; + }); } else { - curEnd = { + curEnd = clipCursorToContent(cm, { line: curStart.line, - ch: Math.min(curStart.ch + repeat, - lineLength(cm, curStart.line)) - }; + ch: curStart.ch + repeat + }); } // Make the initial selection. if (!actionArgs.repeatIsExplicit && !vim.visualLine) { @@ -1000,18 +983,17 @@ } }, joinLines: function(cm, actionArgs, vim) { + var curStart, curEnd; if (vim.visualMode) { - var curStart = cm.getCursor('anchor'); - var curEnd = cm.getCursor('head'); + curStart = cm.getCursor('anchor'); + curEnd = cm.getCursor('head'); curEnd.ch = lineLength(cm, curEnd.line) - 1; } else { // Repeat is the number of lines to join. Minimum 2 lines. var repeat = Math.max(actionArgs.repeat, 2); - var curStart = cm.getCursor(); - var lineNumEnd = Math.min(curStart.line + repeat - 1, - cm.lineCount() - 1); - var curEnd = { line: lineNumEnd, - ch: lineLength(cm, lineNumEnd) - 1 }; + curStart = cm.getCursor(); + curEnd = clipCursorToContent(cm, { line: curStart.line + repeat - 1, + ch: Infinity }); } var finalCh = 0; cm.operation(function() { @@ -1061,11 +1043,11 @@ var curPosFinal; var idx; if (linewise && actionArgs.after) { - curPosFinal = makeCursor(cur.line + 1, - findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1))); + curPosFinal = { line: cur.line + 1, + ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1)) }; } else if (linewise && !actionArgs.after) { - curPosFinal = makeCursor(cur.line, - findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line))); + curPosFinal = { line: cur.line, + ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)) }; } else if (!linewise && actionArgs.after) { idx = cm.indexFromPos(cur); curPosFinal = cm.posFromIndex(idx + text.length - 1); @@ -1142,6 +1124,18 @@ } }; + /* + * Below are miscellaneous utility functions used by vim.js + */ + + // Clips cursor @cur to ensure + // that 0 <= cur.ch < line length and + // 0 <= cur.line < total number of lines + function clipCursorToContent(cm, cur) { + var line = Math.min(Math.max(0, cur.line), cm.lineCount() - 1); + var ch = Math.min(Math.max(0, cur.ch), lineLength(cm, line) - 1); + return { line: line, ch: ch }; + } // Merge arguments in place, for overriding arguments. function mergeArgs(to, from) { for (var prop in from) { @@ -1159,12 +1153,9 @@ } return ret; } - function makeCursor(line, ch) { - return { line: line, ch: ch }; - }; function offsetCursor(cur, offsetLine, offsetCh) { return { line: cur.line + offsetLine, ch: cur.ch + offsetCh }; - }; + } function arrayEq(a1, a2) { if (a1.length != a2.length) return false; for (var i = 0; i < a1.length; i++) { @@ -1199,7 +1190,7 @@ }; } function copyCursor(cur) { - return { line: cur.line, ch: cur.ch, user: cur.user }; + return { line: cur.line, ch: cur.ch }; } function cursorEqual(cur1, cur2) { return cur1.ch == cur2.ch && cur1.line == cur2.line; @@ -1209,9 +1200,8 @@ return true; } else if (cur1.line == cur2.line && cur1.ch < cur2.ch) { return true; - } else { - return false; } + return false; } function lineLength(cm, lineNum) { return cm.getLine(lineNum).length; @@ -1239,7 +1229,7 @@ var lines = selection.split('\n'); if (lines.length > 1 && isWhiteSpaceString(lines.pop())) { curEnd.line--; - curEnd.ch = cm.getLine(curEnd.line).length; + curEnd.ch = lineLength(cm, curEnd.line); } } @@ -1259,15 +1249,15 @@ var cur = cm.getCursor(); var line = cm.getLine(cur.line); - var line_to_char = new String(line.substring(0, cur.ch)); + var line_to_char = line.substring(0, cur.ch); // TODO: Case when small word is matching symbols does not work right with // the current regexLastIndexOf check. var start = regexLastIndexOf(line_to_char, (!bigWord) ? /[^a-zA-Z0-9]/ : /\s/) + 1; var end = motions.moveByWords(cm, { repeat: 1, forward: true, wordEnd: true, bigWord: bigWord }); - end.ch += inclusive ? 1 : 0 ; - return {start: {line: cur.line, ch: start}, end: end }; + end.ch += inclusive ? 1 : 0; + return { start: { line: cur.line, ch: start }, end: end }; } /* @@ -1397,24 +1387,23 @@ function moveToCharacter(cm, repeat, forward, character) { var cur = cm.getCursor(); var start = cur.ch; + var idx; for (var i = 0; i < repeat; i ++) { var line = cm.getLine(cur.line); - var idx = charIdxInLine(start, line, character, forward, true); + idx = charIdxInLine(start, line, character, forward, true); if (idx == -1) { return cur; } start = idx; } - return { line: cm.getCursor().line, - ch: idx }; + return { line: cm.getCursor().line, ch: idx }; } function moveToColumn(cm, repeat) { - // repeat is always >= 1, so repeat - 1 alwasy corresponds + // repeat is always >= 1, so repeat - 1 always corresponds // to the column we want to go to. var line = cm.getCursor().line; - var ch = Math.min(cm.getLine(line).length - 1, repeat - 1); - return { line: line, ch: ch }; + return clipCursorToContent(cm, { line: line, ch: repeat - 1 }); } function charIdxInLine(start, line, character, forward, includeChar) { @@ -1445,60 +1434,42 @@ // Are we at the opening or closing char var forwards = inArray(symb, ['(', '[', '{']); - var reverseSymb = (function(sym) { - switch (sym) { - case '(' : return ')'; - case '[' : return ']'; - case '{' : return '}'; - case ')' : return '('; - case ']' : return '['; - case '}' : return '{'; - default : return null; - } - })(symb); + var reverseSymb = ({ + '(': ')', ')': '(', + '[': ']', ']': '[', + '{': '}', '}': '{'})[symb]; // Couldn't find a matching symbol, abort - if (!reverseSymb) return cur; - - // Tracking our imbalance in open/closing symbols. An opening symbol will - // be the first thing we pick up if moving forward, this isn't true moving - // backwards - var disBal = forwards ? 0 : 1; - - var currLine; - while (true) { - if (line == cur.line) { - // First pass, do some special stuff - currLine = forwards ? cm.getLine(line).substr(cur.ch).split('') : - cm.getLine(line).substr(0,cur.ch).split('').reverse(); - } else { - currLine = forwards ? cm.getLine(line).split('') : - cm.getLine(line).split('').reverse(); - } - - for (var index = 0; index < currLine.length; index++) { - if (currLine[index] == symb) { - disBal++; - } else if (currLine[index] == reverseSymb) { - disBal--; - } + if (!reverseSymb) { + return cur; + } - if (disBal === 0) { - if (forwards && cur.line == line) { - return { line: line, ch: index + cur.ch}; - } else if (forwards) { - return { line: line, ch: index}; - } else { - return {line: line, ch: currLine.length - index - 1 }; - } - } + // set our increment to move forward (+1) or backwards (-1) + // depending on which bracket we're matching + var increment = ({'(': 1, '{': 1, '[': 1})[symb] || -1; + var depth = 1, nextCh = symb, index = cur.ch, lineText = cm.getLine(line); + // Simple search for closing paren--just count openings and closings till + // we find our match + // TODO: use info from CodeMirror to ignore closing brackets in comments and + // quotes, etc. + while (nextCh && depth > 0) { + index += increment; + nextCh = lineText.charAt(index); + if (!nextCh) { + line += increment; + index = 0; + lineText = cm.getLine(line) || ''; + nextCh = lineText.charAt(index); + } + if (nextCh === symb) { + depth++; + } else if (nextCh === reverseSymb) { + depth--; } + } - if (forwards) { - line++; - } else { - line--; - } + if (nextCh) { + return { line: line, ch: index }; } return cur; } @@ -1511,7 +1482,7 @@ start.ch += inclusive ? 1 : 0; end.ch += inclusive ? 0 : 1; - return {start: start, end: end}; + return { start: start, end: end }; } function regexLastIndexOf(string, pattern, startIndex) { @@ -1531,8 +1502,7 @@ var cur = cm.getCursor(); var line = cm.getLine(cur.line); var chars = line.split(''); - var start = undefined; - var end = undefined; + var start, end, i, len; var firstIndex = chars.indexOf(symb); // the decision tree is to always look backwards for the beginning first, @@ -1554,7 +1524,7 @@ start = cur.ch + 1; // assign start to ahead of the cursor } else { // go backwards to find the start - for (var i = cur.ch; i > -1 && !start; i--) { + for (i = cur.ch; i > -1 && !start; i--) { if (chars[i] == symb) { start = i + 1; } @@ -1563,7 +1533,7 @@ // look forwards for the end symbol if (start && !end) { - for (var i = start, len = chars.length; i < len && !end; i++) { + for (i = start, len = chars.length; i < len && !end; i++) { if (chars[i] == symb) { end = i; } @@ -1582,8 +1552,8 @@ } return { - start: {line: cur.line, ch: start}, - end: {line: cur.line, ch: end} + start: { line: cur.line, ch: start }, + end: { line: cur.line, ch: end } }; } From ca179a409bc70d94dbdec6afc297c7a78bd5fd68 Mon Sep 17 00:00:00 2001 From: Jason Siefken Date: Mon, 3 Dec 2012 01:17:22 -0800 Subject: [PATCH 12/63] [vim] Clean up Register and RegisterController --- keymap/vim.js | 104 ++++++++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 48cc49f564..ab4e50faa3 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -387,8 +387,17 @@ return repeat; }; - function Register() { + /* + * Register stores information about copy and paste registers. Besides + * text, a register must store whether it is linewise (i.e., when it is + * pasted, should it insert itself into a new line, or should the text be + * inserted at the cursor position.) + */ + function Register(text, linewise) { this.clear(); + if (text) { + this.set(text, linewise); + } } Register.prototype = { set: function(text, linewise) { @@ -407,74 +416,72 @@ clear: function() { this.text = ''; this.linewise = false; - } + }, + toString: function() { return this.text; } }; + /* + * vim registers allow you to keep many independent copy and paste buffers. + * See http://usevim.com/2012/04/13/registers/ for an introduction. + * + * RegisterController keeps the state of all the registers. An initial + * state may be passed in. The unnamed register '"' will always be + * overridden. + */ function RegisterController(registers) { this.registers = registers; - this.lastUpdatedRegisterName = null; this.unamedRegister = registers['\"'] = new Register(); } RegisterController.prototype = { pushText: function(registerName, operator, text, linewise) { // Lowercase and uppercase registers refer to the same register. // Uppercase just means append. - var append = isUpperCase(registerName); var register = this.isValidRegister(registerName) ? this.getRegister(registerName) : null; - if (register && - registerName.toLowerCase() != this.lastUpdatedRegisterName) { - // Switched registers, clear the unamed register. - this.lastUpdatedRegisterName = registerName.toLowerCase(); - this.unamedRegister.set('', false); - } else { - this.lastUpdatedRegisterName = null; + // if no register/an invalid register was specified, things go to the + // default registers + if (!register) { + switch (operator) { + case 'yank': + // The 0 register contains the text from the most recent yank. + this.registers['0'] = new Register(text, linewise); + break; + case 'delete': + case 'change': + if (text.indexOf('\n') == -1) { + // Delete less than 1 line. Update the small delete register. + this.registers['-'] = new Register(text, linewise); + } else { + // Shift down the contents of the numbered registers and put the + // deleted text into register 1. + this._shiftNumericRegisters(); + this.registers['1'] = new Register(text, linewise); + } + break; + } + // Make sure the unnamed register is set to what just happened + this.unamedRegister.set(text, linewise); + return; } - // The unamed register always has the same value as the last used - // register. + + // If we've gotten to this point, we've actually specified a register + var append = isUpperCase(registerName); if (append) { - if (register) { - register.append(text, linewise); - } + register.append(text, linewise); + // The unamed register always has the same value as the last used + // register. this.unamedRegister.append(text, linewise); } else { - if (register) { - register.set(text, linewise); - } + register.set(text, linewise); this.unamedRegister.set(text, linewise); } - if (!register) { - // These only happen if no register was explicitly specified. - if (operator == 'yank') { - // The 0 register contains the text from the most recent yank. - this.getRegisterInternal('0').set(text, linewise); - } else if (operator == 'delete' || operator == 'change') { - if (text.indexOf('\n') == -1) { - // Delete less than 1 line. Update the small delete register. - this.getRegisterInternal('-').set(text, linewise); - } else { - // Shift down the contents of the numbered registers and put the - // deleted text into register 1. - for (var i = 9; i >= 2; i--) { - var from = this.getRegisterInternal('' + (i - 1)); - this.registers['' + i] = from; - } - this.registers['1'] = new Register(); - this.registers['1'].set(text, linewise); - } - } - } }, + // Gets the register named @name. If one of @name doesn't already exist, + // create it. If @name is invalid, return the unamedRegister. getRegister: function(name) { if (!this.isValidRegister(name)) { return this.unamedRegister; } - return this.getRegisterInternal(name); - }, - getRegisterInternal: function(name) { - if (!name) { - return null; - } name = name.toLowerCase(); if (!this.registers[name]) { this.registers[name] = new Register(); @@ -483,6 +490,11 @@ }, isValidRegister: function(name) { return name && inArray(name, validRegisters); + }, + _shiftNumericRegisters: function() { + for (var i = 9; i >= 2; i--) { + this.registers[i] = this.getRegister('' + (i - 1)); + } } }; From e9ad8ba82b8d69448130203bed984e4132979249 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Fri, 30 Nov 2012 23:57:40 -0500 Subject: [PATCH 13/63] [vim keymap] Implement vim style search --- demo/vim.html | 3 +- keymap/vim.js | 236 ++++++++++++++++++++++++++++++++++++++++++----- test/index.html | 1 + test/vim_test.js | 64 ++++++++++++- 4 files changed, 279 insertions(+), 25 deletions(-) diff --git a/demo/vim.html b/demo/vim.html index b3c0e194a6..2108b54616 100644 --- a/demo/vim.html +++ b/demo/vim.html @@ -5,9 +5,10 @@ CodeMirror: Vim bindings demo + + - diff --git a/keymap/vim.js b/keymap/vim.js index ab4e50faa3..6ce176dc9b 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -164,6 +164,8 @@ { keys: ['<'], type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }}, { keys: ['g', '~'], type: 'operator', operator: 'swapcase' }, + { keys: ['n'], type: 'motion', motion: 'findNext' }, + { keys: ['N'], type: 'motion', motion: 'findPrev' }, // Operator-Motion dual commands { keys: ['x'], type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, @@ -209,12 +211,22 @@ { keys: ['Ctrl-r'], type: 'action', action: 'redo' }, { keys: ['m', 'character'], type: 'action', action: 'setMark' }, { keys: ['\"', 'character'], type: 'action', action: 'setRegister' }, + { keys: [',', '/'], type: 'action', action: 'clearSearch' }, // Text object motions { keys: ['a', 'character'], type: 'motion', motion: 'textObjectManipulation' }, { keys: ['i', 'character'], type: 'motion', motion: 'textObjectManipulation', - motionArgs: { textObjectInner: true }} + motionArgs: { textObjectInner: true }}, + // Search + { keys: ['/'], type: 'search', + searchArgs: { forward: true, querySrc: 'prompt' }}, + { keys: ['?'], type: 'search', + searchArgs: { forward: false, querySrc: 'prompt' }}, + { keys: ['*'], type: 'search', + searchArgs: { forward: true, querySrc: 'wordUnderCursor' }}, + { keys: ['#'], type: 'search', + searchArgs: { forward: false, querySrc: 'wordUnderCursor' }} ]; var Vim = function() { @@ -277,6 +289,29 @@ } return false; } + function getVimState(cm) { + if (!cm.vimState) { + // Store instance state in the CodeMirror object. + cm.vimState = { + inputState: new InputState(), + // When using jk for navigation, if you move from a longer line to a + // shorter line, the cursor may clip to the end of the shorter line. + // If j is pressed again and cursor goes to the next line, the + // cursor should go back to its horizontal position on the longer + // line if it can. This is to keep track of the horizontal position. + lastHPos: -1, + // The last motion command run. Cleared if a non-motion command gets + // executed in between. + lastMotion: null, + marks: {}, + registerController: new RegisterController({}), + visualMode: false, + // If we are in visual line mode. No effect if visualMode is false. + visualLine: false + }; + } + return cm.vimState; + } var vimApi= { addKeyMap: function() { @@ -289,33 +324,13 @@ // Initializes vim state variable on the CodeMirror object. Should only be // called lazily by handleKey or for testing. maybeInitState: function(cm) { - if (!cm.vimState) { - // Store instance state in the CodeMirror object. - cm.vimState = { - inputState: new InputState(), - // When using jk for navigation, if you move from a longer line to a - // shorter line, the cursor may clip to the end of the shorter line. - // If j is pressed again and cursor goes to the next line, the - // cursor should go back to its horizontal position on the longer - // line if it can. This is to keep track of the horizontal position. - lastHPos: -1, - // The last motion command run. Cleared if a non-motion command gets - // executed in between. - lastMotion: null, - marks: {}, - registerController: new RegisterController({}), - visualMode: false, - // If we are in visual line mode. No effect if visualMode is false. - visualLine: false - }; - } + getVimState(cm); }, // This is the outermost function called by CodeMirror, after keys have // been mapped to their Vim equivalents. handleKey: function(cm, key) { var command; - this.maybeInitState(cm); - var vim = cm.vimState; + var vim = getVimState(cm); if (key == 'Esc') { // Clear input state and get back to normal mode. vim.inputState.reset(); @@ -544,6 +559,9 @@ case 'action': this.processAction(cm, vim, command); break; + case 'search': + this.processSearch(cm, vim, command); + break; default: break; } @@ -614,6 +632,36 @@ vim.lastMotion = null, actions[command.action](cm, actionArgs, vim); }, + processSearch: function(cm, vim, command) { + if (!cm.getSearchCursor) { + // Search depends on SearchCursor. + return; + } + var forward = command.searchArgs.forward; + getSearchState(cm).reversed = !forward; + var promptPrefix = (forward) ? '/' : '?'; + var handleQuery = function(query) { + updateSearchQuery(cm, query); + commandDispatcher.processMotion(cm, vim, { + type: 'motion', + motion: 'findNext' + }); + } + switch (command.searchArgs.querySrc) { + case 'prompt': + showPrompt(cm, handleQuery, promptPrefix, searchPromptDesc); + break; + case 'wordUnderCursor': + var word = expandToWord(cm, false /** inclusive */, + true /** forward */, false /** bigWord */); + var query = cm.getLine(word.start.line).substring(word.start.ch, + word.end.ch + 1); + // Use \b (word boundary) to simulate \< \> in Vim. + query = '\\b' + query + '\\b'; + handleQuery(query); + break; + } + }, evalInput: function(cm, vim) { // If the motion comand is set, execute both the operator and motion. // Otherwise return. @@ -753,6 +801,12 @@ return clipCursorToContent(cm, { line: cur.line + motionArgs.repeat - 1, ch: Infinity }); }, + findNext: function(cm, motionArgs, vim) { + return findNext(cm, false /** prev */, motionArgs.repeat); + }, + findPrev: function(cm, motionArgs, vim) { + return findNext(cm, true /** prev */, motionArgs.repeat); + }, goToMark: function(cm, motionArgs, vim) { var mark = vim.marks[motionArgs.selectedCharacter]; if (mark) { @@ -949,6 +1003,7 @@ }; var actions = { + clearSearch: clearSearch, enterInsertMode: function(cm, actionArgs) { var insertAt = (actionArgs) ? actionArgs.insertAt : null; if (insertAt == 'eol') { @@ -1569,6 +1624,141 @@ }; } + // Search functions + function SearchState() { + this.query = null; + this.marked = []; + this.reversed = false; + } + function getSearchState(cm) { + var vim = getVimState(cm); + return vim._searchState || (vim._searchState = new SearchState()); + } + function dialog(cm, text, shortText, callback) { + if (cm.openDialog) { + cm.openDialog(text, callback); + } + else { + callback(prompt(shortText, "")); + } + } + function parseQuery(cm, query) { + // First try to extract regex + flags from the input. If no flags found, + // extract just the regex. IE does not accept flags directly defined in the + // regex string in the form /regex/flags + var match = query.match(/^(.*)\/(.*)$/); + var insensitive = false; + var regexp; + if (match) { + insensitive = (match[2].indexOf('i') != -1); + regexp = match[1]; + } else { + regexp = query; + } + // Heuristic: if the query string is all lowercase, do a case-insensitive + // search. + insensitive |= (/^[^A-Z]*$/).test(regexp); + var query; + try { + query = new RegExp(regexp, insensitive ? 'i' : undefined); + return query; + } catch (e) { + showConfirm(cm, 'Invalid regex: ' + regexp); + } + } + function showConfirm(cm, text) { + if (cm.openConfirm) { + cm.openConfirm('' + text + + ' ', function() {}); + } else { + alert(text); + } + } + function makePrompt(prefix, desc) { + var raw = ''; + if (prefix) { + raw += prefix; + } + raw += ' ' + + ''; + if (desc) { + raw += ''; + raw += desc; + raw += '' + } + return raw; + } + var searchPromptDesc = '(Javascript regexp)'; + function showPrompt(cm, onPromptClose, prefix, desc) { + var shortText = (prefix || '') + ' ' + (desc || ''); + dialog(cm, makePrompt(prefix, desc), shortText, onPromptClose); + } + function regexEqual(r1, r2) { + if (r1 instanceof RegExp && r2 instanceof RegExp) { + var props = ["global", "multiline", "ignoreCase", "source"]; + for (var i = 0; i < props.length; i++) { + var prop = props[i]; + if (r1[prop] !== r2[prop]) { + return(false); + } + } + return(true); + } + return(false); + } + function updateSearchQuery(cm, query) { + cm.operation(function() { + var state = getSearchState(cm); + if (!query) return; + var newQuery = parseQuery(cm, query); + if (regexEqual(newQuery, state.query)) return; + clearSearch(cm); + state.query = newQuery; + if (!state.query) return; + if (cm.lineCount() < 2000) { // This is too expensive on big documents. + for (var cursor = cm.getSearchCursor(state.query); cursor.findNext();) + state.marked.push(cm.markText(cursor.from(), cursor.to(), + 'CodeMirror-searching')); + } + }); + } + function findNext(cm, prev, repeat) { + return cm.operation(function() { + var state = getSearchState(cm); + if (!state.query) { + return; + } + var pos = cm.getCursor(); + // If search is initiated with ? instead of /, negate direction. + prev = (state.reversed) ? !prev : prev; + if (!prev) { + pos.ch += 1; + } + var cursor = cm.getSearchCursor(state.query, pos); + for (var i = 0; i < repeat; i++) { + if (!cursor.find(prev)) { + // SearchCursor may have returned null because it hit EOF, wrap + // around and try again. + cursor = cm.getSearchCursor(state.query, + (prev) ? { line: cm.lineCount() - 1} : {line: 0, ch: 0} ); + if (!cursor.find(prev)) return; + } + } + return cursor.from(); + });} + function clearSearch(cm) { + cm.operation(function() { + var state = getSearchState(cm); + if (!state.query) { + return; + } + state.query = null; + for (var i = 0; i < state.marked.length; ++i) { + state.marked[i].clear(); + } + state.marked.length = 0; + });} + function buildVimKeyMap() { /** * Handle the raw key event from CodeMirror. Translate the diff --git a/test/index.html b/test/index.html index 7261ec001d..5e3641aefe 100644 --- a/test/index.html +++ b/test/index.html @@ -8,6 +8,7 @@ + diff --git a/test/vim_test.js b/test/vim_test.js index d03255ec3f..19b5543555 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -116,9 +116,15 @@ function testVim(name, run, opts, expectedFail) { eqPos(pos, cm.getCursor()); } } + function fakeOpenDialog(result) { + return function(text, callback) { + return callback(result); + } + } var helpers = { doKeys: doKeysFn(cm), - assertCursorAt: assertCursorAtFn(cm) + assertCursorAt: assertCursorAtFn(cm), + fakeOpenDialog: fakeOpenDialog } var successful = false; try { @@ -674,3 +680,59 @@ testVim('r', function(cm, vim, helpers) { eq('wuuuet', cm.getValue()); eqPos(makeCursor(0, 3), cm.getCursor()); }, { value: 'wordet' }); +testVim('/ and n/N', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('match'); + helpers.doKeys('/'); + helpers.assertCursorAt(makeCursor(0, 11)); + helpers.doKeys('n'); + helpers.assertCursorAt(makeCursor(1, 6)); + helpers.doKeys('N'); + helpers.assertCursorAt(makeCursor(0, 11)); + + cm.setCursor(0, 0); + helpers.doKeys('2', '/'); + helpers.assertCursorAt(makeCursor(1, 6)); +}, { value: 'match nope match \n nope Match' }); +testVim('/_case', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('Match'); + helpers.doKeys('/'); + helpers.assertCursorAt(makeCursor(1, 6)); +}, { value: 'match nope match \n nope Match' }); +testVim('? and n/N', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('match'); + helpers.doKeys('?'); + helpers.assertCursorAt(makeCursor(1, 6)); + helpers.doKeys('n'); + helpers.assertCursorAt(makeCursor(0, 11)); + helpers.doKeys('N'); + helpers.assertCursorAt(makeCursor(1, 6)); + + cm.setCursor(0, 0); + helpers.doKeys('2', '?'); + helpers.assertCursorAt(makeCursor(0, 11)); +}, { value: 'match nope match \n nope Match' }); +testVim(',/ clearSearch', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('match'); + helpers.doKeys('?'); + var origPos = cm.getCursor(); + helpers.doKeys(',', '/', 'n'); + helpers.assertCursorAt(origPos); +}, { value: 'match nope match \n nope Match' }); +testVim('*', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('*'); + helpers.assertCursorAt(makeCursor(0, 22)); + + cm.setCursor(0, 9); + helpers.doKeys('2', '*'); + helpers.assertCursorAt(makeCursor(1, 8)); +}, { value: 'nomatch match nomatch match \nnomatch Match' }); +testVim('#', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('#'); + helpers.assertCursorAt(makeCursor(1, 8)); + + cm.setCursor(0, 9); + helpers.doKeys('2', '#'); + helpers.assertCursorAt(makeCursor(0, 22)); +}, { value: 'nomatch match nomatch match \nnomatch Match' }); From 154613dcd56bb8a5bd8d2015c687f7102ec78131 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Mon, 3 Dec 2012 00:52:24 -0500 Subject: [PATCH 14/63] [vim keymap] Add indentation for newlines from o/O Use newlineAndIndentContinueComment by default, falling back to newlineAndIndent if the former command is not defined. Same behavior is made default for Enter key in insert mode. --- demo/vim.html | 1 + keymap/vim.js | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/demo/vim.html b/demo/vim.html index 2108b54616..053cfd7ab7 100644 --- a/demo/vim.html +++ b/demo/vim.html @@ -5,6 +5,7 @@ CodeMirror: Vim bindings demo + diff --git a/keymap/vim.js b/keymap/vim.js index 6ce176dc9b..d97caae9f5 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1078,10 +1078,19 @@ }, newLineAndEnterInsertMode: function(cm, actionArgs) { var insertAt = cm.getCursor(); - insertAt.ch = 0; - insertAt.line = (actionArgs.after) ? insertAt.line + 1 : insertAt.line; - cm.replaceRange('\n', insertAt); - cm.setCursor(insertAt); + if (insertAt.line == 0 && !actionArgs.after) { + // Special case for inserting newline before start of document. + cm.replaceRange('\n', makeCursor(0, 0)); + cm.setCursor(0, 0); + } else { + insertAt.line = (actionArgs.after) ? insertAt.line : + insertAt.line - 1; + insertAt.ch = lineLength(cm, insertAt.line); + cm.setCursor(insertAt); + var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment || + CodeMirror.commands.newlineAndIndent; + newlineFn(cm); + } this.enterInsertMode(cm); }, paste: function(cm, actionArgs, vim) { @@ -1837,6 +1846,11 @@ 'Ctrl-C': exitInsertMode, 'Ctrl-N': 'autocomplete', 'Ctrl-P': 'autocomplete', + 'Enter': function(cm) { + var fn = CodeMirror.commands.newlineAndIndentContinueComment || + CodeMirror.commands.newlineAndIndent; + fn(cm); + }, fallthrough: ['default'] }; From 48046d7b58faa5b413af398757ef1f37ddcc7394 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Fri, 30 Nov 2012 23:57:40 -0500 Subject: [PATCH 15/63] [vim keymap] Implement vim style search --- demo/vim.html | 1 - 1 file changed, 1 deletion(-) diff --git a/demo/vim.html b/demo/vim.html index 053cfd7ab7..2108b54616 100644 --- a/demo/vim.html +++ b/demo/vim.html @@ -5,7 +5,6 @@ CodeMirror: Vim bindings demo - From de88672d96903e450f124e112724f13cb3b9f5c7 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 3 Dec 2012 10:52:36 +0100 Subject: [PATCH 16/63] [css mode] Handle unquoted url(...) values Closes #1026 --- mode/css/css.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/mode/css/css.js b/mode/css/css.js index 87d5d7401e..e1fc70b0cb 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -237,6 +237,11 @@ CodeMirror.defineMode("css", function(config) { else if (/[;{}\[\]\(\)]/.test(ch)) { return ret(null, ch); } + else if (ch == "u" && stream.match("rl(")) { + stream.backUp(1); + state.tokenize = tokenParenthesized; + return ret("property", "variable"); + } else { stream.eatWhile(/[\w\\\-]/); return ret("property", "variable"); @@ -267,7 +272,7 @@ CodeMirror.defineMode("css", function(config) { return ret("comment", "comment"); } - function tokenString(quote) { + function tokenString(quote, nonInclusive) { return function(stream, state) { var escaped = false, ch; while ((ch = stream.next()) != null) { @@ -275,11 +280,23 @@ CodeMirror.defineMode("css", function(config) { break; escaped = !escaped && ch == "\\"; } - if (!escaped) state.tokenize = tokenBase; + if (!escaped) { + if (nonInclusive) stream.backUp(1); + state.tokenize = tokenBase; + } return ret("string", "string"); }; } + function tokenParenthesized(stream, state) { + stream.next(); // Must be '(' + if (!stream.match(/\s*[\"\']/, false)) + state.tokenize = tokenString(")", true); + else + state.tokenize = tokenBase; + return ret(null, "("); + } + return { startState: function(base) { return {tokenize: tokenBase, From 0e7912a02673f5ec90e5a847f12891d28cfd4587 Mon Sep 17 00:00:00 2001 From: Tony Jian Date: Sun, 2 Dec 2012 22:34:37 +0800 Subject: [PATCH 17/63] add continue-list util for markdown mode --- lib/util/continuelist.js | 29 +++++++++++++++++++++++++++++ mode/markdown/index.html | 4 +++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 lib/util/continuelist.js diff --git a/lib/util/continuelist.js b/lib/util/continuelist.js new file mode 100644 index 0000000000..8a2b8653e2 --- /dev/null +++ b/lib/util/continuelist.js @@ -0,0 +1,29 @@ +(function() { + CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) { + var pos = cm.getCursor(), token = cm.getTokenAt(pos); + var mode = CodeMirror.innerMode(cm.getMode(), token.state).mode; + var space; + if (token.className == "string") { + var full = cm.getRange({line: pos.line, ch: 0}, {line: pos.line, ch: token.end}); + var listStart = /\*|\d+\./, listContinue; + if (token.string.search(listStart) == 0) { + var reg = /^[\W]*(\d+)\./g; + var matches = reg.exec(full); + if(matches) + listContinue = (parseInt(matches[1]) + 1) + ". "; + else + listContinue = "* "; + space = full.slice(0, token.start); + if (!/^\s*$/.test(space)) { + space = ""; + for (var i = 0; i < token.start; ++i) space += " "; + } + } + } + + if (space != null) + cm.replaceSelection("\n" + space + listContinue, "end"); + else + cm.execCommand("newlineAndIndent"); + }; +})(); diff --git a/mode/markdown/index.html b/mode/markdown/index.html index ca970f871e..5d7452f1e8 100644 --- a/mode/markdown/index.html +++ b/mode/markdown/index.html @@ -5,6 +5,7 @@ CodeMirror: Markdown mode + @@ -328,7 +329,8 @@

    CodeMirror: Markdown mode

    var editor = CodeMirror.fromTextArea(document.getElementById("code"), { mode: 'markdown', lineNumbers: true, - theme: "default" + theme: "default", + extraKeys: {"Enter": "newlineAndIndentContinueMarkdownList"} }); From 8c349de6caa3a6db4ed378aad5220d8b0ee37ed8 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 3 Dec 2012 11:39:00 +0100 Subject: [PATCH 18/63] Add Graphit to real-world uses --- doc/realworld.html | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/realworld.html b/doc/realworld.html index b5d2daa98f..2f0ab91634 100644 --- a/doc/realworld.html +++ b/doc/realworld.html @@ -42,6 +42,7 @@

    { } CodeMi
  • Go language tour
  • GitHub's Android app
  • Google Apps Script
  • +
  • Graphit (function graphing)
  • Haxe (Haxe Playground)
  • HaxPad (editor for Win RT)
  • Histone template engine playground
  • From 6361b3a9d6a6102bb10125f8e7da721c97a282f2 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 3 Dec 2012 11:41:22 +0100 Subject: [PATCH 19/63] Remove unused variable --- lib/util/continuelist.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/util/continuelist.js b/lib/util/continuelist.js index 8a2b8653e2..33b343bb53 100644 --- a/lib/util/continuelist.js +++ b/lib/util/continuelist.js @@ -1,7 +1,6 @@ (function() { CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) { var pos = cm.getCursor(), token = cm.getTokenAt(pos); - var mode = CodeMirror.innerMode(cm.getMode(), token.state).mode; var space; if (token.className == "string") { var full = cm.getRange({line: pos.line, ch: 0}, {line: pos.line, ch: token.end}); From b7c968bdd5fdfb40998c2e8ef86512c110f58355 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 3 Dec 2012 15:17:37 +0100 Subject: [PATCH 20/63] In mouse handler, don't assume document doesn't change between mousedown and mouseup --- lib/codemirror.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 9ab41a86f6..73c9a2867e 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1398,14 +1398,19 @@ window.CodeMirror = (function() { return; } e_preventDefault(e); - if (type == "single") extendSelection(cm, start); + if (type == "single") extendSelection(cm, clipPos(doc, start)); var startstart = sel.from, startend = sel.to; function doSelect(cur) { if (type == "single") { - extendSelection(cm, start, cur); - } else if (type == "double") { + extendSelection(cm, clipPos(doc, start), cur); + return; + } + + startstart = clipPos(doc, startstart); + startend = clipPos(doc, startend); + if (type == "double") { var word = findWordAt(getLine(doc, cur.line).text, cur); if (posLess(cur, startstart)) extendSelection(cm, word.from, startend); else extendSelection(cm, startstart, word.to); From 994b28e83df5a185ad1010f53e0f56979d489727 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Mon, 3 Dec 2012 21:03:39 -0500 Subject: [PATCH 21/63] Clean up to meet JSHint compliance. Various other style fixes. Updates to comments. --- keymap/vim.js | 126 +++++++++++++++++++++++++++----------------------- 1 file changed, 69 insertions(+), 57 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index d97caae9f5..cf8a1a02b8 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1,5 +1,3 @@ -// Reimplementation of vim keybindings -// word1 /** * Supported keybindings: * @@ -50,7 +48,6 @@ * 6. Motion, operator, and action implementations * 7. Helper functions for the key handler, motions, operators, and actions * 8. Set up Vim to work as a keymap for CodeMirror. - * */ (function() { @@ -285,7 +282,9 @@ } function inArray(val, arr) { for (var i = 0; i < arr.length; i++) { - if (arr[i] == val) { return true; } + if (arr[i] == val) { + return true; + } } return false; } @@ -640,7 +639,7 @@ var forward = command.searchArgs.forward; getSearchState(cm).reversed = !forward; var promptPrefix = (forward) ? '/' : '?'; - var handleQuery = function(query) { + function handleQuery(query) { updateSearchQuery(cm, query); commandDispatcher.processMotion(cm, vim, { type: 'motion', @@ -795,8 +794,8 @@ // All of the functions below return Cursor objects. var motions = { expandToLine: function(cm, motionArgs) { - // Expands forward to end of line, and then to next line if repeat is > 1. - // Does not handle backward motion! + // Expands forward to end of line, and then to next line if repeat is + // >1. Does not handle backward motion! var cur = cm.getCursor(); return clipCursorToContent(cm, { line: cur.line + motionArgs.repeat - 1, ch: Infinity }); @@ -843,10 +842,8 @@ }, moveByPage: function(cm, motionArgs) { // CodeMirror only exposes functions that move the cursor page down, so - // doing this bad hack to move the cursor and move it back. evalInput will - // move the cursor to where it should be in the end. - // TODO: Consider making motions move the cursor by default, so as to not - // need this ugliness. But it might make visual mode hard. + // doing this bad hack to move the cursor and move it back. evalInput + // will move the cursor to where it should be in the end. var curStart = cm.getCursor(); var repeat = motionArgs.repeat; cm.moveV((motionArgs.forward ? repeat : -repeat), 'page'); @@ -889,7 +886,7 @@ var cursor = cm.getCursor(); var line = cm.getLine(cursor.line); return { line: cursor.line, - ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)) }; + ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)) }; }, moveToMatchedSymbol: function(cm, motionArgs) { var cursor = cm.getCursor(); @@ -1031,8 +1028,8 @@ }); } else { curEnd = clipCursorToContent(cm, { - line: curStart.line, - ch: curStart.ch + repeat + line: curStart.line, + ch: curStart.ch + repeat }); } // Make the initial selection. @@ -1060,7 +1057,7 @@ var repeat = Math.max(actionArgs.repeat, 2); curStart = cm.getCursor(); curEnd = clipCursorToContent(cm, { line: curStart.line + repeat - 1, - ch: Infinity }); + ch: Infinity }); } var finalCh = 0; cm.operation(function() { @@ -1078,9 +1075,9 @@ }, newLineAndEnterInsertMode: function(cm, actionArgs) { var insertAt = cm.getCursor(); - if (insertAt.line == 0 && !actionArgs.after) { + if (insertAt.line === 0 && !actionArgs.after) { // Special case for inserting newline before start of document. - cm.replaceRange('\n', makeCursor(0, 0)); + cm.replaceRange('\n', { line: 0, ch: 0 }); cm.setCursor(0, 0); } else { insertAt.line = (actionArgs.after) ? insertAt.line : @@ -1097,7 +1094,9 @@ var cur = cm.getCursor(); var register = vim.registerController.getRegister( actionArgs.registerName); - if (!register.text) { return; } + if (!register.text) { + return; + } for (var text = '', i = 0; i < actionArgs.repeat; i++) { text += register.text; } @@ -1233,7 +1232,9 @@ return { line: cur.line + offsetLine, ch: cur.ch + offsetCh }; } function arrayEq(a1, a2) { - if (a1.length != a2.length) return false; + if (a1.length != a2.length) { + return false; + } for (var i = 0; i < a1.length; i++) { if (a1[i] != a2[i]) { return false; @@ -1317,8 +1318,8 @@ } function findFirstNonWhiteSpaceCharacter(text) { - var firstNonWS = text.search(/\S/); - return firstNonWS == -1 ? text.length : firstNonWS; + var firstNonWS = text.search(/\S/); + return firstNonWS == -1 ? text.length : firstNonWS; } function expandToWord(cm, inclusive, forward, bigWord) { @@ -1337,14 +1338,15 @@ } /* - * Returns the boundaries of the next word. If the cursor in the middle of the - * word, then returns the boundaries of the current word, starting at the - * cursor. If the cursor is at the start/end of a word, and we are going + * Returns the boundaries of the next word. If the cursor in the middle of + * the word, then returns the boundaries of the current word, starting at + * the cursor. If the cursor is at the start/end of a word, and we are going * forward/backward, respectively, find the boundaries of the next word. * * @param {CodeMirror} cm CodeMirror object. * @param {Cursor} cur The cursor position. - * @param {boolean} forward True to search forward. False to search backward. + * @param {boolean} forward True to search forward. False to search + * backward. * @param {boolean} bigWord True if punctuation count as part of the word. * False if only [a-zA-Z0-9] characters count as part of the word. * @return {Object{from:number, to:number, line: number}} The boundaries of @@ -1379,9 +1381,9 @@ continue; } else { return { - from: Math.min(wordStart, wordEnd + 1), - to: Math.max(wordStart, wordEnd), - line: lineNum}; + from: Math.min(wordStart, wordEnd + 1), + to: Math.max(wordStart, wordEnd), + line: lineNum }; } } } @@ -1404,7 +1406,8 @@ /** * @param {CodeMirror} cm CodeMirror object. * @param {int} repeat Number of words to move past. - * @param {boolean} forward True to search forward. False to search backward. + * @param {boolean} forward True to search forward. False to search + * backward. * @param {boolean} wordEnd True to move to end of word. False to move to * beginning of word. * @param {boolean} bigWord True if punctuation count as part of the word. @@ -1421,8 +1424,9 @@ word = findWord(cm, cur, forward, bigWord); movedToNextWord = true; if (word) { - // Move to the word we just found. If by moving to the word we end up - // in the same spot, then move an extra character and search again. + // Move to the word we just found. If by moving to the word we end + // up in the same spot, then move an extra character and search + // again. cur.line = word.line; if (forward && wordEnd) { // 'e' @@ -1526,8 +1530,8 @@ var depth = 1, nextCh = symb, index = cur.ch, lineText = cm.getLine(line); // Simple search for closing paren--just count openings and closings till // we find our match - // TODO: use info from CodeMirror to ignore closing brackets in comments and - // quotes, etc. + // TODO: use info from CodeMirror to ignore closing brackets in comments + // and quotes, etc. while (nextCh && depth > 0) { index += increment; nextCh = lineText.charAt(index); @@ -1538,9 +1542,9 @@ nextCh = lineText.charAt(index); } if (nextCh === symb) { - depth++; + depth++; } else if (nextCh === reverseSymb) { - depth--; + depth--; } } @@ -1571,8 +1575,8 @@ return -1; } - // takes in a symbol and a cursor and tries to simulate text objects that have - // identical opening and closing symbols + // Takes in a symbol and a cursor and tries to simulate text objects that + // have identical opening and closing symbols // TODO support across multiple lines function findBeginningAndEnd(cm, symb, inclusive) { var cur = cm.getCursor(); @@ -1617,10 +1621,9 @@ } // nothing found - // FIXME still enters insert mode - if (!start || !end) return { - start: cur, end: cur - }; + if (!start || !end) { + return { start: cur, end: cur }; + } // include the symbols if (inclusive) { @@ -1653,26 +1656,25 @@ } function parseQuery(cm, query) { // First try to extract regex + flags from the input. If no flags found, - // extract just the regex. IE does not accept flags directly defined in the - // regex string in the form /regex/flags + // extract just the regex. IE does not accept flags directly defined in + // the regex string in the form /regex/flags var match = query.match(/^(.*)\/(.*)$/); var insensitive = false; - var regexp; + var query_regex; if (match) { insensitive = (match[2].indexOf('i') != -1); - regexp = match[1]; + query_regex = match[1]; } else { - regexp = query; + query_regex = query; } // Heuristic: if the query string is all lowercase, do a case-insensitive // search. - insensitive |= (/^[^A-Z]*$/).test(regexp); - var query; + insensitive = insensitive || (/^[^A-Z]*$/).test(query_regex); try { - query = new RegExp(regexp, insensitive ? 'i' : undefined); - return query; + var regexp = new RegExp(query_regex, insensitive ? 'i' : undefined); + return regexp; } catch (e) { - showConfirm(cm, 'Invalid regex: ' + regexp); + showConfirm(cm, 'Invalid regex: ' + query_regex); } } function showConfirm(cm, text) { @@ -1693,7 +1695,7 @@ if (desc) { raw += ''; raw += desc; - raw += '' + raw += ''; } return raw; } @@ -1718,16 +1720,24 @@ function updateSearchQuery(cm, query) { cm.operation(function() { var state = getSearchState(cm); - if (!query) return; + if (!query) { + return; + } var newQuery = parseQuery(cm, query); - if (regexEqual(newQuery, state.query)) return; + if (regexEqual(newQuery, state.query)) { + return; + } clearSearch(cm); state.query = newQuery; - if (!state.query) return; + if (!state.query) { + return; + } if (cm.lineCount() < 2000) { // This is too expensive on big documents. - for (var cursor = cm.getSearchCursor(state.query); cursor.findNext();) + for (var cursor = cm.getSearchCursor(state.query); + cursor.findNext();) { state.marked.push(cm.markText(cursor.from(), cursor.to(), 'CodeMirror-searching')); + } } }); } @@ -1750,7 +1760,9 @@ // around and try again. cursor = cm.getSearchCursor(state.query, (prev) ? { line: cm.lineCount() - 1} : {line: 0, ch: 0} ); - if (!cursor.find(prev)) return; + if (!cursor.find(prev)) { + return; + } } } return cursor.from(); From bfd2465baffceb9cdc944a278205b704f50dc4e7 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Mon, 3 Dec 2012 22:26:34 -0500 Subject: [PATCH 22/63] [vim keymap] Rewrite expandWordUnderCursor. --- keymap/vim.js | 74 +++++++++++++++++++++++++++++++++++++----------- test/vim_test.js | 12 ++++++++ 2 files changed, 70 insertions(+), 16 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index cf8a1a02b8..b8c5ac3794 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -269,7 +269,7 @@ return (/^[A-Z]$/).test(k); } function isAlphanumeric(k) { - return (/^[a-zA-Z0-9]/).test(k); + return (/^[\w]$/).test(k); } function isWhiteSpace(k) { return whiteSpaceRegex.test(k); @@ -651,12 +651,13 @@ showPrompt(cm, handleQuery, promptPrefix, searchPromptDesc); break; case 'wordUnderCursor': - var word = expandToWord(cm, false /** inclusive */, - true /** forward */, false /** bigWord */); + var word = expandWordUnderCursor(cm, false /** inclusive */, + true /** forward */, false /** bigWord */, + true /** noSymbol */); var query = cm.getLine(word.start.line).substring(word.start.ch, word.end.ch + 1); - // Use \b (word boundary) to simulate \< \> in Vim. query = '\\b' + query + '\\b'; + cm.setCursor(word.start); handleQuery(query); break; } @@ -1175,11 +1176,11 @@ // TODO: implement text objects for the reverse like }. Should just be // an additional mapping after moving to the defaultKeyMap. 'w': function(cm, inclusive) { - return expandToWord(cm, inclusive, true /** forward */, + return expandWordUnderCursor(cm, inclusive, true /** forward */, false /** bigWord */); }, 'W': function(cm, inclusive) { - return expandToWord(cm, inclusive, + return expandWordUnderCursor(cm, inclusive, true /** forward */, true /** bigWord */); }, '{': function(cm, inclusive) { @@ -1283,6 +1284,12 @@ function lineLength(cm, lineNum) { return cm.getLine(lineNum).length; } + function reverse(s){ + return s.split("").reverse().join(""); + } + function escapeRegex(s) { + return s.replace(/([.?*+\^$\[\]\\(){}|\-])/g, "\\$1"); + } function exitVisualMode(cm, vim) { vim.visualMode = false; @@ -1322,19 +1329,54 @@ return firstNonWS == -1 ? text.length : firstNonWS; } - function expandToWord(cm, inclusive, forward, bigWord) { + function expandWordUnderCursor(cm, inclusive, forward, bigWord, noSymbol) { var cur = cm.getCursor(); var line = cm.getLine(cur.line); + var idx = cur.ch; + + // Seek to first word or non-whitespace character, depending on if + // noSymbol is true. + var textAfterIdx = line.substring(idx); + var firstMatchedChar; + if (noSymbol) { + firstMatchedChar = textAfterIdx.search(/\w/); + } else { + firstMatchedChar = textAfterIdx.search(/\S/); + } + if (firstMatchedChar == -1) { + return null; + } + idx += firstMatchedChar; + textAfterIdx = line.substring(idx); + var textBeforeIdx = line.substring(0, idx); + + var matchRegex; + // Greedy matchers for the "word" we are trying to expand. + if (bigWord) { + matchRegex = /^\S+/; + } else { + if ((/\w/).test(line.charAt(idx))) { + matchRegex = /^\w+/; + } else { + matchRegex = /^[^\w\s]+/; + } + } + + var wordAfterRegex = matchRegex.exec(textAfterIdx); + var wordStart = idx; + var wordEnd = idx + wordAfterRegex[0].length - 1; + // TODO: Find a better way to do this. It will be slow on very long lines. + var wordBeforeRegex = matchRegex.exec(reverse(textBeforeIdx)); + if (wordBeforeRegex) { + wordStart -= wordBeforeRegex[0].length; + } + + if (inclusive) { + wordEnd++; + } - var line_to_char = line.substring(0, cur.ch); - // TODO: Case when small word is matching symbols does not work right with - // the current regexLastIndexOf check. - var start = regexLastIndexOf(line_to_char, - (!bigWord) ? /[^a-zA-Z0-9]/ : /\s/) + 1; - var end = motions.moveByWords(cm, { repeat: 1, forward: true, - wordEnd: true, bigWord: bigWord }); - end.ch += inclusive ? 1 : 0; - return { start: { line: cur.line, ch: start }, end: end }; + return { start: { line: cur.line, ch: wordStart }, + end: { line: cur.line, ch: wordEnd }}; } /* diff --git a/test/vim_test.js b/test/vim_test.js index 19b5543555..2d7067ee22 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -736,3 +736,15 @@ testVim('#', function(cm, vim, helpers) { helpers.doKeys('2', '#'); helpers.assertCursorAt(makeCursor(0, 22)); }, { value: 'nomatch match nomatch match \nnomatch Match' }); +testVim('*_seek', function(cm, vim, helpers) { + // Should skip over space and symbols. + cm.setCursor(0, 3); + helpers.doKeys('*'); + helpers.assertCursorAt(makeCursor(0, 22)); +}, { value: ' := match nomatch match \nnomatch Match' }); +testVim('#', function(cm, vim, helpers) { + // Should skip over space and symbols. + cm.setCursor(0, 3); + helpers.doKeys('#'); + helpers.assertCursorAt(makeCursor(1, 8)); +}, { value: ' := match nomatch match \nnomatch Match' }); From 9f59a5338b82872cc2386ad33ad6e2ffee45aef5 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Mon, 3 Dec 2012 22:49:48 -0500 Subject: [PATCH 23/63] [vim keymap] Fix a,x,s,d/y/c l at end of line --- keymap/vim.js | 20 ++++++++++---------- test/vim_test.js | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index b8c5ac3794..9cae1ffc58 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -184,8 +184,7 @@ motion: 'moveByCharacters', motionArgs: { forward: true }}, // Actions { keys: ['a'], type: 'action', action: 'enterInsertMode', - motion: 'moveByCharacters', - motionArgs: { forward: true, noRepeat: true }}, + actionArgs: { insertAt: 'charAfter' }}, { keys: ['A'], type: 'action', action: 'enterInsertMode', actionArgs: { insertAt: 'eol' }}, { keys: ['i'], type: 'action', action: 'enterInsertMode' }, @@ -739,6 +738,7 @@ cm.setCursor(selectionStart); cm.setSelection(selectionStart, selectionEnd); } else if (!operator) { + curEnd = clipCursorToContent(cm, curEnd); cm.setCursor(curEnd.line, curEnd.ch); } } @@ -798,8 +798,7 @@ // Expands forward to end of line, and then to next line if repeat is // >1. Does not handle backward motion! var cur = cm.getCursor(); - return clipCursorToContent(cm, { line: cur.line + motionArgs.repeat - 1, - ch: Infinity }); + return { line: cur.line + motionArgs.repeat - 1, ch: Infinity }; }, findNext: function(cm, motionArgs, vim) { return findNext(cm, false /** prev */, motionArgs.repeat); @@ -818,7 +817,7 @@ var cur = cm.getCursor(); var repeat = motionArgs.repeat; var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat; - return clipCursorToContent(cm, { line: cur.line, ch: ch }); + return { line: cur.line, ch: ch }; }, moveByLines: function(cm, motionArgs, vim) { var endCh = cm.getCursor().ch; @@ -839,7 +838,7 @@ var cur = cm.getCursor(); var repeat = motionArgs.repeat; var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat; - return clipCursorToContent(cm, { line: line, ch: endCh }); + return { line: line, ch: endCh }; }, moveByPage: function(cm, motionArgs) { // CodeMirror only exposes functions that move the cursor page down, so @@ -878,8 +877,7 @@ moveToEol: function(cm, motionArgs, vim) { var cur = cm.getCursor(); vim.lastHPos = Infinity; - return clipCursorToContent(cm, { line: cur.line + motionArgs.repeat - 1, - ch: Infinity }); + return { line: cur.line + motionArgs.repeat - 1, ch: Infinity }; }, moveToFirstNonWhiteSpaceCharacter: function(cm) { // Go to the start of the line where the text begins, or the end for @@ -907,8 +905,8 @@ if (motionArgs.repeatIsExplicit) { lineNum = motionArgs.repeat - 1; } - return clipCursorToContent(cm, { line: lineNum, - ch: findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)) }); + return { line: lineNum, + ch: findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)) }; }, textObjectManipulation: function(cm, motionArgs) { var character = motionArgs.selectedCharacter; @@ -1008,6 +1006,8 @@ var cursor = cm.getCursor(); cursor = { line: cursor.line, ch: lineLength(cm, cursor.line) }; cm.setCursor(cursor); + } else if (insertAt == 'charAfter') { + cm.setCursor(offsetCursor(cm.getCursor(), 0, 1)); } cm.setOption('keyMap', 'vim-insert'); }, diff --git a/test/vim_test.js b/test/vim_test.js index 2d7067ee22..c90fd2e1b4 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -254,6 +254,16 @@ testVim('dl', function(cm, vim, helpers) { is(!register.linewise); eqPos(curStart, cm.getCursor()); }, { value: ' word1 ' }); +testVim('dl_eol', function(cm, vim, helpers) { + var curStart = makeCursor(0, 6); + cm.setCursor(curStart); + helpers.doKeys('d', 'l'); + eq(' word1', cm.getValue()); + var register = vim.registerController.getRegister(); + eq(' ', register.text); + is(!register.linewise); + helpers.assertCursorAt(makeCursor(0, 6)); +}, { value: ' word1 ' }); testVim('dl_repeat', function(cm, vim, helpers) { var curStart = makeCursor(0, 0); cm.setCursor(curStart); @@ -586,6 +596,12 @@ testVim('a', function(cm, vim, helpers) { eqPos(makeCursor(0, 2), cm.getCursor()); eq('vim-insert', cm.getOption('keyMap')); }); +testVim('a_eol', function(cm, vim, helpers) { + cm.setCursor(0, lines[0].length - 1); + helpers.doKeys('a'); + helpers.assertCursorAt(makeCursor(0, lines[0].length)); + eq('vim-insert', cm.getOption('keyMap')); +}); testVim('i', function(cm, vim, helpers) { cm.setCursor(0, 1); helpers.doKeys('i'); From 5dc92890a81e48b362531762a82995b590e5c5a4 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Mon, 3 Dec 2012 23:38:41 -0500 Subject: [PATCH 24/63] [vim keymap] Fix a few edge cases --- keymap/vim.js | 11 +++++++++-- test/vim_test.js | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 9cae1ffc58..833e035401 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -838,6 +838,9 @@ var cur = cm.getCursor(); var repeat = motionArgs.repeat; var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat; + if (line < 0 || line > cm.lineCount() - 1) { + return null; + } return { line: line, ch: endCh }; }, moveByPage: function(cm, motionArgs) { @@ -1498,8 +1501,12 @@ cur.ch = word.from; } } else { - // No more words to be found. Move to end of line. - return { line: cur.line, ch: lineLength(cm, cur.line) }; + // No more words to be found. Move to the end. + if (forward) { + return { line: cur.line, ch: lineLength(cm, cur.line) }; + } else { + return { line: cur.line, ch: 0 }; + } } } } diff --git a/test/vim_test.js b/test/vim_test.js index c90fd2e1b4..b5e77867c0 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -26,6 +26,8 @@ var lines = (function() { } return ret; })(); +var endOfDocument = makeCursor(lines.length - 1, + lines[lines.length - 1].length); var wordLine = lines[0]; var bigWordLine = lines[1]; var charLine = lines[2]; @@ -112,7 +114,13 @@ function testVim(name, run, opts, expectedFail) { } } function assertCursorAtFn(cm) { - return function(pos) { + return function(line, ch) { + var pos; + if (ch == null && typeof line.line == 'number') { + pos = line; + } else { + pos = makeCursor(line, ch); + } eqPos(pos, cm.getCursor()); } } @@ -179,17 +187,22 @@ testMotion('k_repeat', ['2', 'k'], makeCursor(0, 4), makeCursor(2, 4)); testMotion('w', 'w', word1.start); testMotion('w_repeat', ['2', 'w'], word2.start); testMotion('w_wrap', ['w'], word3.start, word2.start); +testMotion('w_endOfDocument', 'w', endOfDocument, endOfDocument); testMotion('W', 'W', bigWord1.start); testMotion('W_repeat', ['2', 'W'], bigWord3.start, bigWord1.start); testMotion('e', 'e', word1.end); testMotion('e_repeat', ['2', 'e'], word2.end); testMotion('e_wrap', 'e', word3.end, word2.end); +testMotion('e_endOfDocument', 'e', endOfDocument, endOfDocument); testMotion('b', 'b', word3.start, word3.end); testMotion('b_repeat', ['2', 'b'], word2.start, word3.end); testMotion('b_wrap', 'b', word2.start, word3.start); +testMotion('b_startOfDocument', 'b', makeCursor(0, 0), makeCursor(0, 0)); testMotion('ge', ['g', 'e'], word2.end, word3.end); testMotion('ge_repeat', ['2', 'g', 'e'], word1.end, word3.start); testMotion('ge_wrap', ['g', 'e'], word2.end, word3.start); +testMotion('ge_startOfDocument', ['g', 'e'], makeCursor(0, 0), + makeCursor(0, 0)); testMotion('gg', ['g', 'g'], makeCursor(lines[0].line, lines[0].textStart), makeCursor(3, 1)); testMotion('gg_repeat', ['3', 'g', 'g'], @@ -294,6 +307,16 @@ testVim('dj', function(cm, vim, helpers) { is(register.linewise); eqPos(makeCursor(0, 1), cm.getCursor()); }, { value: ' word1\nword2\n word3' }); +testVim('dj_end_of_document', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'j'); + eq(' word1 ', cm.getValue()); + var register = vim.registerController.getRegister(); + eq('', register.text); + is(!register.linewise); + helpers.assertCursorAt(0, 3); +}, { value: ' word1 ' }); testVim('dk', function(cm, vim, helpers) { var curStart = makeCursor(1, 3); cm.setCursor(curStart); @@ -304,6 +327,16 @@ testVim('dk', function(cm, vim, helpers) { is(register.linewise); eqPos(makeCursor(0, 1), cm.getCursor()); }, { value: ' word1\nword2\n word3' }); +testVim('dk_start_of_document', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'k'); + eq(' word1 ', cm.getValue()); + var register = vim.registerController.getRegister(); + eq('', register.text); + is(!register.linewise); + helpers.assertCursorAt(0, 3); +}, { value: ' word1 ' }); testVim('dw_space', function(cm, vim, helpers) { var curStart = makeCursor(0, 0); cm.setCursor(curStart); From 03c4853f6934d180d2bb0269212b78731de66f76 Mon Sep 17 00:00:00 2001 From: Matt Pass Date: Mon, 3 Dec 2012 16:30:37 +0000 Subject: [PATCH 25/63] Postfixed main theme classes where BG/FG set Added .CodeMirror to end of class names so that under CodeMirror v3, the authors intended BG/FG is used rather than the parent styling seen --- theme/ambiance.css | 4 ++-- theme/blackboard.css | 2 +- theme/cobalt.css | 2 +- theme/erlang-dark.css | 2 +- theme/lesser-dark.css | 2 +- theme/monokai.css | 2 +- theme/night.css | 2 +- theme/rubyblue.css | 2 +- theme/twilight.css | 2 +- theme/vibrant-ink.css | 2 +- theme/xq-dark.css | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/theme/ambiance.css b/theme/ambiance.css index c275442e5f..beec553851 100644 --- a/theme/ambiance.css +++ b/theme/ambiance.css @@ -39,7 +39,7 @@ /* Editor styling */ -.cm-s-ambiance { +.cm-s-ambiance.CodeMirror { line-height: 1.40em; font-family: Monaco, Menlo,"Andale Mono","lucida console","Courier New",monospace !important; color: #E6E1DC; @@ -70,7 +70,7 @@ background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.031); } -.cm-s-ambiance, +.cm-s-ambiance.CodeMirror, .cm-s-ambiance .CodeMirror-gutters { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAQAAAAHUWYVAABFFUlEQVQYGbzBCeDVU/74/6fj9HIcx/FRHx9JCFmzMyGRURhLZIkUsoeRfUjS2FNDtr6WkMhO9sm+S8maJfu+Jcsg+/o/c+Z4z/t97/vezy3z+z8ekGlnYICG/o7gdk+wmSHZ1z4pJItqapjoKXWahm8NmV6eOTbWUOp6/6a/XIg6GQqmenJ2lDHyvCFZ2cBDbmtHA043VFhHwXxClWmeYAdLhV00Bd85go8VmaFCkbVkzlQENzfBDZ5gtN7HwF0KDrTwJ0dypSOzpaKCMwQHKTIreYIxlmhXTzTWkVm+LTynZhiSBT3RZQ7aGfjGEd3qyXQ1FDymqbKxpspERQN2MiRjNZlFFQXfCNFm9nM1zpAsoYjmtRTc5ajwuaXc5xrWskT97RaKzAGe5ARHhVUsDbjKklziiX5WROcJwSNCNI+9w1Jwv4Zb2r7lCMZ4oq5C0EdTx+2GzNuKpJ+iFf38JEWkHJn9DNF7mmBDITrWEg0VWL3pHU20tSZnuqWu+R3BtYa8XxV1HO7GyD32UkOpL/yDloINFTmvtId+nmAjxRw40VMwVKiwrKLE4bK5UOVntYwhOcSSXKrJHKPJedocpGjVz/ZMIbnYUPB10/eKCrs5apqpgVmWzBYWpmtKHecJPjaUuEgRDDaU0oZghCJ6zNMQ5ZhDYx05r5v2muQdM0EILtXUsaKiQX9WMEUotagQzFbUNN6NUPC2nm5pxEWGCjMc3GdJHjSU2kORLK/JGSrkfGEIjncU/CYUnOipoYemwj8tST9NsJmB7TUVXtbUtXATJVZXBMvYeTXJfobgJUPmGMP/yFaWonaa6BcFO3nqcIqCozSZoZoSr1g4zJOzuyGnxTEX3lUEJ7WcZgme8ddaWvWJo2AJR9DZU3CUIbhCSG6ybSwN6qtJVnCU2svDTP2ZInOw2cBTrqtQahtNZn9NcJ4l2NaSmSkkP1noZWnVwkLmdUPOwLZEwy2Z3S3R+4rIG9hcbpPXHFVWcQdZkn2FOta3cKWQnNRC5g1LsJah4GCzSVsKnCOY5OAFRTBekyyryeyilhFKva75r4Mc0aWanGEaThcy31s439KKxTzJYY5WTHPU1FtIHjQU3Oip4xlNzj/lBw23dYZVliQa7WAXf4shetcQfatI+jWRDBPmyNeW6A1P5kdDgyYJlba0BIM8BZu1JfrFwItyjcAMR3K0BWOIrtMEXyhyrlVEx3ui5dUBjmB/Q3CXW85R4mBD0s7B+4q5tKUjOlb9qqmhi5AZ6GFIC5HXtOobdYGlVdMVbNJ8toNTFcHxnoL+muBagcctjWnbNMuR00uI7nQESwg5q2qqrKWIfrNUmeQocY6HuyxJV02wj36w00yhpmUFenv4p6fUkZYqLyuinx2RGOjhCXYyJF84oiU00YMOOhhquNdfbOB7gU88pY4xJO8LVdp6/q2voeB4R04vIdhSE40xZObx1HGGJ/ja0LBthFInKaLPPFzuCaYaoj8JjPME8yoyxo6zlBqkiUZYgq00OYMswbWO5NGmq+xhipxHLRW29ARjNKXO0wRnear8XSg4XFPLKEPUS1GqvyLwiuBUoa7zpZ0l5xxFwWmWZC1H5h5FwU8eQ7K+g8UcVY6TMQreVQT/8uQ8Z+ALIXnSEa2pYZQneE9RZbSBNYXfWYJzW/h/4j4Dp1tYVcFIC5019Vyi4ThPqSFCzjGWaHQTBU8q6vrVwgxP9Lkm840imWKpcLCjYTtrKuwvsKSnrvHCXGkSMk9p6lhckfRpIeis+N2PiszT+mFLspyGleUhDwcLrZqmyeylxwjBcKHEapqkmyangyLZRVOijwOtCY5SsG5zL0OwlCJ4y5KznF3EUNDDrinwiyLZRzOXtlBbK5ITHFGLp8Q0R6ab6mS7enI2cFrxOyHvOCFaT1HThS1krjCwqWeurCkk+willhCC+RSZnRXBiZaC5RXRIZYKp2lyfrHwiKPKR0JDzrdU2EFgpidawlFDR6FgXUMNa+g1FY3bUQh2cLCwosRdnuQTS/S+JVrGLeWIvtQUvONJxlqSQYYKpwoN2kaocLjdVsis4Mk80ESF2YpSkzwldjHkjFCUutI/r+EHDU8oCs6yzL3PhWiEooZdFMkymlas4AcI3KmoMMNSQ3tHzjGWCrcJJdYyZC7QFGwjRL9p+MrRkAGWzIaWCn9W0F3TsK01c2ZvQw0byvxuQU0r1lM0qJO7wW0kRIMdDTtXEdzi4VIh+EoIHm0mWtAtpCixlabgn83fKTI7anJe9ST7WIK1DMGpQmYeA58ImV6ezOGOzK2Kgq01pd60cKWiUi9Lievb/0vIDPHQ05Kzt4ddPckQBQtoaurjyHnek/nKzpQLrVgKPjIkh2v4uyezpv+Xoo7fPFXaGFp1vaLKxQ4uUpQQS5VuQs7BCq4xRJv7fwpVvvFEB3j+620haOuocqMhWd6TTPAEx+mdFNGHdranFe95WrWmIvlY4F1Dle2ECgc6cto7SryuqGGGha0tFQ5V53migUKmg6XKAo4qS3mik+0OZpAhOLeZKicacgaYcyx5hypYQE02ZA4xi/pNhOQxR4klNKyqacj+mpxnLTnnGSo85++3ZCZq6lrZkXlGEX3o+C9FieccJbZWVFjC0Yo1FZnJhoYMFoI1hEZ9r6hwg75HwzBNhbZCdJEfJwTPGzJvaKImw1yYX1HDAmpXR+ZJQ/SmgqMNVQb5vgamGwLtt7VwvP7Qk1xpiM5x5Cyv93E06MZmgs0Nya2azIKOYKCGBQQW97RmhKNKF02JZqHEJ4o58qp7X5EcZmc56trXEqzjCBZ1MFGR87Ql2tSTs6CGxS05PTzRQorkbw7aKoKXFDXsYW42VJih/q+FP2BdTzDTwVqOYB13liM50vG7wy28qagyuIXMeQI/Oqq8bcn5wJI50xH00CRntyfpL1T4hydYpoXgNiFzoIUTDZnLNRzh4TBHwbYGDvZkxmlyJloyr6tRihpeUG94GnKtIznREF0tzJG/OOr73JBcrSh1k6WuTprgLU+mnSGnv6Zge0NNz+kTDdH8nuAuTdJDCNb21LCiIuqlYbqGzT3RAoZofQfjFazkqeNWdYaGvYTM001EW2oKPvVk1ldUGSgUtHFwjKM1h9jnFcmy5lChoLNaQMGGDsYbKixlaMBmmsx1QjCfflwTfO/gckW0ruZ3jugKR3R5W9hGUWqCgxuFgsuaCHorotGKzGaeZB9DMsaTnKCpMtwTvOzhYk0rdrArKCqcaWmVk1+F372ur1YkKxgatI8Qfe1gIX9wE9FgS8ESmuABIXnRUbCapcKe+nO7slClSZFzpV/LkLncEb1qiO42fS3R855Su2mCLh62t1SYZZYVmKwIHjREF2uihTzB20JOkz7dkxzYQnK0UOU494wh+VWRc6Un2kpTaVgLDFEkJ/uhzRcI0YKGgpGWOlocBU/a4fKoJ/pEaNV6jip3+Es9VXY078rGnmAdf7t9ylPXS34RBSuYPs1UecZTU78WanhBCHpZ5sAoTz0LGZKjPf9TRypqWEiTvOFglL1fCEY3wY/++rbk7C8bWebA6p6om6PgOL2kp44TFJlVNBXae2rqqdZztOJpT87GQsE9jqCPIe9VReZuQ/CIgacsyZdCpIScSYqcZk8r+nsyCzhyfhOqHGOIvrLknC8wTpFcaYiGC/RU1NRbUeUpocQOnkRpGOrIOcNRx+1uA0UrzhSSt+VyS3SJpnFWkzNDqOFGIWcfR86DnmARTQ1HKIL33ExPiemeOhYSSjzlSUZZuE4TveoJLnBUOFof6KiysCbnAEcZgcUNTDOwkqWu3RWtmGpZwlHhJENdZ3miGz0lJlsKnjbwqSHQjpxnFDlTLLwqJPMZMjd7KrzkSG7VsxXBZE+F8YZkb01Oe00yyRK9psh5SYh29ySPKBo2ylNht7ZkZnsKenjKNJu9PNEyZpaCHv4Kt6RQsLvAVp7M9kIimmCUwGeWqLMmGuIotYMmWNpSahkhZw9FqZsVnKJhsjAHvtHMsTM9fCI06Dx/u3vfUXCqfsKRc4oFY2jMsoo/7DJDwZ1CsIKnJu+J9ldkpmiCxQx1rWjI+T9FwcWWzOuaYH0Hj7klNRVWEQpmaqosakiGNTFHdjS/qnUdmf0NJW5xsL0HhimCCZZSRzmSPTXJQ4aaztAwtZnoabebJ+htCaZ7Cm535ByoqXKbX1WRc4Eh2MkRXWzImVc96Cj4VdOKVxR84VdQsIUM8Psoou2byVHyZFuq7O8otbSQ2UAoeEWTudATLGSpZzVLlXVkPU2Jc+27lsw2jmg5T5VhbeE3BT083K9WsTTkFU/Osi0rC5lRlpwRHUiesNS0sOvmqGML1aRbPAxTJD9ZKtxuob+hhl8cwYGWpJ8nub7t5p6coYbMovZ1BTdaKn1jYD6h4GFDNFyT/Kqe1XCXphXHOKLZmuRSRdBPEfVUXQzJm5YGPGGJdvAEr7hHNdGZnuBvrpciGmopOLf5N0uVMy0FfYToJk90uUCbJupaVpO53UJXR2bVpoU00V2KOo4zMFrBd0Jtz2pa0clT5Q5L8IpQ177mWQejPMEJhuQjS10ref6HHjdEhy1P1EYR7GtO0uSsKJQYLiTnG1rVScj5lyazpqWGl5uBbRWl7m6ixGOOnEsMJR7z8J0n6KMnCdxhiNYQCoZ6CmYLnO8omC3MkW3bktlPmEt/VQQHejL3+dOE5FlPdK/Mq8hZxxJtLyRrepLThYKbLZxkSb5W52vYxNOaOxUF0yxMUPwBTYqCzy01XayYK0sJyWBLqX0MwU5CzoymRzV0EjjeUeLgDpTo6ij42ZAzvD01dHUUTPLU96MdLbBME8nFBn7zJCMtJcZokn8YoqU0FS5WFKyniHobguMcmW8N0XkWZjkyN3hqOMtS08r+/xTBwpZSZ3qiVRX8SzMHHjfUNFjgHEPmY9PL3ykEzxkSre/1ZD6z/NuznuB0RcE1TWTm9zRgfUWVJiG6yrzgmWPXC8EAR4Wxhlad0ZbgQyEz3pG5RVEwwDJH2mgKpjcTiCOzn1lfUWANFbZ2BA8balnEweJC9J0iuaeZoI+ippFCztEKVvckR2iice1JvhVytrQwUAZpgsubCPaU7xUe9vWnaOpaSBEspalykhC9bUlOMpT42ZHca6hyrqKmw/wMR8H5ZmdFoBVJb03O4UL0tSNnvIeRmkrLWqrs78gcrEn2tpcboh0UPOW3UUR9PMk4T4nnNKWmCjlrefhCwxRNztfmIQVdDElvS4m1/WuOujoZCs5XVOjtKPGokJzsYCtFYoWonSPT21DheU/wWhM19FcElwqNGOsp9Q8N/cwXaiND1MmeL1Q5XROtYYgGeFq1aTMsoMmcrKjQrOFQTQ1fmBYhmW6o8Jkjc7iDJRTBIo5kgJD5yMEYA3srCg7VFKwiVJkmRCc5ohGOKhsYMn/XBLdo5taZjlb9YAlGWRimqbCsoY7HFAXLa5I1HPRxMMsQDHFkWtRNniqT9UEeNjcE7RUlrCJ4R2CSJuqlKHWvJXjAUNcITYkenuBRB84TbeepcqTj3zZyFJzgYQdHnqfgI0ddUwS6GqWpsKWhjq9cV0vBAEMN2znq+EBfIWT+pClYw5xsTlJU6GeIBsjGmmANTzJZiIYpgrM0Oa8ZMjd7NP87jxhqGOhJlnQtjuQpB+8aEE00wZFznSJPyHxgH3HkPOsJFvYk8zqCHzTs1BYOa4J3PFU+UVRZxlHDM4YavlNUuMoRveiZA2d7grMNc2g+RbSCEKzmgYsUmWmazFJyoiOZ4KnyhKOGRzWJa0+moyV4TVHDzn51Awtqaphfk/lRQ08FX1iiqxTB/kLwd0VynKfEvI6cd4XMV5bMhZ7gZUWVzYQ6Nm2BYzxJbw3bGthEUUMfgbGeorae6DxHtJoZ6alhZ0+ytiVoK1R4z5PTrOECT/SugseEOlb1MMNR4VRNcJy+V1Hg9ONClSZFZjdHlc6W6FBLdJja2MC5hhpu0DBYEY1TFGwiFAxRRCsYkiM9JRb0JNMVkW6CZYT/2EiTGWmo8k+h4FhDNE7BvppoTSFnmCV5xZKzvcCdDo7VVPnIU+I+Rc68juApC90MwcFCsJ5hDqxgScYKreruyQwTqrzoqDCmhWi4IbhB0Yrt3RGa6GfDv52rKXWhh28dyZaWUvcZeMTBaZoSGyiCtRU5J8iviioHaErs7Jkj61syVzTTgOcUOQ8buFBTYWdL5g3T4qlpe0+wvD63heAXRfCCIed9RbCsp2CiI7raUOYOTU13N8PNHvpaGvayo4a3LLT1lDrVEPT2zLUlheB1R+ZTRfKWJ+dcocLJfi11vyJ51lLqJ0WD7tRwryezjiV5W28uJO9qykzX8JDe2lHl/9oyBwa2UMfOngpXCixvKdXTk3wrsKmiVYdZIqsoWEERjbcUNDuiaQomGoIbFdEHmsyWnuR+IeriKDVLnlawlyNHKwKlSU631PKep8J4Q+ayjkSLKYLhalNHlYvttb6fHm0p6OApsZ4l2VfdqZkjuysy6ysKLlckf1KUutCTs39bmCgEyyoasIWlVaMF7mgmWtBT8Kol5xpH9IGllo8cJdopcvZ2sImlDmMIbtDk3KIpeNiS08lQw11NFPTwVFlPP6pJ2gvRfI7gQUfmNAtf6Gs0wQxDsKGlVBdF8rCa3jzdwMaGHOsItrZk7hAyOzpK9VS06j5F49b0VNGOOfKs3lDToMsMBe9ZWtHFEgxTJLs7qrygKZjUnmCYoeAqeU6jqWuLJup4WghOdvCYJnrSkSzoyRkm5M2StQwVltPkfCAk58tET/CSg+8MUecmotMEnhBKfWBIZsg2ihruMJQaoIm+tkTLKEqspMh00w95gvFCQRtDwTT1gVDDSEVdlwqZfxoQRbK0g+tbiBZxzKlpnpypejdDwTaeOvorMk/IJE10h9CqRe28hhLbe0pMsdSwv4ZbhKivo2BjDWfL8UKJgeavwlwb5KlwhyE4u4XkGE2ytZCznKLCDZZq42VzT8HLCrpruFbIfOIINmh/qCdZ1ZBc65kLHR1Bkyf5zn6pN3SvGKIlFNGplhrO9QSXanLOMQTLCa0YJCRrCZm/CZmrLTm7WzCK4GJDiWUdFeYx1LCFg3NMd0XmCuF3Y5rITLDUsYS9zoHVzwnJoYpSTQoObyEzr4cFBNqYTopoaU/wkyLZ2lPhX/5Y95ulxGTV7KjhWrOZgl8MyUUafjYraNjNU1N3IWcjT5WzWqjwtoarHSUObGYO3GCJZpsBlnJGPd6ZYLyl1GdCA2625IwwJDP8GUKymbzuyPlZlvTUsaUh5zFDhRWFzPKKZLAlWdcQbObgF9tOqOsmB1dqcqYJmWstFbZRRI9poolmqiLnU0POvxScpah2iSL5UJNzgScY5+AuIbpO0YD3NCW+dLMszFSdFCWGqG6eVq2uYVNDdICGD6W7EPRWZEY5gpsE9rUkS3mijzzJnm6UpUFXG1hCUeVoS5WfNcFpblELL2qqrCvMvRfd45oalvKU2tiQ6ePJOVMRXase9iTtLJztPxJKLWpo2CRDcJwn2sWSLKIO1WQWNTCvpVUvOZhgSC40JD0dOctaSqzkCRbXsKlb11Oip6PCJ0IwSJM31j3akRxlP7Rwn6aGaUL0qiLnJkvB3xWZ2+Q1TfCwpQH3G0o92UzmX4o/oJNQMMSQc547wVHhdk+VCw01DFYEnTxzZKAm74QmeNNR1w6WzEhNK15VJzuCdxQ53dRUDws5KvwgBMOEgpcVNe0hZI6RXT1Jd0cyj5nsaEAHgVmGaJIlWdsc5Ui2ElrRR6jrRAttNMEAIWrTDFubkZaok7/AkzfIwfuWVq0jHzuCK4QabtLUMVPB3kJ0oyHTSVFlqMALilJf2Rf8k5aaHtMfayocLBS8L89oKoxpJvnAkDPa0qp5DAUTHKWmCcnthlou8iCKaFFLHWcINd1nyIwXqrSxMNmSs6KmoL2QrKuWtlQ5V0120xQ5vRyZS1rgFkWwhiOwiuQbR0OOVhQM9iS3tiXp4RawRPMp5tDletOOBL95MpM01dZTBM9pkn5qF010rIeHFcFZhmSGpYpTsI6nwhqe5C9ynhlpp5ophuRb6WcJFldkVnVEwwxVfrVkvnWUuNLCg5bgboFHPDlDPDmnK7hUrWiIbjadDclujlZcaokOFup4Ri1kacV6jmrrK1hN9bGwpKEBQ4Q6DvIUXOmo6U5LqQM6EPyiKNjVkPnJkDPNEaxhiFay5ExW1NXVUGqcpYYdPcGiCq7z/TSlbhL4pplWXKd7NZO5QQFrefhRQW/NHOsqcIglc4UhWklR8K0QzbAw08CBDnpbgqXdeD/QUsM4RZXDFBW6WJKe/mFPdH0LtBgiq57wFLzlyQzz82qYx5D5WJP5yVJDW01BfyHnS6HKO/reZqId1WGa4Hkh2kWodJ8i6KoIPlAj2hPt76CzXsVR6koPRzWTfKqIentatYpQw2me4AA3y1Kind3SwoOKZDcFXTwl9tWU6mfgRk9d71sKtlNwrjnYw5tC5n5LdKiGry3JKNlHEd3oaMCFHrazBPMp/uNJ+V7IudcSbeOIdjUEdwl0VHCOZo5t6YluEuaC9mQeMgSfOyKnYGFHcIeQ84yQWbuJYJpZw5CzglDH7gKnWqqM9ZTaXcN0TeYhR84eQtJT76JJ1lREe7WnnvsMmRc9FQ7SBBM9mV3lCUdmHk/S2RAMt0QjFNFqQpWjDPQ01DXWUdDBkXziKPjGEP3VP+zIWU2t7im41FOloyWzn/L6dkUy3VLDaZ6appgDLHPjJEsyvJngWEPUyVBiAaHCTEXwrLvSEbV1e1gKJniicWorC1MUrVjB3uDhJE/wgSOzk1DXpk0k73qCM8xw2UvD5kJmDUfOomqMpWCkJRlvKXGmoeBm18USjVIk04SClxTB6YrgLAPLWYK9HLUt5cmc0vYES8GnTeRc6skZbQkWdxRsIcyBRzx1DbTk9FbU0caTPOgJHhJKnOGIVhQqvKmo0llRw9sabrZkDtdg3PqaKi9oatjY8B+G371paMg6+mZFNNtQ04mWBq3rYLOmtWWQp8KJnpy9DdFensyjdqZ+yY40VJlH8wcdLzC8PZnvHMFUTZUrDTkLyQaGus5X5LzpYAf3i+e/ZlhqGqWhh6Ou6xTR9Z6oi5AZZtp7Mj2EEm8oSpxiYZCHU/1fbGdNNNRRoZMhmilEb2gqHOEJDtXkHK/JnG6IrvbPCwV3NhONVdS1thBMs1T4QOBcTWa2IzhMk2nW5Kyn9tXUtpv9RsG2msxk+ZsQzRQacJncpgke0+T8y5Fzj8BiGo7XlJjaTIlpQs7KFjpqGnKuoyEPeIKnFMkZHvopgh81ySxNFWvJWcKRs70j2FOT012IllEEO1n4pD1513Yg2ssQPOThOkvyrqHUdEXOSEsihmBbTbKX1kLBPWqWkLOqJbjB3GBIZmoa8qWl4CG/iZ7oiA72ZL7TJNeZUY7kFQftDcHHluBzRbCegzMtrRjVQpX2lgoPKKLJAkcbMl01XK2p7yhL8pCBbQ3BN2avJgKvttcrWDK3CiUOVxQ8ZP+pqXKyIxnmBymCg5vJjNfkPK4+c8cIfK8ocVt7kmfd/I5SR1hKvCzUtb+lhgc00ZaO6CyhIQP1Uv4yIZjload72PXX0OIJvnFU+0Zf6MhsJwTfW0r0UwQfW4LNLZl5HK261JCZ4qnBaAreVAS3WrjV0LBnNDUNNDToCEeFfwgcb4gOEqLRhirWkexrCEYKVV711DLYEE1XBEsp5tpTGjorkomKYF9FDXv7fR3BGwbettSxnyL53MBPjsxDZjMh+VUW9NRxq1DhVk+FSxQcaGjV9Pawv6eGByw5qzoy7xk4RsOShqjJwWKe/1pEEfzkobeD/dQJmpqedcyBTy2sr4nGNRH0c0SPWTLrqAc0OQcb/gemKgqucQT7ySWKCn2EUotoCvpZct7RO2sy/QW0IWcXd7pQRQyZVwT2USRO87uhjioTLKV2brpMUcMQRbKH/N2T+UlTpaMls6cmc6CCNy3JdYYSUzzJQ4oSD3oKLncULOiJvjBEC2oqnCJkJluCYy2ZQ5so9YYlZ1VLlQU1mXEW1jZERwj/MUSRc24TdexlqLKfQBtDTScJUV8FszXBEY5ktpD5Ur9hYB4Nb1iikw3JoYpkKX+RodRKFt53MMuRnKSpY31PwYaGaILh3wxJGz9TkTPEETxoCWZrgvOlmyMzxFEwVJE5xZKzvyJ4WxEc16Gd4Xe3Weq4XH2jKRikqOkGQ87hQnC7wBmGYLAnesX3M+S87eFATauuN+Qcrh7xIxXJbUIdMw3JGE3ylCWzrieaqCn4zhGM19TQ3z1oH1AX+pWEqIc7wNGAkULBo/ZxRaV9NNyh4Br3rCHZzbzmSfawBL0dNRwpW1kK9mxPXR9povcdrGSZK9c2k0xwFGzjuniCtRSZCZ6ccZ7gaktmgAOtKbG/JnOkJrjcQTdFMsxRQ2cLY3WTIrlCw1eWKn8R6pvt4GFDso3QoL4a3nLk3G6JrtME3dSenpx7PNFTmga0EaJTLQ061sEeQoWXhSo9LTXsaSjoJQRXeZLtDclbCrYzfzHHeaKjHCVOUkQHO3JeEepr56mhiyaYYKjjNU+Fed1wS5VlhWSqI/hYUdDOkaxiKehoyOnrCV5yBHtbWFqTHCCwtpDcYolesVR5yUzTZBb3RNMd0d6WP+SvhuBmRcGxnuQzT95IC285cr41cLGQ6aJJhmi4TMGempxeimBRQw1tFKV+8jd6KuzoSTqqDxzRtpZkurvKEHxlqXKRIjjfUNNXQsNOsRScoWFLT+YeRZVD3GRN0MdQcKqQjHDMrdGGVu3iYJpQx3WGUvfbmxwFfR20WBq0oYY7LMFhhgYtr8jpaEnaOzjawWWaTP8mMr0t/EPDPoqcnxTBI5o58L7uoWnMrpoqPwgVrlAUWE+V+TQl9rawoyP6QGAlQw2TPRX+YSkxyBC8Z6jhHkXBgQL7WII3DVFnRfCrBfxewv9D6xsyjys4VkhWb9pUU627JllV0YDNHMku/ldNMMXDEo4aFnAkk4U6frNEU4XgZUPmEKHUl44KrzmYamjAbh0JFvGnaTLPu1s9jPCwjFpYiN7z1DTOk/nc07CfDFzmCf7i+bfNHXhDtLeBXzTBT5rkMvWOIxpl4EMh2LGJBu2syDnAEx2naEhHDWMMzPZEhygyS1mS5RTJr5ZkoKbEUoYqr2kqdDUE8ztK7OaIntJkFrIECwv8LJTaVx5XJE86go8dFeZ3FN3rjabCAYpoYEeC9zzJVULBbmZhDyd7ko09ydpNZ3nm2Kee4FPPXHnYEF1nqOFEC08LUVcDvYXkJHW8gTaKCk9YGOeIJhqiE4ToPEepdp7IWFjdwnWaufGMwJJCMtUTTBBK9BGCOy2tGGrJTHIwyEOzp6aPzNMOtlZkDvcEWpP5SVNhfkvDxhmSazTJXYrM9U1E0xwFVwqZQwzJxw6+kGGGUj2FglGGmnb1/G51udRSMNlTw6GGnCcUwVcOpmsqTHa06o72sw1RL02p9z0VbnMLOaIX3QKaYKSCFQzBKEUNHTSc48k53RH9wxGMtpQa5KjjW0W0n6XCCCG4yxNNdhQ4R4l1Ff+2sSd6UFHiIEOyqqFgT01mEUMD+joy75jPhOA+oVVLm309FR4yVOlp4RhLiScNmSmaYF5Pw0STrOIoWMSR2UkRXOMp+M4SHW8o8Zoi6OZgjKOaFar8zZDzkWzvKOjkKBjmCXby8JahhjXULY4KlzgKLvAwxVGhvyd4zxB1d9T0piazmKLCVZY5sKiD0y2ZSYrkUEPUbIk+dlQ4SJHTR50k1DPaUWIdTZW9NJwnJMOECgd7ou/MnppMJ02O1VT4Wsh85MnZzcFTngpXGKo84qmwgKbCL/orR/SzJ2crA+t6Mp94KvxJUeIbT3CQu1uIdlQEOzlKfS3UMcrTiFmOuroocrZrT2AcmamOKg8YomeEKm/rlT2sociMaybaUlFhuqHCM2qIJ+rg4EcDFymiDSxzaHdPcpE62pD5kyM5SBMoA1PaUtfIthS85ig1VPiPPYXgYEMNk4Qq7TXBgo7oT57gPUdwgCHzhIVFPFU6OYJzHAX9m5oNrVjeE61miDrqQ4VSa1oiURTsKHC0IfjNwU2WzK6eqK8jWln4g15TVBnqmDteCJ501PGAocJhhqjZdtBEB6lnhLreFJKxmlKbeGrqLiSThVIbCdGzloasa6lpMQXHCME2boLpJgT7yWaemu6wBONbqGNVRS0PKIL7LckbjmQtR7K8I5qtqel+T/ChJTNIKLjdUMNIRyvOEko9YYl2cwQveBikCNawJKcLBbc7+JM92mysNvd/Fqp8a0k6CNEe7cnZrxlW0wQXaXjaktnRwNOGZKYiONwS7a1JVheq3WgJHlQUGKHKmp4KAxXR/ULURcNgoa4zhKSLpZR3kxRRb0NmD0OFn+UCS7CzI1nbP6+o4x47QZE5xRCt3ZagnYcvmpYQktXdk5YKXTzBC57kKEe0VVuiSYqapssMS3C9p2CKkHOg8B8Pa8p5atrIw3qezIWanMGa5HRDNF6RM9wcacl0N+Q8Z8hsIkSnaIIdHRUOEebAPy1zbCkhM062FCJtif7PU+UtoVXzWKqM1PxXO8cfdruhFQ/a6x3JKYagvVDhQEtNiyiiSQ7OsuRsZUku0CRNDs4Sog6KKjsZgk2bYJqijgsEenoKeniinRXBn/U3lgpPdyDZynQx8IiioMnCep5Ky8mjGs6Wty0l1hUQTcNWswS3WRp2kCNZwJG8omG8JphPUaFbC8lEfabwP7VtM9yoaNCAjpR41VNhrD9LkbN722v0CoZMByFzhaW+MyzRYEWFDQwN2M4/JiT76PuljT3VU/A36eaIThb+R9oZGOAJ9tewkgGvqOMNRWYjT/Cwu99Q8LqDE4TgbLWxJ1jaDDAERsFOFrobgjUsBScaguXU8kKm2RL19tRypSHnHNlHiIZqgufs4opgQdVdwxBNNFBR6kVFqb8ogimOzB6a6HTzrlDHEpYaxjiiA4TMQobkDg2vejjfwJGWmnbVFAw3H3hq2NyQfG7hz4aC+w3BbwbesG0swYayvpAs6++Ri1Vfzx93mFChvyN5xVHTS+0p9aqCAxyZ6ZacZyw5+7uuQkFPR9DDk9NOiE7X1PCYJVjVUqq7JlrHwWALF5nfHNGjApdpqgzx5OwilDhCiDYTgnc9waGW4BdLNNUQvOtpzDOWHDH8D7TR/A/85KljEQu3NREc4Pl/6B1Hhc8Umb5CsKMmGC9EPcxoT2amwHNCmeOEnOPbklnMkbOgIvO5UMOpQrS9UGVdt6iH/fURjhI/WOpaW9OKLYRod6HCUEdOX000wpDZQ6hwg6LgZfOqo1RfT/CrJzjekXOGhpc1VW71ZLbXyyp+93ILbC1kPtIEYx0FIx1VDrLoVzXRKRYWk809yYlC9ImcrinxtabKnzRJk3lAU1OLEN1j2zrYzr2myHRXJFf4h4QKT1qSTzTB5+ZNTzTRkAxX8FcLV2uS8eoQQ2aAkFzvCM72sJIcJET3WPjRk5wi32uSS9rfZajpWEvj9hW42F4o5NytSXYy8IKHay10VYdrcl4SkqscrXpMwyGOgtkajheSxdQqmpxP1L3t4R5PqasFnrQEjytq6qgp9Y09Qx9o4S1FzhUCn1kyHSzBWLemoSGvOqLNhZyBjmCaAUYpMgt4Ck7wBBMMwWKWgjsUwTaGVsxWC1mYoKiyqqeGKYqonSIRQ3KIkHO0pmAxTdBHkbOvfllfr+AA+7gnc50huVKYK393FOyg7rbPO/izI7hE4CnHHHnJ0ogNPRUGeUpsrZZTBJcrovUcJe51BPsr6GkJdhCCsZ6aTtMEb2pqWkqeVtDXE/QVggsU/Nl86d9RMF3DxvZTA58agu810RWawCiSzzXBeU3MMW9oyJUedvNEvQyNu1f10BSMddR1vaLCYpYa/mGocLSiYDcLbQz8aMn5iyF4xBNMs1P0QEOV7o5gaWGuzSeLue4tt3ro7y4Tgm4G/mopdZgl6q0o6KzJWE3mMksNr3r+a6CbT8g5wZNzT9O7fi/zpaOmnz3BRoqos+tv9zMbdpxsqDBOEewtJLt7cg5wtKKbvldpSzRRCD43VFheCI7yZLppggMVBS/KMAdHODJvOwq2NQSbKKKPLdFWQs7Fqo+mpl01JXYRgq8dnGLhTiFzqmWsUMdpllZdbKlyvSdYxhI9YghOtxR8LgSLWHK62mGGVoxzBE8LNWzqH9CUesQzFy5RQzTc56mhi6fgXEWwpKfE5Z7M05ZgZUPmo6auiv8YKzDYwWBLMErIbKHJvOwIrvEdhOBcQ9JdU1NHQ7CXn2XIDFBKU2WAgcX9UAUzDXWd5alwuyJ41Z9rjKLCL4aCp4WarhPm2rH+SaHUYE001JDZ2ZAzXPjdMpZWvC9wmqIB2lLhQ01D5jO06hghWMndbM7yRJMsoCj1vYbnFQVrW9jak3OlEJ3s/96+p33dEPRV5GxiqaGjIthUU6FFEZyqCa5qJrpBdzSw95IUnOPIrCUUjRZQFrbw5PR0R1qiYx3cb6nrWUMrBmmiBQxVHtTew5ICP/ip6g4hed/Akob/32wvBHsIOX83cI8hGeNeNPCIkPmXe8fPKx84OMSRM1MTdXSwjCZ4S30jVGhvqTRak/OVhgGazHuOCud5onEO1lJr6ecVyaOK6H7zqlBlIaHE0oroCgfvGJIdPcmfLNGLjpz7hZwZQpUbFME0A1cIJa7VNORkgfsMBatbKgwwJM9bSvQXeNOvbIjelg6WWvo5kvbKaJJNHexkKNHL9xRyFlH8Ti2riB5wVPhUk7nGkJnoCe428LR/wRGdYIlmWebCyxou1rCk4g/ShugBDX0V0ZQWkh0dOVsagkM0yV6OoLd5ye+pRlsCr0n+KiQrGuq5yJDzrTAXHtLUMduTDBVKrSm3eHL+6ijxhFDX9Z5gVU/wliHYTMiMFpKLNMEywu80wd3meoFmt6VbRMPenhrOc6DVe4pgXU8DnnHakLOIIrlF4FZPIw6R+zxBP0dyq6OOZ4Q5sLKCcz084ok+VsMMyQhNZmmBgX5xIXOEJTmi7VsGTvMTNdHHhpzdbE8Du2oKxgvBqQKdDDnTFOylCFaxR1syz2iqrOI/FEpNc3C6f11/7+ASS6l2inq2ciTrCCzgyemrCL5SVPjQkdPZUmGy2c9Sw9FtR1sS30RmsKPCS4rkIC/2U0MduwucYolGaPjKEyhzmiPYXagyWbYz8LWBDdzRimAXzxx4z8K9hpzlhLq+NiQ97HuKorMUfK/OVvC2JfiHUPCQI/q7J2gjK+tTDNxkCc4TMssqCs4TGtLVwQihyoAWgj9bosU80XGW6Ac9TJGziaUh5+hnFcHOnlaM1iRn29NaqGENTTTSUHCH2tWTeV0osUhH6psuVLjRUmGWhm6OZEshGeNowABHcJ2Bpy2ZszRcKkRXd2QuKVEeXnbfaEq825FguqfgfE2whlChSRMdron+LATTPQ2Z369t4B9C5gs/ylzv+CMmepIDPclFQl13W0rspPd1JOcbghGOEutqCv5qacURQl3dDKyvyJlqKXGPgcM9FfawJAMVmdcspcYKOZc4GjDYkFlK05olNMHyHn4zFNykyOxt99RkHlfwmiHo60l2EKI+mhreEKp080Tbug08BVPcgoqC5zWt+NLDTZ7oNSF51N1qie7Va3uCCwyZbkINf/NED6jzOsBdZjFN8oqG3wxVunqCSYYKf3EdhJyf9YWGf7tRU2oH3VHgPr1fe5J9hOgHd7xQ0y7qBwXr23aGErP0cm64JVjZwsOGqL+mhNgZmhJLW2oY4UhedsyBgzrCKrq7BmcpNVhR6jBPq64Vgi+kn6XE68pp8J5/+0wRHGOpsKenQn9DZntPzjRLZpDAdD2fnSgkG9tmIXnUwQ6WVighs7Yi2MxQ0N3CqYaCXkJ0oyOztMDJjmSSpcpvlrk0RMMOjmArQ04PRV1DO1FwhCVaUVPpKUM03JK5SxPsIWRu8/CGHi8UHChiqGFDTbSRJWeYUDDcH6vJWUxR4k1FXbMUwV6e4AJFXS8oMqsZKqzvYQ9DDQdZckY4aGsIhtlubbd2r3j4QBMoTamdPZk7O/Bf62lacZwneNjQoGcdVU7zJOd7ghsUHOkosagic6cnWc8+4gg285R6zZP5s1/LUbCKIznTwK36PkdwlOrl4U1LwfdCCa+IrvFkmgw1PCAUXKWo0sURXWcI2muKJlgyFzhynCY4RBOsqCjoI1R5zREco0n2Vt09BQtYSizgKNHfUmUrQ5UOCh51BFcLmY7umhYqXKQomOop8bUnWNNQcIiBcYaC6xzMNOS8JQQfeqKBmmglB+97ok/lfk3ygaHSyZaCRTzRxQo6GzLfa2jWBPepw+UmT7SQEJyiyRkhBLMVOfcoMjcK0eZChfUNzFAUzCsEN5vP/X1uP/n/aoMX+K+nw/Hjr/9xOo7j7Pju61tLcgvJpTWXNbfN5jLpi6VfCOviTktKlFusQixdEKWmEBUKNaIpjZRSSOXSgzaaKLdabrm1/9nZ+/f+vd/vz/v9+Xy+zZ7PRorYoZqyLrCwQdEAixxVOEXNNnjX2nUSRlkqGmWowk8lxR50JPy9Bo6qJXaXwNvREBvnThPEPrewryLhcAnj5WE15Fqi8W7R1sAuEu86S4ENikItFN4xkv9Af4nXSnUVcLiA9xzesFpivRRVeFKtsMRaKBhuSbjOELnAUtlSQUpXgdfB4Z1oSbnFEetbQ0IrAe+Y+pqnDcEJFj6S8LDZzZHwY4e3XONNlARraomNEt2bkvGsosA3ioyHm+6jCMbI59wqt4eeara28IzEmyPgoRaUOEDhTVdEJhmCoTWfC0p8aNkCp0oYqih2iqGi4yXeMkOsn4LdLLnmKfh/YogjNsPebeFGR4m9BJHLzB61XQ3BtpISfS2FugsK9FAtLWX1dCRcrCnUp44CNzuCowUZmxSRgYaE6Za0W2u/E7CVXCiI/UOR8aAm1+OSyE3mOUcwyc1zBBeoX1kiKy0Zfxck1Gsyulti11i83QTBF5Kg3pDQThFMVHiPSlK+0cSedng/VaS8bOZbtsBcTcZAR8JP5KeqQ1OYKAi20njdNNRpgnsU//K+JnaXJaGTomr7aYIphoRn9aeShJWKEq9LcozSF7QleEfDI5LYm5bgVkFkRwVDBCVu0DDIkGupo8TZBq+/pMQURYErJQmPKGKjNDkWOLx7Jd5QizdUweIaKrlP7SwJDhZvONjLkOsBBX9UpGxnydhXkfBLQ8IxgojQbLFnJf81JytSljclYYyEFyx0kVBvKWOFJmONpshGAcsduQY5giVNCV51eOdJYo/pLhbvM0uDHSevNKRcrKZIqnCtJeEsO95RoqcgGK4ocZcho1tTYtcZvH41pNQ7vA0WrhIfOSraIIntIAi+NXWCErdbkvrWwjRLrt0NKUdL6KSOscTOdMSOUtBHwL6OLA0vNSdynaWQEnCpIvKaIrJJEbvHkmuNhn6OjM8VkSGSqn1uYJCGHnq9I3aLhNME3t6GjIkO7xrNFumpyTNX/NrwX7CrIRiqqWijI9JO4d1iieykyfiposQIQ8YjjsjlBh6oHWbwRjgYJQn2NgSnNycmJAk3NiXhx44Sxykihxm8ybUwT1OVKySc7vi3OXVkdBJ4AyXBeksDXG0IhgtYY0lY5ahCD0ehborIk5aUWRJviMA7Xt5kyRjonrXENkm8yYqgs8VzgrJmClK20uMM3jRJ0FiQICQF9hdETlLQWRIb5ki6WDfWRPobvO6a4GP5mcOrNzDFELtTkONLh9dXE8xypEg7z8A9jkhrQ6Fhjlg/QVktJXxt4WXzT/03Q8IaQWSqIuEvloQ2mqC9Jfi7wRul4RX3pSPlzpoVlmCtI2jvKHCFhjcM3sN6lqF6HxnKelLjXWbwrpR4xzuCrTUZx2qq9oAh8p6ixCUGr78g8oyjRAtB5CZFwi80VerVpI0h+IeBxa6Zg6kWvpDHaioYYuEsRbDC3eOmC2JvGYLeioxGknL2UATNJN6hmtj1DlpLvDVmocYbrGCVJKOrg4X6DgddLA203BKMFngdJJFtFd7vJLm6KEpc5yjQrkk7M80SGe34X24nSex1Ra5Omgb71JKyg8SrU3i/kARKwWpH0kOGhKkObyfd0ZGjvyXlAkVZ4xRbYJ2irFMkFY1SwyWxr2oo4zlNiV+7zmaweFpT4kR3kaDAFW6xpSqzJay05FtYR4HmZhc9UxKbbfF2V8RG1MBmSaE+kmC6JnaRXK9gsiXhJHl/U0qM0WTcbyhwkYIvFGwjSbjfwhiJt8ZSQU+Bd5+marPMOkVkD0muxYLIfEuhh60x/J92itguihJSEMySVPQnTewnEm+620rTQEMsOfo4/kP/0ARvWjitlpSX7GxBgcMEsd3EEeYWvdytd+Saawi6aCIj1CkGb6Aj9rwhx16Cf3vAwFy5pyLhVonXzy51FDpdEblbkdJbUcEPDEFzQ8qNmhzzLTmmKWKbFCXeEuRabp6rxbvAtLF442QjQ+wEA9eL1xSR7Q0JXzlSHjJ4exq89yR0laScJ/FW6z4a73pFMEfDiRZvuvijIt86RaSFOl01riV2mD1UEvxGk/Geg5aWwGki1zgKPG9J2U8PEg8qYvMsZeytiTRXBMslCU8JSlxi8EabjwUldlDNLfzTUmCgxWsjqWCOHavYAqsknKFIO0yQ61VL5AVFxk6WhEaCAkdJgt9aSkzXlKNX2jEa79waYuc7gq0N3GDJGCBhoiTXUEPsdknCUE1CK0fwsiaylSF2uiDyO4XX3pFhNd7R4itFGc0k/ElBZwWvq+GC6szVeEoS/MZ+qylwpKNKv9Z469UOjqCjwlusicyTxG6VpNxcQ8IncoR4RhLbR+NdpGGmJWOcIzJGUuKPGpQg8rrG21dOMqQssJQ4RxH5jaUqnZuQ0F4Q+cjxLwPtpZbIAk3QTJHQWBE5S1BokoVtDd6lhqr9UpHSUxMcIYl9pojsb8h4SBOsMQcqvOWC2E8EVehqiJ1hrrAEbQxeK0NGZ0Gkq+guSRgniM23bIHVkqwx4hiHd7smaOyglyIyQuM978j4VS08J/A2G1KeMBRo4fBaSNhKUEZfQewVQ/C1I+MgfbEleEzCUw7mKXI0M3hd1EESVji8x5uQ41nxs1q4RMJCCXs7Iq9acpxn22oSDnQ/sJTxsCbHIYZiLyhY05TY0ZLIOQrGaSJDDN4t8pVaIrsqqFdEegtizc1iTew5Q4ayBDMUsQMkXocaYkc0hZua412siZ1rSXlR460zRJ5SlHGe5j801RLMlJTxtaOM3Q1pvxJ45zUlWFD7rsAbpfEm1JHxG0eh8w2R7QQVzBUw28FhFp5QZzq8t2rx2joqulYTWSuJdTYfWwqMFMcovFmSyJPNyLhE4E10pHzYjOC3huArRa571ZsGajQpQx38SBP5pyZB6lMU3khDnp0MBV51BE9o2E+TY5Ml2E8S7C0o6w1xvCZjf0HkVEHCzFoyNmqC+9wdcqN+Tp7jSDheE9ws8Y5V0NJCn2bk2tqSY4okdrEhx1iDN8cSudwepWmAGXKcJXK65H9to8jYQRH7SBF01ESUJdd0TayVInaWhLkOjlXE5irKGOnI6GSWGCJa482zBI9rCr0jyTVcEuzriC1vcr6mwFGSiqy5zMwxBH/TJHwjSPhL8+01kaaSUuMFKTcLEvaUePcrSmwn8DZrgikWb7CGPxkSjhQwrRk57tctmxLsb9sZvL9LSlyuSLlWkqOjwduo8b6Uv1DkmudIeFF2dHCgxVtk8dpIvHpBxhEOdhKk7OLIUSdJ+cSRY57B+0DgGUUlNfpthTfGkauzxrvTsUUaCVhlKeteTXCoJDCa2NOKhOmC4G1H8JBd4OBZReSRGkqcb/CO1PyLJTLB4j1q8JYaIutEjSLX8YKM+a6phdMsdLFUoV5RTm9JSkuDN8WcIon0NZMNZWh1q8C7SJEwV5HxrmnnTrf3KoJBlmCYI2ilSLlfEvlE4011NNgjgthzEua0oKK7JLE7HZHlEl60BLMVFewg4EWNt0ThrVNEVkkiTwpKXSWJzdRENgvKGq4IhjsiezgSFtsfCUq8qki5S1LRQeYQQ4nemmCkImWMw3tFUoUBZk4NOeZYEp4XRKTGa6wJjrWNHBVJR4m3FCnbuD6aak2WsMTh3SZImGCIPKNgsDpVwnsa70K31lCFJZYcwwSMFcQulGTsZuEaSdBXkPGZhu0FsdUO73RHjq8MPGGIfaGIbVTk6iuI3GFgucHrIQkmWSJdBd7BBu+uOryWAhY7+Lki9rK5wtEQzWwvtbqGhIMFwWRJsElsY4m9IIg9L6lCX0VklaPAYkfkZEGDnOWowlBJjtMUkcGK4Lg6EtoZInMUBVYLgn0UsdmCyCz7gIGHFfk+k1QwTh5We7A9x+IdJ6CvIkEagms0hR50eH9UnTQJ+2oiKyVlLFUE+8gBGu8MQ3CppUHesnjTHN4QB/UGPhCTHLFPHMFrCqa73gqObUJGa03wgbhHkrCfpEpzNLE7JDS25FMKhlhKKWKfCgqstLCPu1zBXy0J2ztwjtixBu8UTRn9LVtkmCN2iyFhtME70JHRQ1KVZXqKI/KNIKYMCYs1GUMEKbM1bKOI9LDXC7zbHS+bt+1MTWS9odA9DtrYtpbImQJ2VHh/lisEwaHqUk1kjKTAKknkBEXkbkdMGwq0dnhzLJF3NJH3JVwrqOB4Sca2hti75nmJN0WzxS6UxDYoEpxpa4htVlRjkYE7DZGzJVU72uC9IyhQL4i8YfGWSYLLNcHXloyz7QhNifmKSE9JgfGmuyLhc403Xm9vqcp6gXe3xuuv8F6VJNxkyTHEkHG2g0aKXL0MsXc1bGfgas2//dCONXiNLCX+5mB7eZIl1kHh7ajwpikyzlUUWOVOsjSQlsS+M0R+pPje/dzBXRZGO0rMtgQrLLG9VSu9n6CMXS3BhwYmSoIBhsjNBmZbgusE9BCPCP5triU4VhNbJfE+swSP27aayE8tuTpYYjtrYjMVGZdp2NpS1s6aBnKSHDsbKuplKbHM4a0wMFd/5/DmGyKrJSUaW4IBrqUhx0vyfzTBBLPIUcnZdrAkNsKR0sWRspumSns6Ch0v/qqIbBYUWKvPU/CFoyrDJGwSNFhbA/MlzKqjrO80hRbpKx0Jewsi/STftwGSlKc1JZyAzx05dhLEdnfQvhZOqiHWWEAHC7+30FuRcZUgaO5gpaIK+xsiHRUsqaPElTV40xQZQ107Q9BZE1nryDVGU9ZSQ47bmhBpLcYpUt7S+xuK/FiT8qKjwXYw5ypS2iuCv7q1gtgjhuBuB8LCFY5cUuCNtsQOFcT+4Ih9JX+k8Ea6v0iCIRZOtCT0Et00JW5UeC85Cg0ScK0k411HcG1zKtre3SeITBRk7WfwDhEvaYLTHP9le0m8By0JDwn4TlLW/aJOvGHxdjYUes+ScZigCkYQdNdEOhkiezgShqkx8ueKjI8lDfK2oNiOFvrZH1hS+tk7NV7nOmLHicGWEgubkXKdwdtZknCLJXaCpkrjZBtLZFsDP9CdxWsSr05Sxl6CMmoFbCOgryX40uDtamB7SVmXW4Ihlgpmq+00tBKUUa83WbjLUNkzDmY7cow1JDygyPGlhgGKYKz4vcV7QBNbJIgM11TUqZaMdwTeSguH6rOaw1JRKzaaGyxVm2EJ/uCIrVWUcZUkcp2grMsEjK+DMwS59jQk3Kd6SEq1d0S6uVmO4Bc1lDXTUcHjluCXEq+1OlBDj1pi9zgiXxnKuE0SqTXwhqbETW6RggMEnGl/q49UT2iCzgJvRwVXS2K/d6+ZkyUl7jawSVLit46EwxVljDZwoSQ20sDBihztHfk2yA8NVZghiXwrYHQdfKAOtzsayjhY9bY0yE2CWEeJ9xfzO423xhL5syS2TFJofO2pboHob0nY4GiAgRrvGQEDa/FWSsoaaYl0syRsEt3kWoH3B01shCXhTUWe9w3Bt44SC9QCh3eShQctwbaK2ApLroGCMlZrYqvlY3qYhM0aXpFkPOuoqJ3Dm6fxXrGwVF9gCWZagjPqznfkuMKQ8DPTQRO8ZqG1hPGKEm9IgpGW4DZDgTNriTxvFiq+Lz+0cKfp4wj6OCK9JSnzNSn9LFU7UhKZZMnYwcJ8s8yRsECScK4j5UOB95HFO0CzhY4xJxuCix0lDlEUeMdS6EZBkTsUkZ4K74dugyTXS7aNgL8aqjDfkCE0ZbwkCXpaWCKhl8P7VD5jxykivSyxyZrYERbe168LYu9ZYh86IkscgVLE7tWPKmJv11CgoyJltMEbrohtVAQfO4ImltiHEroYEs7RxAarVpY8AwXMcMReFOTYWe5iiLRQxJ5Q8DtJ8LQhWOhIeFESPGsILhbNDRljNbHzNRlTFbk2S3L0NOS6V1KFJYKUbSTcIIhM0wQ/s2TM0SRMNcQmSap3jCH4yhJZKSkwyRHpYYgsFeQ4U7xoCB7VVOExhXepo9ABBsYbvGWKXPME3lyH95YioZ0gssQRWWbI+FaSMkXijZXwgiTlYdPdkNLaETxlyDVIwqeaEus0aTcYcg0RVOkpR3CSJqIddK+90JCxzsDVloyrFd5ZAr4TBKfaWa6boEA7C7s6EpYaeFPjveooY72mjIccLHJ9HUwVlDhKkmutJDJBwnp1rvulJZggKDRfbXAkvC/4l3ozQOG9a8lxjx0i7nV4jSXc7vhe3OwIxjgSHjdEhhsif9YkPGlus3iLFDnWOFhtCZbJg0UbQcIaR67JjthoCyMEZRwhiXWyxO5QxI6w5NhT4U1WsJvDO60J34fW9hwzwlKij6ZAW9ne4L0s8C6XeBMEkd/LQy1VucBRot6QMlbivaBhoBgjqGiCJNhsqVp/S2SsG6DIONCR0dXhvWbJ+MRRZJkkuEjgDXJjFQW6SSL7GXK8Z2CZg7cVsbWGoKmEpzQ5elpiy8Ryg7dMkLLUEauzeO86CuwlSOlgYLojZWeJ9xM3S1PWfEfKl5ISLQ0MEKR8YOB2QfCxJBjrKPCN4f9MkaSsqoVXJBmP7EpFZ9UQfOoOFwSzBN4MQ8LsGrymlipcJQhmy0GaQjPqCHaXRwuCZwRbqK2Fg9wlClZqYicrIgMdZfxTQ0c7TBIbrChxmuzoKG8XRaSrIhhiyNFJkrC7oIAWMEOQa5aBekPCRknCo4IKPrYkvCDI8aYmY7WFtprgekcJZ3oLIqssCSMtFbQTJKwXYy3BY5oCh2iKPCpJOE+zRdpYgi6O2KmOAgvVCYaU4ySRek1sgyFhJ403QFHiVEmJHwtybO1gs8Hr5+BETQX3War0qZngYGgtVZtoqd6vFSk/UwdZElYqyjrF4HXUeFspIi9IGKf4j92pKGAdCYMVsbcV3kRF0N+R8LUd5PCsIGWoxDtBkCI0nKofdJQxT+LtZflvuc8Q3CjwWkq8KwUpHzkK/NmSsclCL0nseQdj5FRH5CNHSgtLiW80Of5HU9Hhlsga9bnBq3fEVltKfO5IaSTmGjjc4J0otcP7QsJUSQM8pEj5/wCuUuC2DWz8AAAAAElFTkSuQmCC"); } diff --git a/theme/blackboard.css b/theme/blackboard.css index 6e7bab7d03..f2bde690c8 100644 --- a/theme/blackboard.css +++ b/theme/blackboard.css @@ -1,6 +1,6 @@ /* Port of TextMate's Blackboard theme */ -.cm-s-blackboard { background: #0C1021; color: #F8F8F8; } +.cm-s-blackboard.CodeMirror { background: #0C1021; color: #F8F8F8; } .cm-s-blackboard .CodeMirror-selected { background: #253B76 !important; } .cm-s-blackboard .CodeMirror-gutters { background: #0C1021; border-right: 0; } .cm-s-blackboard .CodeMirror-linenumber { color: #888; } diff --git a/theme/cobalt.css b/theme/cobalt.css index daf0abe366..6095799f36 100644 --- a/theme/cobalt.css +++ b/theme/cobalt.css @@ -1,4 +1,4 @@ -.cm-s-cobalt { background: #002240; color: white; } +.cm-s-cobalt.CodeMirror { background: #002240; color: white; } .cm-s-cobalt div.CodeMirror-selected { background: #b36539 !important; } .cm-s-cobalt .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } .cm-s-cobalt .CodeMirror-linenumber { color: #d0d0d0; } diff --git a/theme/erlang-dark.css b/theme/erlang-dark.css index d6a6d377db..ea9c26c44d 100644 --- a/theme/erlang-dark.css +++ b/theme/erlang-dark.css @@ -1,4 +1,4 @@ -.cm-s-erlang-dark { background: #002240; color: white; } +.cm-s-erlang-dark.CodeMirror { background: #002240; color: white; } .cm-s-erlang-dark div.CodeMirror-selected { background: #b36539 !important; } .cm-s-erlang-dark .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } .cm-s-erlang-dark .CodeMirror-linenumber { color: #d0d0d0; } diff --git a/theme/lesser-dark.css b/theme/lesser-dark.css index 509b1e804e..67f71ad727 100644 --- a/theme/lesser-dark.css +++ b/theme/lesser-dark.css @@ -9,7 +9,7 @@ Ported to CodeMirror by Peter Kroon font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', 'Monaco', Courier, monospace !important; } -.cm-s-lesser-dark { background: #262626; color: #EBEFE7; text-shadow: 0 -1px 1px #262626; } +.cm-s-lesser-dark.CodeMirror { background: #262626; color: #EBEFE7; text-shadow: 0 -1px 1px #262626; } .cm-s-lesser-dark div.CodeMirror-selected {background: #45443B !important;} /* 33322B*/ .cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid white !important; } .cm-s-lesser-dark pre { padding: 0 8px; }/*editable code holder*/ diff --git a/theme/monokai.css b/theme/monokai.css index 89692f5d6a..a0b3c7c0af 100644 --- a/theme/monokai.css +++ b/theme/monokai.css @@ -1,6 +1,6 @@ /* Based on Sublime Text's Monokai theme */ -.cm-s-monokai {background: #272822; color: #f8f8f2;} +.cm-s-monokai.CodeMirror {background: #272822; color: #f8f8f2;} .cm-s-monokai div.CodeMirror-selected {background: #49483E !important;} .cm-s-monokai .CodeMirror-gutters {background: #272822; border-right: 0px;} .cm-s-monokai .CodeMirror-linenumber {color: #d0d0d0;} diff --git a/theme/night.css b/theme/night.css index b403e54dd6..8804a399a1 100644 --- a/theme/night.css +++ b/theme/night.css @@ -1,6 +1,6 @@ /* Loosely based on the Midnight Textmate theme */ -.cm-s-night { background: #0a001f; color: #f8f8f8; } +.cm-s-night.CodeMirror { background: #0a001f; color: #f8f8f8; } .cm-s-night div.CodeMirror-selected { background: #447 !important; } .cm-s-night .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } .cm-s-night .CodeMirror-linenumber { color: #f8f8f8; } diff --git a/theme/rubyblue.css b/theme/rubyblue.css index 47e0b69e08..8817de07bf 100644 --- a/theme/rubyblue.css +++ b/theme/rubyblue.css @@ -1,6 +1,6 @@ .cm-s-rubyblue { font:13px/1.4em Trebuchet, Verdana, sans-serif; } /* - customized editor font - */ -.cm-s-rubyblue { background: #112435; color: white; } +.cm-s-rubyblue.CodeMirror { background: #112435; color: white; } .cm-s-rubyblue div.CodeMirror-selected { background: #38566F !important; } .cm-s-rubyblue .CodeMirror-gutters { background: #1F4661; border-right: 7px solid #3E7087; } .cm-s-rubyblue .CodeMirror-linenumber { color: white; } diff --git a/theme/twilight.css b/theme/twilight.css index 1d151dbd9c..fd8944ba8d 100644 --- a/theme/twilight.css +++ b/theme/twilight.css @@ -1,4 +1,4 @@ -.cm-s-twilight { background: #141414; color: #f7f7f7; } /**/ +.cm-s-twilight.CodeMirror { background: #141414; color: #f7f7f7; } /**/ .cm-s-twilight .CodeMirror-selected { background: #323232 !important; } /**/ .cm-s-twilight .CodeMirror-gutters { background: #222; border-right: 1px solid #aaa; } diff --git a/theme/vibrant-ink.css b/theme/vibrant-ink.css index 9a6fd4df49..22024a489a 100644 --- a/theme/vibrant-ink.css +++ b/theme/vibrant-ink.css @@ -1,6 +1,6 @@ /* Taken from the popular Visual Studio Vibrant Ink Schema */ -.cm-s-vibrant-ink { background: black; color: white; } +.cm-s-vibrant-ink.CodeMirror { background: black; color: white; } .cm-s-vibrant-ink .CodeMirror-selected { background: #35493c !important; } .cm-s-vibrant-ink .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } diff --git a/theme/xq-dark.css b/theme/xq-dark.css index 5add87be70..fd9bb12abc 100644 --- a/theme/xq-dark.css +++ b/theme/xq-dark.css @@ -20,7 +20,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -.cm-s-xq-dark { background: #0a001f; color: #f8f8f8; } +.cm-s-xq-dark.CodeMirror { background: #0a001f; color: #f8f8f8; } .cm-s-xq-dark span.CodeMirror-selected { background: #a8f !important; } .cm-s-xq-dark .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } .cm-s-xq-dark .CodeMirror-linenumber { color: #f8f8f8; } From 5e11eadb63b0002d985d17d75042f6673b47f9e9 Mon Sep 17 00:00:00 2001 From: James Campos Date: Mon, 3 Dec 2012 02:46:54 -0800 Subject: [PATCH 26/63] [vim][v3] fix search highlighting `markText` changed parameters from v2 to v3 --- keymap/vim.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keymap/vim.js b/keymap/vim.js index 833e035401..58ccbc3745 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1785,7 +1785,7 @@ for (var cursor = cm.getSearchCursor(state.query); cursor.findNext();) { state.marked.push(cm.markText(cursor.from(), cursor.to(), - 'CodeMirror-searching')); + { className: 'CodeMirror-searching' })); } } }); From c3312da21fa07077a8d081b8c93ca97b5448d703 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 4 Dec 2012 11:30:12 +0100 Subject: [PATCH 27/63] Don't mutate cursor style for blinking when it is invisible Closes #1044 --- lib/codemirror.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/codemirror.js b/lib/codemirror.js index 73c9a2867e..c7154252fb 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -752,6 +752,7 @@ window.CodeMirror = (function() { var on = true; display.cursor.style.visibility = display.otherCursor.style.visibility = ""; display.blinker = setInterval(function() { + if (!display.cursor.offsetHeight) return; display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden"; }, cm.options.cursorBlinkRate); } From 8bcede98c421e0aa084dc224d49a8494e06c8cd1 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 4 Dec 2012 11:53:19 +0100 Subject: [PATCH 28/63] Improve cache clearing (again) Issue #1042 --- lib/codemirror.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index c7154252fb..33e13e562e 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -214,7 +214,7 @@ window.CodeMirror = (function() { }); } regChange(cm, 0, doc.size); - clearMeasureLineCache(cm); + clearCaches(cm); setTimeout(bind(updateScrollbars, cm.display), 100); } @@ -227,6 +227,7 @@ window.CodeMirror = (function() { function themeChanged(cm) { cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + clearCaches(cm); } function guttersChanged(cm) { @@ -924,8 +925,9 @@ window.CodeMirror = (function() { return data; } - function clearMeasureLineCache(cm) { + function clearCaches(cm) { cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0; + cm.display.cachedCharWidth = cm.display.cachedTextHeight = null; } // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page" @@ -1260,7 +1262,7 @@ window.CodeMirror = (function() { on(window, "resize", function resizeHandler() { // Might be a text scaling operation, clear size caches. d.cachedCharWidth = d.cachedTextHeight = null; - clearMeasureLineCache(cm); + clearCaches(cm); if (d.wrapper.parentNode) updateDisplay(cm, true); else off(window, "resize", resizeHandler); }); @@ -2729,7 +2731,7 @@ window.CodeMirror = (function() { operation: function(f){return operation(this, f)();}, refresh: function() { - clearMeasureLineCache(this); + clearCaches(this); if (this.display.scroller.scrollHeight > this.view.scrollTop) this.display.scrollbarV.scrollTop = this.display.scroller.scrollTop = this.view.scrollTop; updateDisplay(this, true); @@ -2766,13 +2768,12 @@ window.CodeMirror = (function() { option("smartIndent", true); option("tabSize", 4, function(cm) { loadMode(cm); - clearMeasureLineCache(cm); + clearCaches(cm); updateDisplay(cm, true); }, true); option("electricChars", true); option("theme", "default", function(cm) { - clearMeasureLineCache(cm); themeChanged(cm); guttersChanged(cm); }, true); From 42041769ecc52af08ac4fadf513aabca78706157 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Tue, 4 Dec 2012 12:14:33 +0100 Subject: [PATCH 29/63] Allow multiple iterations when scrolling a position into view New computed heights for intermediate lines might cause the first attempt to not actually scroll the desited position into view. Closes #1043 Closes #1042 --- lib/codemirror.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 33e13e562e..a45a149b3c 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -2132,8 +2132,8 @@ window.CodeMirror = (function() { // SCROLLING function scrollCursorIntoView(cm) { - var view = cm.view, coords = cursorCoords(cm, view.sel.head); - scrollIntoView(cm, coords.left, coords.top, coords.left, coords.bottom); + var view = cm.view; + var coords = scrollPosIntoView(cm, view.sel.head); if (!view.focused) return; var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; if (coords.top + box.top < 0) doScroll = true; @@ -2150,6 +2150,23 @@ window.CodeMirror = (function() { } } + function scrollPosIntoView(cm, pos) { + for (;;) { + var changed = false, coords = cursorCoords(cm, pos); + var scrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom); + var startTop = cm.view.scrollTop, startLeft = cm.view.scrollLeft; + if (scrollPos.scrollTop != null) { + setScrollTop(cm, scrollPos.scrollTop); + if (Math.abs(cm.view.scrollTop - startTop) > 1) changed = true; + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft); + if (Math.abs(cm.view.scrollLeft - startLeft) > 1) changed = true; + } + if (!changed) return coords; + } + } + function scrollIntoView(cm, x1, y1, x2, y2) { var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); @@ -2711,9 +2728,9 @@ window.CodeMirror = (function() { }, scrollIntoView: function(pos) { + if (typeof pos == "number") pos = {line: pos, ch: 0}; pos = pos ? clipPos(this.view.doc, pos) : this.view.sel.head; - var coords = cursorCoords(this, pos); - scrollIntoView(this, coords.left, coords.top, coords.left, coords.bottom); + scrollPosIntoView(this, pos); }, setSize: function(width, height) { From 8cdacf4fc2caeb1a6c8cb161f65a9e3a05440fe6 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 5 Dec 2012 11:53:23 +0100 Subject: [PATCH 30/63] Invalidate max line length when calling .refresh() Closes #1049 --- lib/codemirror.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/codemirror.js b/lib/codemirror.js index a45a149b3c..1ccb31a4c1 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -928,6 +928,7 @@ window.CodeMirror = (function() { function clearCaches(cm) { cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0; cm.display.cachedCharWidth = cm.display.cachedTextHeight = null; + cm.view.maxLineChanged = true; } // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page" From a05aca9bb3981b136689253809a766454a12ecd2 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 5 Dec 2012 12:01:36 +0100 Subject: [PATCH 31/63] [htmlembedded mode] Don't directly call startState and indent on unknown mode It may not provide these methods. Closes #1051 --- mode/htmlembedded/htmlembedded.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mode/htmlembedded/htmlembedded.js b/mode/htmlembedded/htmlembedded.js index b7888689f1..e183d67467 100644 --- a/mode/htmlembedded/htmlembedded.js +++ b/mode/htmlembedded/htmlembedded.js @@ -34,8 +34,8 @@ CodeMirror.defineMode("htmlembedded", function(config, parserConfig) { htmlMixedMode = htmlMixedMode || CodeMirror.getMode(config, "htmlmixed"); return { token : parserConfig.startOpen ? scriptingDispatch : htmlDispatch, - htmlState : htmlMixedMode.startState(), - scriptState : scriptingMode.startState() + htmlState : CodeMirror.startState(htmlMixedMode), + scriptState : CodeMirror.startState(scriptingMode) }; }, @@ -46,7 +46,7 @@ CodeMirror.defineMode("htmlembedded", function(config, parserConfig) { indent: function(state, textAfter) { if (state.token == htmlDispatch) return htmlMixedMode.indent(state.htmlState, textAfter); - else + else if (scriptingMode.indent) return scriptingMode.indent(state.scriptState, textAfter); }, From f7fe5cdc4d3e6568d8286caf17b8a75ba0776a20 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 5 Dec 2012 12:04:34 +0100 Subject: [PATCH 32/63] [css mode] Include leading space in string token for non-quoted url values Issue #1026 --- mode/css/css.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode/css/css.js b/mode/css/css.js index e1fc70b0cb..f4ce9f9d77 100644 --- a/mode/css/css.js +++ b/mode/css/css.js @@ -352,7 +352,7 @@ CodeMirror.defineMode("css", function(config) { // sequence of selectors: // One or more of the named type of selector chained with commas. - if (stream.eatSpace()) return null; + if (state.tokenize == tokenBase && stream.eatSpace()) return null; var style = state.tokenize(stream, state); // Changing style returned based on context From 821cfe1b0f0823906ae296ae3556d21df8ddd601 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 5 Dec 2012 13:56:08 +0100 Subject: [PATCH 33/63] Don't call dataTransfer.setDragImage on Sarari It seems to cause Safari 6.0.2 to follow a null pointer and crash. --- lib/codemirror.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 1ccb31a4c1..d11b0b6040 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -1546,7 +1546,8 @@ window.CodeMirror = (function() { e.dataTransfer.setData("Text", txt); // Use dummy image instead of default browsers image. - if (e.dataTransfer.setDragImage) + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) e.dataTransfer.setDragImage(elt('img'), 0, 0); } From 17cffdf6407de1e8fe533d4db740471327a8b717 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Wed, 5 Dec 2012 15:33:55 +0100 Subject: [PATCH 34/63] Fix typo in manual --- doc/manual.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual.html b/doc/manual.html index 9eef7f3b4a..bb544cdcbd 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -1223,7 +1223,7 @@

    Writing CodeMirror Modes

    CodeMirror. This function takes two arguments. The first should be the name of the mode, for which you should use a lowercase string, preferably one that is also the name of the files that define the - mode (i.e. "xml" is defined xml.js). The + mode (i.e. "xml" is defined in xml.js). The second argument should be a function that, given a CodeMirror configuration object (the thing passed to the CodeMirror function) and an optional mode From 041bb7d1f4e342a4b9809f3542084a34b7a710bd Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Wed, 5 Dec 2012 15:26:13 -0500 Subject: [PATCH 35/63] [vim keymap] Fix yank and paste between files --- demo/vim.html | 10 ++++++ keymap/vim.js | 36 ++++++++++++++++----- test/vim_test.js | 82 +++++++++++++++++++++++++----------------------- 3 files changed, 82 insertions(+), 46 deletions(-) diff --git a/demo/vim.html b/demo/vim.html index 2108b54616..d2a143acf1 100644 --- a/demo/vim.html +++ b/demo/vim.html @@ -35,6 +35,10 @@

    CodeMirror: Vim bindings demo

    } +
    +

    The vim keybindings are enabled by including keymap/vim.js and setting the keyMap option to "vim". Because @@ -49,6 +53,12 @@

    CodeMirror: Vim bindings demo

    keyMap: "vim", showCursorWhenSelecting: true }); + var editor2 = CodeMirror.fromTextArea(document.getElementById("code2"), { + lineNumbers: true, + mode: "text/x-csrc", + keyMap: "vim", + showCursorWhenSelecting: true + }); diff --git a/keymap/vim.js b/keymap/vim.js index 58ccbc3745..da48b1c83f 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -287,6 +287,17 @@ } return false; } + + // Global Vim state. Call getVimGlobalState to get and initialize. + var vimGlobalState; + function getVimGlobalState() { + if (!vimGlobalState) { + vimGlobalState = { + registerController: new RegisterController({}) + } + } + return vimGlobalState; + } function getVimState(cm) { if (!cm.vimState) { // Store instance state in the CodeMirror object. @@ -302,7 +313,6 @@ // executed in between. lastMotion: null, marks: {}, - registerController: new RegisterController({}), visualMode: false, // If we are in visual line mode. No effect if visualMode is false. visualLine: false @@ -319,6 +329,15 @@ buildKeyMap: function() { // TODO: Convert keymap into dictionary format for fast lookup. }, + // Testing hook, though it might be useful to expose the register + // controller anyways. + getRegisterController: function() { + return getVimGlobalState().registerController; + }, + // Testing hook. + _clearVimGlobalState: function() { + vimGlobalState = null; + }, // Initializes vim state variable on the CodeMirror object. Should only be // called lazily by handleKey or for testing. maybeInitState: function(cm) { @@ -932,8 +951,9 @@ var operators = { change: function(cm, operatorArgs, vim, curStart, curEnd) { - vim.registerController.pushText(operatorArgs.registerName, 'change', - cm.getRange(curStart, curEnd), operatorArgs.linewise); + getVimGlobalState().registerController.pushText( + operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd), + operatorArgs.linewise); if (operatorArgs.linewise) { // Delete starting at the first nonwhitespace character of the first // line, instead of from the start of the first line. This way we get @@ -954,8 +974,9 @@ }, // delete is a javascript keyword. 'delete': function(cm, operatorArgs, vim, curStart, curEnd) { - vim.registerController.pushText(operatorArgs.registerName, 'delete', - cm.getRange(curStart, curEnd), operatorArgs.linewise); + getVimGlobalState().registerController.pushText( + operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd), + operatorArgs.linewise); cm.replaceRange('', curStart, curEnd); if (operatorArgs.linewise) { cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); @@ -995,7 +1016,8 @@ cm.setCursor(curOriginal); }, yank: function(cm, operatorArgs, vim, curStart, curEnd, curOriginal) { - vim.registerController.pushText(operatorArgs.registerName, 'yank', + getVimGlobalState().registerController.pushText( + operatorArgs.registerName, 'yank', cm.getRange(curStart, curEnd), operatorArgs.linewise); cm.setCursor(curOriginal); } @@ -1096,7 +1118,7 @@ }, paste: function(cm, actionArgs, vim) { var cur = cm.getCursor(); - var register = vim.registerController.getRegister( + var register = getVimGlobalState().registerController.getRegister( actionArgs.registerName); if (!register.text) { return; diff --git a/test/vim_test.js b/test/vim_test.js index b5e77867c0..ebdcfe2192 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -132,8 +132,12 @@ function testVim(name, run, opts, expectedFail) { var helpers = { doKeys: doKeysFn(cm), assertCursorAt: assertCursorAtFn(cm), - fakeOpenDialog: fakeOpenDialog + fakeOpenDialog: fakeOpenDialog, + getRegisterController: function() { + return CodeMirror.Vim.getRegisterController(); + } } + CodeMirror.Vim._clearVimGlobalState(); var successful = false; try { run(cm, vim, helpers); @@ -262,7 +266,7 @@ testVim('dl', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('d', 'l'); eq('word1 ', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq(' ', register.text); is(!register.linewise); eqPos(curStart, cm.getCursor()); @@ -272,7 +276,7 @@ testVim('dl_eol', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('d', 'l'); eq(' word1', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq(' ', register.text); is(!register.linewise); helpers.assertCursorAt(makeCursor(0, 6)); @@ -282,7 +286,7 @@ testVim('dl_repeat', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('2', 'd', 'l'); eq('ord1 ', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq(' w', register.text); is(!register.linewise); eqPos(curStart, cm.getCursor()); @@ -292,7 +296,7 @@ testVim('dh', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('d', 'h'); eq(' wrd1 ', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('o', register.text); is(!register.linewise); eqPos(offsetCursor(curStart, 0 , -1), cm.getCursor()); @@ -302,7 +306,7 @@ testVim('dj', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('d', 'j'); eq(' word3', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq(' word1\nword2\n', register.text); is(register.linewise); eqPos(makeCursor(0, 1), cm.getCursor()); @@ -312,7 +316,7 @@ testVim('dj_end_of_document', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('d', 'j'); eq(' word1 ', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('', register.text); is(!register.linewise); helpers.assertCursorAt(0, 3); @@ -322,7 +326,7 @@ testVim('dk', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('d', 'k'); eq(' word3', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq(' word1\nword2\n', register.text); is(register.linewise); eqPos(makeCursor(0, 1), cm.getCursor()); @@ -332,7 +336,7 @@ testVim('dk_start_of_document', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('d', 'k'); eq(' word1 ', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('', register.text); is(!register.linewise); helpers.assertCursorAt(0, 3); @@ -342,7 +346,7 @@ testVim('dw_space', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('d', 'w'); eq('word1 ', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq(' ', register.text); is(!register.linewise); eqPos(curStart, cm.getCursor()); @@ -352,7 +356,7 @@ testVim('dw_word', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('d', 'w'); eq(' word2', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('word1 ', register.text); is(!register.linewise); eqPos(curStart, cm.getCursor()); @@ -364,7 +368,7 @@ testVim('dw_only_word', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('d', 'w'); eq(' ', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('word1 ', register.text); is(!register.linewise); eqPos(curStart, cm.getCursor()); @@ -376,7 +380,7 @@ testVim('dw_eol', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('d', 'w'); eq(' \nword2', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('word1', register.text); is(!register.linewise); eqPos(curStart, cm.getCursor()); @@ -388,7 +392,7 @@ testVim('dw_repeat', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('d', '2', 'w'); eq(' ', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('word1\nword2', register.text); is(!register.linewise); eqPos(curStart, cm.getCursor()); @@ -400,7 +404,7 @@ testVim('d_inclusive', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('d', 'e'); eq(' ', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('word1', register.text); is(!register.linewise); eqPos(curStart, cm.getCursor()); @@ -410,7 +414,7 @@ testVim('d_reverse', function(cm, vim, helpers) { cm.setCursor(1, 0); helpers.doKeys('d', 'b'); eq(' word2 ', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('word1\n', register.text); is(!register.linewise); eqPos(makeCursor(0, 1), cm.getCursor()); @@ -422,7 +426,7 @@ testVim('dd', function(cm, vim, helpers) { var expectedLineCount = cm.lineCount() - 1; helpers.doKeys('d', 'd'); eq(expectedLineCount, cm.lineCount()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq(expectedBuffer, register.text); is(register.linewise); eqPos(makeCursor(0, lines[1].textStart), cm.getCursor()); @@ -434,7 +438,7 @@ testVim('dd_prefix_repeat', function(cm, vim, helpers) { var expectedLineCount = cm.lineCount() - 2; helpers.doKeys('2', 'd', 'd'); eq(expectedLineCount, cm.lineCount()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq(expectedBuffer, register.text); is(register.linewise); eqPos(makeCursor(0, lines[2].textStart), cm.getCursor()); @@ -446,7 +450,7 @@ testVim('dd_motion_repeat', function(cm, vim, helpers) { var expectedLineCount = cm.lineCount() - 2; helpers.doKeys('d', '2', 'd'); eq(expectedLineCount, cm.lineCount()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq(expectedBuffer, register.text); is(register.linewise); eqPos(makeCursor(0, lines[2].textStart), cm.getCursor()); @@ -458,7 +462,7 @@ testVim('dd_multiply_repeat', function(cm, vim, helpers) { var expectedLineCount = cm.lineCount() - 6; helpers.doKeys('2', 'd', '3', 'd'); eq(expectedLineCount, cm.lineCount()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq(expectedBuffer, register.text); is(register.linewise); eqPos(makeCursor(0, lines[6].textStart), cm.getCursor()); @@ -472,7 +476,7 @@ testVim('yw_repeat', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('y', '2', 'w'); eq(' word1\nword2', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('word1\nword2', register.text); is(!register.linewise); eqPos(curStart, cm.getCursor()); @@ -485,7 +489,7 @@ testVim('yy_multiply_repeat', function(cm, vim, helpers) { var expectedLineCount = cm.lineCount(); helpers.doKeys('2', 'y', '3', 'y'); eq(expectedLineCount, cm.lineCount()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq(expectedBuffer, register.text); is(register.linewise); eqPos(curStart, cm.getCursor()); @@ -500,7 +504,7 @@ testVim('cw_repeat', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('c', '2', 'w'); eq(' ', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('word1\nword2', register.text); is(!register.linewise); eqPos(curStart, cm.getCursor()); @@ -513,7 +517,7 @@ testVim('cc_multiply_repeat', function(cm, vim, helpers) { var expectedLineCount = cm.lineCount() - 5; helpers.doKeys('2', 'c', '3', 'c'); eq(expectedLineCount, cm.lineCount()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq(expectedBuffer, register.text); is(register.linewise); eqPos(makeCursor(0, lines[0].textStart), cm.getCursor()); @@ -527,7 +531,7 @@ testVim('g~w_repeat', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('g', '~', '2', 'w'); eq(' WORD1\nWORD2', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('', register.text); is(!register.linewise); eqPos(curStart, cm.getCursor()); @@ -539,7 +543,7 @@ testVim('g~g~', function(cm, vim, helpers) { var expectedValue = cm.getValue().toUpperCase(); helpers.doKeys('2', 'g', '~', '3', 'g', '~'); eq(expectedValue, cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('', register.text); is(!register.linewise); eqPos(curStart, cm.getCursor()); @@ -550,7 +554,7 @@ testVim('>{motion}', function(cm, vim, helpers) { var expectedValue = ' word1\n word2\nword3 '; helpers.doKeys('>', 'k'); eq(expectedValue, cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('', register.text); is(!register.linewise); eqPos(makeCursor(0, 3), cm.getCursor()); @@ -561,7 +565,7 @@ testVim('>>', function(cm, vim, helpers) { var expectedValue = ' word1\n word2\nword3 '; helpers.doKeys('2', '>', '>'); eq(expectedValue, cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('', register.text); is(!register.linewise); eqPos(makeCursor(0, 3), cm.getCursor()); @@ -572,7 +576,7 @@ testVim('<{motion}', function(cm, vim, helpers) { var expectedValue = ' word1\nword2\nword3 '; helpers.doKeys('<', 'k'); eq(expectedValue, cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('', register.text); is(!register.linewise); eqPos(makeCursor(0, 1), cm.getCursor()); @@ -583,7 +587,7 @@ testVim('<<', function(cm, vim, helpers) { var expectedValue = ' word1\nword2\nword3 '; helpers.doKeys('2', '<', '<'); eq(expectedValue, cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('', register.text); is(!register.linewise); eqPos(makeCursor(0, 1), cm.getCursor()); @@ -595,7 +599,7 @@ testVim('D', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('D'); eq(' wo\nword2\n word3', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('rd1', register.text); is(!register.linewise); eqPos(makeCursor(0, 3), cm.getCursor()); @@ -605,7 +609,7 @@ testVim('C', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('C'); eq(' wo\nword2\n word3', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('rd1', register.text); is(!register.linewise); eqPos(makeCursor(0, 3), cm.getCursor()); @@ -616,7 +620,7 @@ testVim('Y', function(cm, vim, helpers) { cm.setCursor(curStart); helpers.doKeys('Y'); eq(' word1\nword2\n word3', cm.getValue()); - var register = vim.registerController.getRegister(); + var register = helpers.getRegisterController().getRegister(); eq('rd1', register.text); is(!register.linewise); eqPos(makeCursor(0, 3), cm.getCursor()); @@ -683,42 +687,42 @@ testVim('J_repeat', function(cm, vim, helpers) { }, { value: 'word1 \n word2\nword3\n word4' }); testVim('p', function(cm, vim, helpers) { cm.setCursor(0, 1); - vim.registerController.pushText('"', 'yank', 'abc\ndef', false); + helpers.getRegisterController().pushText('"', 'yank', 'abc\ndef', false); helpers.doKeys('p'); eq('__abc\ndef_', cm.getValue()); eqPos(makeCursor(1, 2), cm.getCursor()); }, { value: '___' }); testVim('p_register', function(cm, vim, helpers) { cm.setCursor(0, 1); - vim.registerController.getRegister('a').set('abc\ndef', false); + helpers.getRegisterController().getRegister('a').set('abc\ndef', false); helpers.doKeys('"', 'a', 'p'); eq('__abc\ndef_', cm.getValue()); eqPos(makeCursor(1, 2), cm.getCursor()); }, { value: '___' }); testVim('p_wrong_register', function(cm, vim, helpers) { cm.setCursor(0, 1); - vim.registerController.getRegister('a').set('abc\ndef', false); + helpers.getRegisterController().getRegister('a').set('abc\ndef', false); helpers.doKeys('p'); eq('___', cm.getValue()); eqPos(makeCursor(0, 1), cm.getCursor()); }, { value: '___' }); testVim('p_line', function(cm, vim, helpers) { cm.setCursor(0, 1); - vim.registerController.pushText('"', 'yank', ' a\nd\n', true); + helpers.getRegisterController().pushText('"', 'yank', ' a\nd\n', true); helpers.doKeys('2', 'p'); eq('___\n a\nd\n a\nd', cm.getValue()); eqPos(makeCursor(1, 2), cm.getCursor()); }, { value: '___' }); testVim('P', function(cm, vim, helpers) { cm.setCursor(0, 1); - vim.registerController.pushText('"', 'yank', 'abc\ndef', false); + helpers.getRegisterController().pushText('"', 'yank', 'abc\ndef', false); helpers.doKeys('P'); eq('_abc\ndef__', cm.getValue()); eqPos(makeCursor(1, 3), cm.getCursor()); }, { value: '___' }); testVim('P_line', function(cm, vim, helpers) { cm.setCursor(0, 1); - vim.registerController.pushText('"', 'yank', ' a\nd\n', true); + helpers.getRegisterController().pushText('"', 'yank', ' a\nd\n', true); helpers.doKeys('2', 'P'); eq(' a\nd\n a\nd\n___', cm.getValue()); eqPos(makeCursor(0, 2), cm.getCursor()); From 623b84f4f3838af4bb28efcdc885765352ca7517 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Thu, 6 Dec 2012 21:00:17 -0500 Subject: [PATCH 36/63] [vim keymap] Put underscores after variable name for private variables --- keymap/vim.js | 8 ++++---- test/vim_test.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index da48b1c83f..6e4b590ed3 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -335,7 +335,7 @@ return getVimGlobalState().registerController; }, // Testing hook. - _clearVimGlobalState: function() { + clearVimGlobalState_: function() { vimGlobalState = null; }, // Initializes vim state variable on the CodeMirror object. Should only be @@ -486,7 +486,7 @@ } else { // Shift down the contents of the numbered registers and put the // deleted text into register 1. - this._shiftNumericRegisters(); + this.shiftNumericRegisters_(); this.registers['1'] = new Register(text, linewise); } break; @@ -523,7 +523,7 @@ isValidRegister: function(name) { return name && inArray(name, validRegisters); }, - _shiftNumericRegisters: function() { + shiftNumericRegisters_: function() { for (var i = 9; i >= 2; i--) { this.registers[i] = this.getRegister('' + (i - 1)); } @@ -1715,7 +1715,7 @@ } function getSearchState(cm) { var vim = getVimState(cm); - return vim._searchState || (vim._searchState = new SearchState()); + return vim.searchState_ || (vim.searchState_ = new SearchState()); } function dialog(cm, text, shortText, callback) { if (cm.openDialog) { diff --git a/test/vim_test.js b/test/vim_test.js index ebdcfe2192..5fe1ab2cf9 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -137,7 +137,7 @@ function testVim(name, run, opts, expectedFail) { return CodeMirror.Vim.getRegisterController(); } } - CodeMirror.Vim._clearVimGlobalState(); + CodeMirror.Vim.clearVimGlobalState_(); var successful = false; try { run(cm, vim, helpers); From 016396a506a5d56f74ffbe06441f666b6a4b1f06 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Thu, 6 Dec 2012 21:57:03 -0500 Subject: [PATCH 37/63] [vim keymap] Make query same across cm instances. Update marks on findNext --- keymap/vim.js | 100 +++++++++++++++++++++++++++++++++-------------- test/vim_test.js | 10 +++-- 2 files changed, 77 insertions(+), 33 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 6e4b590ed3..b6b7d39444 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -207,7 +207,7 @@ { keys: ['Ctrl-r'], type: 'action', action: 'redo' }, { keys: ['m', 'character'], type: 'action', action: 'setMark' }, { keys: ['\"', 'character'], type: 'action', action: 'setRegister' }, - { keys: [',', '/'], type: 'action', action: 'clearSearch' }, + { keys: [',', '/'], type: 'action', action: 'clearSearchHighlight' }, // Text object motions { keys: ['a', 'character'], type: 'motion', motion: 'textObjectManipulation' }, @@ -293,6 +293,10 @@ function getVimGlobalState() { if (!vimGlobalState) { vimGlobalState = { + // The current search query. + searchQuery: null, + // Whether we are searching backwards. + searchIsReversed: false, registerController: new RegisterController({}) } } @@ -655,7 +659,7 @@ return; } var forward = command.searchArgs.forward; - getSearchState(cm).reversed = !forward; + getSearchState(cm).setReversed(!forward); var promptPrefix = (forward) ? '/' : '?'; function handleQuery(query) { updateSearchQuery(cm, query); @@ -672,6 +676,9 @@ var word = expandWordUnderCursor(cm, false /** inclusive */, true /** forward */, false /** bigWord */, true /** noSymbol */); + if (!word) { + return; + } var query = cm.getLine(word.start.line).substring(word.start.ch, word.end.ch + 1); query = '\\b' + query + '\\b'; @@ -1024,7 +1031,7 @@ }; var actions = { - clearSearch: clearSearch, + clearSearchHighlight: clearSearchHighlight, enterInsertMode: function(cm, actionArgs) { var insertAt = (actionArgs) ? actionArgs.insertAt : null; if (insertAt == 'eol') { @@ -1709,10 +1716,29 @@ // Search functions function SearchState() { - this.query = null; - this.marked = []; - this.reversed = false; + // Highlighted text that match the query. + this.marked = null; } + SearchState.prototype = { + getQuery: function() { + return getVimGlobalState().query; + }, + setQuery: function(query) { + getVimGlobalState().query = query; + }, + getMarked: function() { + return this.marked; + }, + setMarked: function(marked) { + this.marked = marked; + }, + isReversed: function() { + return getVimGlobalState().isReversed; + }, + setReversed: function(reversed) { + getVimGlobalState().isReversed = reversed; + } + }; function getSearchState(cm) { var vim = getVimState(cm); return vim.searchState_ || (vim.searchState_ = new SearchState()); @@ -1788,48 +1814,59 @@ } return(false); } - function updateSearchQuery(cm, query) { + function updateSearchQuery(cm, rawQuery) { cm.operation(function() { var state = getSearchState(cm); - if (!query) { + if (!rawQuery) { return; } - var newQuery = parseQuery(cm, query); - if (regexEqual(newQuery, state.query)) { + var query = parseQuery(cm, rawQuery); + if (regexEqual(query, state.getQuery())) { return; } - clearSearch(cm); - state.query = newQuery; - if (!state.query) { + if (!query) { return; } - if (cm.lineCount() < 2000) { // This is too expensive on big documents. - for (var cursor = cm.getSearchCursor(state.query); - cursor.findNext();) { - state.marked.push(cm.markText(cursor.from(), cursor.to(), - { className: 'CodeMirror-searching' })); - } - } + clearSearchHighlight(cm); + highlightSearchMatches(cm, query); + state.setQuery(query); }); } + function highlightSearchMatches(cm, query) { + // TODO: Highlight only text inside the viewport. Highlighting everything + // is inefficient and expensive. + if (cm.lineCount() < 2000) { // This is too expensive on big documents. + var marked = []; + for (var cursor = cm.getSearchCursor(query); + cursor.findNext();) { + marked.push(cm.markText(cursor.from(), cursor.to(), + { className: 'CodeMirror-searching' })); + } + getSearchState(cm).setMarked(marked); + } + } function findNext(cm, prev, repeat) { return cm.operation(function() { var state = getSearchState(cm); - if (!state.query) { + var query = state.getQuery(); + if (!query) { return; } + if (!state.getMarked()) { + highlightSearchMatches(cm, query); + } var pos = cm.getCursor(); // If search is initiated with ? instead of /, negate direction. - prev = (state.reversed) ? !prev : prev; + prev = (state.isReversed()) ? !prev : prev; if (!prev) { pos.ch += 1; } - var cursor = cm.getSearchCursor(state.query, pos); + var cursor = cm.getSearchCursor(query, pos); for (var i = 0; i < repeat; i++) { if (!cursor.find(prev)) { // SearchCursor may have returned null because it hit EOF, wrap // around and try again. - cursor = cm.getSearchCursor(state.query, + cursor = cm.getSearchCursor(query, (prev) ? { line: cm.lineCount() - 1} : {line: 0, ch: 0} ); if (!cursor.find(prev)) { return; @@ -1838,17 +1875,20 @@ } return cursor.from(); });} - function clearSearch(cm) { + function clearSearchHighlight(cm) { cm.operation(function() { var state = getSearchState(cm); - if (!state.query) { + if (!state.getQuery()) { + return; + } + var marked = state.getMarked(); + if (!marked) { return; } - state.query = null; - for (var i = 0; i < state.marked.length; ++i) { - state.marked[i].clear(); + for (var i = 0; i < marked.length; ++i) { + marked[i].clear(); } - state.marked.length = 0; + state.setMarked(null); });} function buildVimKeyMap() { diff --git a/test/vim_test.js b/test/vim_test.js index 5fe1ab2cf9..c2b8bb7441 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -764,12 +764,11 @@ testVim('? and n/N', function(cm, vim, helpers) { helpers.doKeys('2', '?'); helpers.assertCursorAt(makeCursor(0, 11)); }, { value: 'match nope match \n nope Match' }); -testVim(',/ clearSearch', function(cm, vim, helpers) { +testVim(',/ clearSearchHighlight', function(cm, vim, helpers) { cm.openDialog = helpers.fakeOpenDialog('match'); helpers.doKeys('?'); - var origPos = cm.getCursor(); helpers.doKeys(',', '/', 'n'); - helpers.assertCursorAt(origPos); + helpers.assertCursorAt(0, 11); }, { value: 'match nope match \n nope Match' }); testVim('*', function(cm, vim, helpers) { cm.setCursor(0, 9); @@ -780,6 +779,11 @@ testVim('*', function(cm, vim, helpers) { helpers.doKeys('2', '*'); helpers.assertCursorAt(makeCursor(1, 8)); }, { value: 'nomatch match nomatch match \nnomatch Match' }); +testVim('*_no_word', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('*'); + helpers.assertCursorAt(0, 0); +}, { value: ' \n match \n' }); testVim('#', function(cm, vim, helpers) { cm.setCursor(0, 9); helpers.doKeys('#'); From dff894df670d063619c3147a7380829e9dfa0555 Mon Sep 17 00:00:00 2001 From: James Campos Date: Thu, 6 Dec 2012 06:57:13 -0800 Subject: [PATCH 38/63] chmod -x mode/properties/* --- mode/properties/index.html | 0 mode/properties/properties.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 mode/properties/index.html mode change 100755 => 100644 mode/properties/properties.js diff --git a/mode/properties/index.html b/mode/properties/index.html old mode 100755 new mode 100644 diff --git a/mode/properties/properties.js b/mode/properties/properties.js old mode 100755 new mode 100644 From c59d7b2ce51d2dfd34887af83919e1e3da87c0a5 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Wed, 5 Dec 2012 21:41:54 -0500 Subject: [PATCH 39/63] [vim keymap] Improvements to visual mode. - Fix bug where visual mode cannot be entered from EOL. - When already in characterwise visual mode, pressing Shift-V now enters linewise visual mode instead of exiting visual mode. - Mouse selection in normal mode enables visual mode. --- keymap/vim.js | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index b6b7d39444..124a871d33 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -365,6 +365,11 @@ // The selection was cleared. Exit visual mode. exitVisualMode(cm, vim); } + if (!vim.visualMode && + !cursorEqual(cm.getCursor('head'), cm.getCursor('anchor'))) { + vim.visualMode = true; + vim.visualLine = false; + } if (key != '0' || (key == '0' && vim.inputState.getRepeat() === 0)) { // Have to special case 0 since it's both a motion and a number. command = commandDispatcher.matchCommand(key, defaultKeymap, vim); @@ -1047,23 +1052,23 @@ var repeat = actionArgs.repeat; var curStart = cm.getCursor(); var curEnd; - vim.visualLine = !!actionArgs.linewise; // TODO: The repeat should actually select number of characters/lines // equal to the repeat times the size of the previous visual // operation. if (!vim.visualMode) { vim.visualMode = true; + vim.visualLine = !!actionArgs.linewise; if (vim.visualLine) { curStart.ch = 0; curEnd = clipCursorToContent(cm, { line: curStart.line + repeat - 1, ch: lineLength(cm, curStart.line) - }); + }, true /** includeLineBreak */); } else { curEnd = clipCursorToContent(cm, { line: curStart.line, ch: curStart.ch + repeat - }); + }, true /** includeLineBreak */); } // Make the initial selection. if (!actionArgs.repeatIsExplicit && !vim.visualLine) { @@ -1076,7 +1081,20 @@ cm.setSelection(curStart, curEnd); } } else { - exitVisualMode(cm, vim); + if (!vim.visualLine && actionArgs.linewise) { + // Shift-V pressed in characterwise visual mode. Switch to linewise + // visual mode instead of exiting visual mode. + vim.visualLine = true; + curStart = cm.getCursor('anchor'); + curEnd = cm.getCursor('head'); + curStart.ch = cursorIsBefore(curStart, curEnd) ? 0 : + lineLength(cm, curStart.line); + curEnd.ch = cursorIsBefore(curStart, curEnd) ? + lineLength(cm, curEnd.line) : 0; + cm.setSelection(curStart, curEnd); + } else { + exitVisualMode(cm, vim); + } } }, joinLines: function(cm, actionArgs, vim) { @@ -1236,12 +1254,18 @@ * Below are miscellaneous utility functions used by vim.js */ - // Clips cursor @cur to ensure - // that 0 <= cur.ch < line length and - // 0 <= cur.line < total number of lines - function clipCursorToContent(cm, cur) { + /** + * Clips cursor to ensure that: + * 0 <= cur.ch < lineLength + * AND + * 0 <= cur.line < lineCount + * If includeLineBreak is true, then allow cur.ch == lineLength. + */ + function clipCursorToContent(cm, cur, includeLineBreak) { var line = Math.min(Math.max(0, cur.line), cm.lineCount() - 1); - var ch = Math.min(Math.max(0, cur.ch), lineLength(cm, line) - 1); + var maxCh = lineLength(cm, line) - 1; + maxCh = (includeLineBreak) ? maxCh + 1 : maxCh; + var ch = Math.min(Math.max(0, cur.ch), maxCh); return { line: line, ch: ch }; } // Merge arguments in place, for overriding arguments. @@ -1332,7 +1356,7 @@ // Clear the selection and set the cursor only if the selection has not // already been cleared. Otherwise we risk moving the cursor somewhere // it's not supposed to be. - cm.setCursor(selectionEnd); + cm.setCursor(clipCursorToContent(cm, selectionEnd)); } } From a7c53fdd9f4df13693a49d04aea7f398406a7e8d Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Fri, 7 Dec 2012 21:07:58 +0100 Subject: [PATCH 40/63] Don't assume textarea.form will stay the same in fromTextArea Closes #1056 --- lib/codemirror.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index d11b0b6040..27a8b83ae6 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -3101,13 +3101,13 @@ window.CodeMirror = (function() { if (textarea.form) { // Deplorable hack to make the submit method do the right thing. on(textarea.form, "submit", save); - var realSubmit = textarea.form.submit; + var form = textarea.form, realSubmit = form.submit; try { - textarea.form.submit = function wrappedSubmit() { + form.submit = function wrappedSubmit() { save(); - textarea.form.submit = realSubmit; - textarea.form.submit(); - textarea.form.submit = wrappedSubmit; + form.submit = realSubmit; + form.submit(); + form.submit = wrappedSubmit; }; } catch(e) {} } From 99ed2b4f2676553422d24fecb4ea2e0b3953130e Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Dec 2012 10:30:02 +0100 Subject: [PATCH 41/63] Add examples to v3 upgrade guide --- doc/upgrade_v3.html | 57 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/doc/upgrade_v3.html b/doc/upgrade_v3.html index f9a9553e36..3af3ee750d 100644 --- a/doc/upgrade_v3.html +++ b/doc/upgrade_v3.html @@ -35,9 +35,9 @@

    DOM structure

    structure of the editor has changed quite a lot, mostly to implement a new scrolling model.

    -

    Editor height is now set on the outer wrapper (CSS -class CodeMirror) element, not on the scroller -(CodeMirror-scroll) element.

    +

    Editor height is now set on the outer wrapper element (CSS +class CodeMirror), not on the scroller element +(CodeMirror-scroll).

    Other nodes were moved, dropped, and added. If you have any code that makes assumptions about the internal DOM structure of the editor, @@ -64,6 +64,22 @@

    Gutter model

    The fixedGutter option was removed (since it is now the only behavior).

    +
    +<style>
    +  /* Define a gutter style */
    +  .note-gutter { width: 3em; background: cyan; }
    +</style>
    +<script>
    +  // Create an instance with two gutters -- line numbers and notes
    +  var cm = new CodeMirror(document.body, {
    +    gutters: ["note-gutter", "CodeMirror-linenumbers"],
    +    lineNumbers: true
    +  });
    +  // Add a note to line 0
    +  cm.setGutterMarker(0, "note-gutter", document.createTextNode("hi"));
    +</script>
    +
    +

    Event handling

    Most of the onXYZ options have been removed. The same @@ -78,6 +94,12 @@

    Event handling

    which act more as hooks than as event handlers, are still there in their old form.)

    +
    +cm.on("change", function(cm, change) {
    +  console.log("something changed! (" + change.origin + ")");
    +});
    +
    +

    markText method arguments

    The markText method @@ -86,6 +108,15 @@

    markText method arguments

    takes the CSS class name as a separate argument, but makes it an optional field in the options object instead.

    +
    +// Style first ten lines, and forbid the cursor from entering them
    +cm.markText({line: 0, ch: 0}, {line: 10, ch: 0}, {
    +  className: "magic-text",
    +  inclusiveLeft: true,
    +  atomic: true
    +});
    +
    +

    Line folding

    The interface for hiding lines has been @@ -95,6 +126,19 @@

    Line folding

    The folding script has been updated to use the new interface, and should now be more robust.

    +
    +// Fold a range, replacing it with the text "??"
    +var range = cm.markText({line: 4, ch: 2}, {line: 8, ch: 1}, {
    +  replacedWith: document.createTextNode("??"),
    +  // Auto-unfold when cursor moves into the range
    +  clearOnEnter: true
    +});
    +// Get notified when auto-unfolding
    +CodeMirror.on(range, "clear", function() {
    +  console.log("boom");
    +});
    +
    +

    Line CSS classes

    The setLineClass method has been replaced @@ -102,6 +146,13 @@

    Line CSS classes

    and removeLineClass, which allow more modular control over the classes attached to a line.

    +
    +var marked = cm.addLineClass(10, "background", "highlighted-line");
    +setTimeout(function() {
    +  cm.removeLineClass(marked, "background", "highlighted-line");
    +});
    +
    +

    Position properties

    All methods that take or return objects that represent screen From 40e5bae3fcc0b58cb241ac301b08669820fbe647 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Dec 2012 11:12:21 +0100 Subject: [PATCH 42/63] [util/runmode] Move from innerHTML to document.create* To work around IE innerHTML bugs --- lib/util/runmode.js | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/lib/util/runmode.js b/lib/util/runmode.js index 327976badf..3e1bed7361 100644 --- a/lib/util/runmode.js +++ b/lib/util/runmode.js @@ -1,43 +1,44 @@ CodeMirror.runMode = function(string, modespec, callback, options) { - function esc(str) { - return str.replace(/[<&]/g, function(ch) { return ch == "<" ? "<" : "&"; }); - } - var mode = CodeMirror.getMode(CodeMirror.defaults, modespec); - var isNode = callback.nodeType == 1; - var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize; - if (isNode) { - var node = callback, accum = [], col = 0; + + if (callback.nodeType == 1) { + var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize; + var node = callback, col = 0; + node.innerHTML = ""; callback = function(text, style) { if (text == "\n") { - accum.push("
    "); + node.appendChild(document.createElement("br")); col = 0; return; } - var escaped = ""; - // HTML-escape and replace tabs + var content = ""; + // replace tabs for (var pos = 0;;) { var idx = text.indexOf("\t", pos); if (idx == -1) { - escaped += esc(text.slice(pos)); + content += text.slice(pos); col += text.length - pos; break; } else { col += idx - pos; - escaped += esc(text.slice(pos, idx)); + content += text.slice(pos, idx); var size = tabSize - col % tabSize; col += size; - for (var i = 0; i < size; ++i) escaped += " "; + for (var i = 0; i < size; ++i) content += " "; pos = idx + 1; } } - if (style) - accum.push("" + escaped + ""); - else - accum.push(escaped); + if (style) { + var sp = node.appendChild(document.createElement("span")); + sp.className = "cm-" + style.replace(/ +/g, " cm-"); + sp.appendChild(document.createTextNode(content)); + } else { + node.appendChild(document.createTextNode(content)); + } }; } + var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode); for (var i = 0, e = lines.length; i < e; ++i) { if (i) callback("\n"); @@ -48,6 +49,4 @@ CodeMirror.runMode = function(string, modespec, callback, options) { stream.start = stream.pos; } } - if (isNode) - node.innerHTML = accum.join(""); }; From 4bfa0b594f912045579b5700e7043cdfdeb9cc9b Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Dec 2012 11:13:10 +0100 Subject: [PATCH 43/63] Add a syntax highlighting shim for PRE tags, use in manual --- doc/manual.html | 20 +++++++++++++++----- doc/upgrade_v3.html | 19 ++++++++++++++----- lib/util/colorize.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 lib/util/colorize.js diff --git a/doc/manual.html b/doc/manual.html index bb544cdcbd..811acaee45 100644 --- a/doc/manual.html +++ b/doc/manual.html @@ -6,6 +6,14 @@ + + + + + + + + @@ -48,21 +56,21 @@

    Basic Usage

    (See the compression helper for an easy way to combine scripts.) For example:

    -
    <script src="lib/codemirror.js"></script>
    +    
    <script src="lib/codemirror.js"></script>
     <link rel="stylesheet" href="../lib/codemirror.css">
     <script src="mode/javascript/javascript.js"></script>

    Having done this, an editor instance can be created like this:

    -
    var myCodeMirror = CodeMirror(document.body);
    +
    var myCodeMirror = CodeMirror(document.body);

    The editor will be appended to the document body, will start empty, and will use the mode that we loaded. To have more control over the new editor, a configuration object can be passed to CodeMirror as a second argument:

    -
    var myCodeMirror = CodeMirror(document.body, {
    +    
    var myCodeMirror = CodeMirror(document.body, {
       value: "function myScript(){return 100;}\n",
       mode:  "javascript"
     });
    @@ -80,7 +88,7 @@

    Basic Usage

    document somewhere. This could be used to, for example, replace a textarea with a real editor:

    -
    var myCodeMirror = CodeMirror(function(elt) {
    +    
    var myCodeMirror = CodeMirror(function(elt) {
       myTextArea.parentNode.replaceChild(elt, myTextArea);
     }, {value: myTextArea.value});
    @@ -88,7 +96,7 @@

    Basic Usage

    CodeMirror, the library provides a much more powerful shortcut:

    -
    var myCodeMirror = CodeMirror.fromTextArea(myTextArea);
    +
    var myCodeMirror = CodeMirror.fromTextArea(myTextArea);

    This will, among other things, ensure that the textarea's value is updated with the editor's contents when the form (if it is part @@ -1449,5 +1457,7 @@

    Contents

     
    + + diff --git a/doc/upgrade_v3.html b/doc/upgrade_v3.html index 3af3ee750d..eaaffec17f 100644 --- a/doc/upgrade_v3.html +++ b/doc/upgrade_v3.html @@ -5,6 +5,14 @@ CodeMirror: Upgrading to v3 + + + + + + + + @@ -64,7 +72,7 @@

    Gutter model

    The fixedGutter option was removed (since it is now the only behavior).

    -
    +
     <style>
       /* Define a gutter style */
       .note-gutter { width: 3em; background: cyan; }
    @@ -94,7 +102,7 @@ 

    Event handling

    which act more as hooks than as event handlers, are still there in their old form.)

    -
    +
     cm.on("change", function(cm, change) {
       console.log("something changed! (" + change.origin + ")");
     });
    @@ -108,7 +116,7 @@ 

    markText method arguments

    takes the CSS class name as a separate argument, but makes it an optional field in the options object instead.

    -
    +
     // Style first ten lines, and forbid the cursor from entering them
     cm.markText({line: 0, ch: 0}, {line: 10, ch: 0}, {
       className: "magic-text",
    @@ -126,7 +134,7 @@ 

    Line folding

    The folding script has been updated to use the new interface, and should now be more robust.

    -
    +
     // Fold a range, replacing it with the text "??"
     var range = cm.markText({line: 4, ch: 2}, {line: 8, ch: 1}, {
       replacedWith: document.createTextNode("??"),
    @@ -146,7 +154,7 @@ 

    Line CSS classes

    and removeLineClass, which allow more modular control over the classes attached to a line.

    -
    +
     var marked = cm.addLineClass(10, "background", "highlighted-line");
     setTimeout(function() {
       cm.removeLineClass(marked, "background", "highlighted-line");
    @@ -214,5 +222,6 @@ 

    Contents

    + diff --git a/lib/util/colorize.js b/lib/util/colorize.js new file mode 100644 index 0000000000..62286d21e4 --- /dev/null +++ b/lib/util/colorize.js @@ -0,0 +1,29 @@ +CodeMirror.colorize = (function() { + + var isBlock = /^(p|li|div|h\\d|pre|blockquote|td)$/; + + function textContent(node, out) { + if (node.nodeType == 3) return out.push(node.nodeValue); + for (var ch = node.firstChild; ch; ch = ch.nextSibling) { + textContent(ch, out); + if (isBlock.test(node.nodeType)) out.push("\n"); + } + } + + return function(collection, defaultMode) { + if (!collection) collection = document.body.getElementsByTagName("pre"); + + for (var i = 0; i < collection.length; ++i) { + var node = collection[i]; + var mode = node.getAttribute("data-lang") || defaultMode; + if (!mode) continue; + + var text = []; + textContent(node, text); + node.innerHTML = ""; + CodeMirror.runMode(text.join(""), mode, node); + + node.className += " cm-s-default"; + } + }; +})(); From 3e088ab300484b3a1880f22cf7b13892b6ae2715 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Dec 2012 11:31:34 +0100 Subject: [PATCH 44/63] [clike mode] Add statementIndentUnit config option To specify the amount that continued statements have to be indented. Closes #1053 --- mode/clike/clike.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mode/clike/clike.js b/mode/clike/clike.js index 2b0c64dc5f..4d1484a708 100644 --- a/mode/clike/clike.js +++ b/mode/clike/clike.js @@ -1,5 +1,6 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { var indentUnit = config.indentUnit, + statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, keywords = parserConfig.keywords || {}, builtin = parserConfig.builtin || {}, blockKeywords = parserConfig.blockKeywords || {}, @@ -89,7 +90,10 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { this.prev = prev; } function pushContext(state, col, type) { - return state.context = new Context(state.indented, col, type, null, state.context); + var indent = state.indented; + if (state.context && state.context.type == "statement") + indent = state.context.indented; + return state.context = new Context(indent, col, type, null, state.context); } function popContext(state) { var t = state.context.type; @@ -144,7 +148,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) { var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; var closing = firstChar == ctx.type; - if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit); + if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); else if (ctx.align) return ctx.column + (closing ? 0 : 1); else return ctx.indented + (closing ? 0 : indentUnit); }, From 0682373fb8ea950d1e44a2e4af5b60631f7248a7 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Sat, 8 Dec 2012 13:45:40 -0500 Subject: [PATCH 45/63] Add an option to show dialog at the bottom. --- lib/util/dialog.css | 19 ++++++++++++------- lib/util/dialog.js | 23 ++++++++++++++--------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/lib/util/dialog.css b/lib/util/dialog.css index 8c4f847920..2e7c0fc9b8 100644 --- a/lib/util/dialog.css +++ b/lib/util/dialog.css @@ -1,18 +1,23 @@ .CodeMirror-dialog { - position: relative; -} - -.CodeMirror-dialog > div { position: absolute; - top: 0; left: 0; right: 0; + left: 0; right: 0; background: white; - border-bottom: 1px solid #eee; z-index: 15; padding: .1em .8em; overflow: hidden; color: #333; } +.CodeMirror-dialog-top { + border-bottom: 1px solid #eee; + top: 0; +} + +.CodeMirror-dialog-bottom { + border-top: 1px solid #eee; + bottom: 0; +} + .CodeMirror-dialog input { border: none; outline: none; @@ -24,4 +29,4 @@ .CodeMirror-dialog button { font-size: 70%; -} \ No newline at end of file +} diff --git a/lib/util/dialog.js b/lib/util/dialog.js index 1d4751623b..845a3ea946 100644 --- a/lib/util/dialog.js +++ b/lib/util/dialog.js @@ -1,16 +1,21 @@ // Open simple dialogs on top of an editor. Relies on dialog.css. (function() { - function dialogDiv(cm, template) { + function dialogDiv(cm, template, bottom) { var wrap = cm.getWrapperElement(); - var dialog = wrap.insertBefore(document.createElement("div"), wrap.firstChild); - dialog.className = "CodeMirror-dialog"; - dialog.innerHTML = '
    ' + template + '
    '; + var dialog; + dialog = wrap.appendChild(document.createElement("div")); + if (bottom) { + dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom"; + } else { + dialog.className = "CodeMirror-dialog CodeMirror-dialog-top"; + } + dialog.innerHTML = template; return dialog; } - CodeMirror.defineExtension("openDialog", function(template, callback) { - var dialog = dialogDiv(this, template); + CodeMirror.defineExtension("openDialog", function(template, callback, bottom) { + var dialog = dialogDiv(this, template, bottom); var closed = false, me = this; function close() { if (closed) return; @@ -40,8 +45,8 @@ return close; }); - CodeMirror.defineExtension("openConfirm", function(template, callbacks) { - var dialog = dialogDiv(this, template); + CodeMirror.defineExtension("openConfirm", function(template, callbacks, bottom) { + var dialog = dialogDiv(this, template, bottom); var buttons = dialog.getElementsByTagName("button"); var closed = false, me = this, blurring = 1; function close() { @@ -67,4 +72,4 @@ CodeMirror.on(b, "focus", function() { ++blurring; }); } }); -})(); \ No newline at end of file +})(); From 38d0c3240ee3f1d7db98fb233ed22c048c4c16c5 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Sat, 8 Dec 2012 13:47:18 -0500 Subject: [PATCH 46/63] [vim keymap] Move dialog to bottom of editor --- keymap/vim.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 124a871d33..27c97a304e 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1769,7 +1769,7 @@ } function dialog(cm, text, shortText, callback) { if (cm.openDialog) { - cm.openDialog(text, callback); + cm.openDialog(text, callback, true /** bottom */); } else { callback(prompt(shortText, "")); @@ -1801,7 +1801,8 @@ function showConfirm(cm, text) { if (cm.openConfirm) { cm.openConfirm('' + text + - ' ', function() {}); + ' ', function() {}, + true /** bottom */); } else { alert(text); } @@ -1809,9 +1810,9 @@ function makePrompt(prefix, desc) { var raw = ''; if (prefix) { - raw += prefix; + raw += '' + prefix + ''; } - raw += ' ' + + raw += ' ' + ''; if (desc) { raw += ''; From 0a83f784c5e635abc6c77fbc9a97f1e690c58a2f Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Dec 2012 11:39:35 +0100 Subject: [PATCH 47/63] Make dialog methods take an option object instead of a boolean parameter Issue #1065 --- keymap/vim.js | 4 ++-- lib/util/dialog.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 27c97a304e..659a6658e9 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -1769,7 +1769,7 @@ } function dialog(cm, text, shortText, callback) { if (cm.openDialog) { - cm.openDialog(text, callback, true /** bottom */); + cm.openDialog(text, callback, {bottom: true}); } else { callback(prompt(shortText, "")); @@ -1802,7 +1802,7 @@ if (cm.openConfirm) { cm.openConfirm('' + text + ' ', function() {}, - true /** bottom */); + {bottom: true}); } else { alert(text); } diff --git a/lib/util/dialog.js b/lib/util/dialog.js index 845a3ea946..380b80455c 100644 --- a/lib/util/dialog.js +++ b/lib/util/dialog.js @@ -14,8 +14,8 @@ return dialog; } - CodeMirror.defineExtension("openDialog", function(template, callback, bottom) { - var dialog = dialogDiv(this, template, bottom); + CodeMirror.defineExtension("openDialog", function(template, callback, options) { + var dialog = dialogDiv(this, template, options && options.bottom); var closed = false, me = this; function close() { if (closed) return; @@ -45,8 +45,8 @@ return close; }); - CodeMirror.defineExtension("openConfirm", function(template, callbacks, bottom) { - var dialog = dialogDiv(this, template, bottom); + CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) { + var dialog = dialogDiv(this, template, options && options.bottom); var buttons = dialog.getElementsByTagName("button"); var closed = false, me = this, blurring = 1; function close() { From 6da60252625bd1a24be688834d11c6f5205434ad Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Sat, 8 Dec 2012 13:21:12 -0500 Subject: [PATCH 48/63] [vim keymap] Improvements to * and #. * and # will fall back to non-blank characters if no keyword is found. --- keymap/vim.js | 91 +++++++++++++++++++++++++++++++++++------------- test/vim_test.js | 5 +++ 2 files changed, 72 insertions(+), 24 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index 659a6658e9..aae8a29e44 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -298,7 +298,7 @@ // Whether we are searching backwards. searchIsReversed: false, registerController: new RegisterController({}) - } + }; } return vimGlobalState; } @@ -666,29 +666,43 @@ var forward = command.searchArgs.forward; getSearchState(cm).setReversed(!forward); var promptPrefix = (forward) ? '/' : '?'; - function handleQuery(query) { - updateSearchQuery(cm, query); + function handleQuery(query, ignoreCase, smartCase) { + updateSearchQuery(cm, query, ignoreCase, smartCase); commandDispatcher.processMotion(cm, vim, { type: 'motion', motion: 'findNext' }); } + function onPromptClose(query) { + handleQuery(query, true /** ignoreCase */, true /** smartCase */); + } switch (command.searchArgs.querySrc) { case 'prompt': - showPrompt(cm, handleQuery, promptPrefix, searchPromptDesc); + showPrompt(cm, onPromptClose, promptPrefix, searchPromptDesc); break; case 'wordUnderCursor': var word = expandWordUnderCursor(cm, false /** inclusive */, true /** forward */, false /** bigWord */, true /** noSymbol */); + var isKeyword = true; + if (!word) { + word = expandWordUnderCursor(cm, false /** inclusive */, + true /** forward */, false /** bigWord */, + false /** noSymbol */); + isKeyword = false; + } if (!word) { return; } var query = cm.getLine(word.start.line).substring(word.start.ch, word.end.ch + 1); - query = '\\b' + query + '\\b'; + if (isKeyword) { + query = '\\b' + query + '\\b'; + } else { + query = escapeRegex(query); + } cm.setCursor(word.start); - handleQuery(query); + handleQuery(query, true /** ignoreCase */, false /** smartCase */); break; } }, @@ -1344,7 +1358,7 @@ return s.split("").reverse().join(""); } function escapeRegex(s) { - return s.replace(/([.?*+\^$\[\]\\(){}|\-])/g, "\\$1"); + return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, "\\$1"); } function exitVisualMode(cm, vim) { @@ -1775,27 +1789,56 @@ callback(prompt(shortText, "")); } } - function parseQuery(cm, query) { + function findUnescapedSlashes(str) { + var escapeNextChar = false; + var slashes = []; + for (var i = 0; i < str.length; i++) { + var c = str.charAt(i); + if (!escapeNextChar && c == '/') { + slashes.push(i); + } + escapeNextChar = (c == '\\'); + } + return slashes; + } + /** + * Extract the regular expression from the query and return a Regexp object. + * Returns null if the query is blank. + * If ignoreCase is passed in, the Regexp object will have the 'i' flag set. + * If smartCase is passed in, and the query contains upper case letters, + * then ignoreCase is overridden, and the 'i' flag will not be set. + * If the query contains the /i in the flag part of the regular expression, + * then both ignoreCase and smartCase are ignored, and 'i' will be passed + * through to the Regex object. + */ + function parseQuery(cm, query, ignoreCase, smartCase) { // First try to extract regex + flags from the input. If no flags found, // extract just the regex. IE does not accept flags directly defined in // the regex string in the form /regex/flags - var match = query.match(/^(.*)\/(.*)$/); - var insensitive = false; - var query_regex; - if (match) { - insensitive = (match[2].indexOf('i') != -1); - query_regex = match[1]; + var slashes = findUnescapedSlashes(query); + var regexPart; + var forceIgnoreCase; + if (!slashes.length) { + // Query looks like 'regexp' + regexPart = query; } else { - query_regex = query; + // Query looks like 'regexp/...' + regexPart = query.substring(0, slashes[0]); + var flagsPart = query.substring(slashes[0]); + forceIgnoreCase = (flagsPart.indexOf('i') != -1); + } + if (!regexPart) { + return null; + } + if (smartCase) { + ignoreCase = (/^[^A-Z]*$/).test(regexPart); } - // Heuristic: if the query string is all lowercase, do a case-insensitive - // search. - insensitive = insensitive || (/^[^A-Z]*$/).test(query_regex); try { - var regexp = new RegExp(query_regex, insensitive ? 'i' : undefined); + var regexp = new RegExp(regexPart, + (ignoreCase || forceIgnoreCase) ? 'i' : undefined); return regexp; } catch (e) { - showConfirm(cm, 'Invalid regex: ' + query_regex); + showConfirm(cm, 'Invalid regex: ' + regexPart); } } function showConfirm(cm, text) { @@ -1839,17 +1882,17 @@ } return(false); } - function updateSearchQuery(cm, rawQuery) { + function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) { cm.operation(function() { var state = getSearchState(cm); if (!rawQuery) { return; } - var query = parseQuery(cm, rawQuery); - if (regexEqual(query, state.getQuery())) { + var query = parseQuery(cm, rawQuery, !!ignoreCase, !!smartCase); + if (!query) { return; } - if (!query) { + if (regexEqual(query, state.getQuery())) { return; } clearSearchHighlight(cm); diff --git a/test/vim_test.js b/test/vim_test.js index c2b8bb7441..f8c13d64a4 100644 --- a/test/vim_test.js +++ b/test/vim_test.js @@ -784,6 +784,11 @@ testVim('*_no_word', function(cm, vim, helpers) { helpers.doKeys('*'); helpers.assertCursorAt(0, 0); }, { value: ' \n match \n' }); +testVim('*_symbol', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('*'); + helpers.assertCursorAt(1, 0); +}, { value: ' /}\n/} match \n' }); testVim('#', function(cm, vim, helpers) { cm.setCursor(0, 9); helpers.doKeys('#'); From fb4b667fb4c1b61cac86cfd6344ed70dce25571b Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Sat, 8 Dec 2012 17:29:20 -0500 Subject: [PATCH 49/63] [vim keymap] Rudimentary Ex command implementation --- keymap/vim.js | 130 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 4 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index aae8a29e44..b400606a41 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -121,10 +121,10 @@ motion: 'moveByPage', motionArgs: { forward: false }}, { keys: ['g', 'g'], type: 'motion', motion: 'moveToLineOrEdgeOfDocument', - motionArgs: { forward: false, explicitRepeat: true }}, + motionArgs: { forward: false, explicitRepeat: true, linewise: true }}, { keys: ['G'], type: 'motion', motion: 'moveToLineOrEdgeOfDocument', - motionArgs: { forward: true, explicitRepeat: true }}, + motionArgs: { forward: true, explicitRepeat: true, linewise: true }}, { keys: ['0'], type: 'motion', motion: 'moveToStartOfLine' }, { keys: ['^'], type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' }, @@ -222,7 +222,9 @@ { keys: ['*'], type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor' }}, { keys: ['#'], type: 'search', - searchArgs: { forward: false, querySrc: 'wordUnderCursor' }} + searchArgs: { forward: false, querySrc: 'wordUnderCursor' }}, + // Ex command + { keys: [':'], type: 'ex' }, ]; var Vim = function() { @@ -588,6 +590,8 @@ case 'search': this.processSearch(cm, vim, command); break; + case 'ex': + this.processEx(cm, vim, command); default: break; } @@ -706,6 +710,12 @@ break; } }, + processEx: function(cm, vim, command) { + function onPromptClose(input) { + exCommandDispatcher.processCommand(cm, input); + } + showPrompt(cm, onPromptClose, ':'); + }, evalInput: function(cm, vim) { // If the motion comand is set, execute both the operator and motion. // Otherwise return. @@ -722,7 +732,14 @@ var curStart = copyCursor(selectionEnd); var curOriginal = copyCursor(curStart); var curEnd; - var repeat = inputState.getRepeat(); + var repeat; + if (motionArgs.repeat !== undefined) { + // If motionArgs specifies a repeat, that takes precedence over the + // input state's repeat. Used by Ex mode and can be user defined. + repeat = inputState.motionArgs.repeat; + } else { + repeat = inputState.getRepeat(); + } if (repeat > 0 && motionArgs.explicitRepeat) { motionArgs.repeatIsExplicit = true; } else if (motionArgs.noRepeat || @@ -1395,6 +1412,9 @@ } function findFirstNonWhiteSpaceCharacter(text) { + if (!text) { + return 0; + } var firstNonWS = text.search(/\S/); return firstNonWS == -1 ? text.length : firstNonWS; } @@ -1959,6 +1979,108 @@ state.setMarked(null); });} + // Ex command handling + // Care must be taken when adding to the default Ex command map. For any + // pair of commands that have a shared prefix, at least one of their + // shortNames must not match the prefix of the other command. + var defaultExCommandMap = [ + { name: 'write', shortName: 'w' }, + { name: 'undo', shortName: 'u' }, + { name: 'redo', shortName: 'red' } + ]; + var ExCommandDispatcher = function() { + this.buildCommandMap_(); + }; + ExCommandDispatcher.prototype = { + processCommand: function(cm, input) { + var parsedCommand = this.parseInput_(input); + var commandName; + if (!parsedCommand.commandName) { + // If only a line range is defined, move to the line. + if (parsedCommand.line !== undefined) { + commandName = 'move'; + } + } else { + var command = this.matchCommand_(parsedCommand.commandName); + if (command) { + commandName = command.name; + } + } + if (!commandName) { + showConfirm(cm, 'Not an editor command: ' + input); + return; + } + exCommands[commandName](cm, parsedCommand); + }, + parseInput_: function(input) { + var result = {}; + // Trim preceding ':'. + var colons = (/^:+/).exec(input); + if (colons) { + input = input.substring(colons[0].length); + } + + // Parse range. + var numberMatch = (/^(\d+)/).exec(input); + if (numberMatch) { + result['line'] = parseInt(numberMatch[1]); + input = input.substring(numberMatch[0].length); + } + // Parse command name. + var commandMatch = (/(\w+)/).exec(input); + if (commandMatch) { + result['commandName'] = commandMatch[1]; + } + return result; + }, + matchCommand_: function(commandName) { + // Return the command in the command map that matches the shortest + // prefix of the passed in command name. The match is guaranteed to be + // unambiguous if the defaultExCommandMap's shortNames are set up + // correctly. (see @code{defaultExCommandMap}). + for (var i = 1; i <= commandName.length; i++) { + var prefix = commandName.substring(0, i); + if (this.commandMap_[prefix]) { + var command = this.commandMap_[prefix]; + if (command.name.indexOf(commandName) === 0) { + return command; + } + } + } + return null; + }, + buildCommandMap_: function() { + this.commandMap_ = {}; + for (var i = 0; i < defaultExCommandMap.length; i++) { + var command = defaultExCommandMap[i]; + this.commandMap_[command.shortName] = command; + } + } + }; + + var exCommands = { + move: function(cm, args) { + commandDispatcher.processMotion(cm, getVimState(cm), { + motion: 'moveToLineOrEdgeOfDocument', + motionArgs: { forward: false, explicitRepeat: true, + linewise: true, repeat: args.line }}); + }, + redo: CodeMirror.commands.redo, + undo: CodeMirror.commands.undo, + write: function(cm) { + if (CodeMirror.commands.save) { + // If a save command is defined, call it. + CodeMirror.commands.save(cm); + } else { + // Saves to text area if no save command is defined. + cm.save(); + } + } + }; + + var exCommandDispatcher = new ExCommandDispatcher(); + + // Register Vim with CodeMirror function buildVimKeyMap() { /** * Handle the raw key event from CodeMirror. Translate the From 2f3d73602b08fd9b12afdcc8855cee8a8193e471 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Sun, 9 Dec 2012 19:54:50 -0500 Subject: [PATCH 50/63] [vim keymap] Add basic :map command --- keymap/vim.js | 131 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 106 insertions(+), 25 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index b400606a41..e1e957acdc 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -224,7 +224,7 @@ { keys: ['#'], type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor' }}, // Ex command - { keys: [':'], type: 'ex' }, + { keys: [':'], type: 'ex' } ]; var Vim = function() { @@ -328,10 +328,6 @@ } var vimApi= { - addKeyMap: function() { - // Add user defined key bindings. - // TODO: Implement this. - }, buildKeyMap: function() { // TODO: Convert keymap into dictionary format for fast lookup. }, @@ -344,6 +340,10 @@ clearVimGlobalState_: function() { vimGlobalState = null; }, + map: function(lhs, rhs) { + // Add user defined key bindings. + exCommandDispatcher.map(lhs, rhs); + }, // Initializes vim state variable on the CodeMirror object. Should only be // called lazily by handleKey or for testing. maybeInitState: function(cm) { @@ -591,7 +591,9 @@ this.processSearch(cm, vim, command); break; case 'ex': + case 'keyToEx': this.processEx(cm, vim, command); + break; default: break; } @@ -714,7 +716,12 @@ function onPromptClose(input) { exCommandDispatcher.processCommand(cm, input); } - showPrompt(cm, onPromptClose, ':'); + if (command.type == 'keyToEx') { + // Handle user defined Ex to Ex mappings + exCommandDispatcher.processCommand(cm, command.exArgs.input); + } else { + showPrompt(cm, onPromptClose, ':'); + } }, evalInput: function(cm, vim) { // If the motion comand is set, execute both the operator and motion. @@ -1374,6 +1381,13 @@ function reverse(s){ return s.split("").reverse().join(""); } + function trim(s) { + if (s.trim) { + return s.trim(); + } else { + return s.replace(/^\s+|\s+$/g, ''); + } + } function escapeRegex(s) { return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, "\\$1"); } @@ -1984,53 +1998,76 @@ // pair of commands that have a shared prefix, at least one of their // shortNames must not match the prefix of the other command. var defaultExCommandMap = [ - { name: 'write', shortName: 'w' }, - { name: 'undo', shortName: 'u' }, - { name: 'redo', shortName: 'red' } + { name: 'map', type: 'builtIn' }, + { name: 'write', shortName: 'w', type: 'builtIn' }, + { name: 'undo', shortName: 'u', type: 'builtIn' }, + { name: 'redo', shortName: 'red', type: 'builtIn' } ]; var ExCommandDispatcher = function() { this.buildCommandMap_(); }; ExCommandDispatcher.prototype = { processCommand: function(cm, input) { - var parsedCommand = this.parseInput_(input); + var params = this.parseInput_(input); var commandName; - if (!parsedCommand.commandName) { + if (!params.commandName) { // If only a line range is defined, move to the line. - if (parsedCommand.line !== undefined) { + if (params.line !== undefined) { commandName = 'move'; } } else { - var command = this.matchCommand_(parsedCommand.commandName); + var command = this.matchCommand_(params.commandName); if (command) { commandName = command.name; + if (command.type == 'exToKey') { + // Handle Ex to Key mapping. + for (var i = 0; i < command.toKeys.length; i++) { + vim.handleKey(cm, command.toKeys[i]); + } + return; + } else if (command.type == 'exToEx') { + // Handle Ex to Ex mapping. + this.processCommand(cm, command.toInput); + return; + } } } if (!commandName) { - showConfirm(cm, 'Not an editor command: ' + input); + showConfirm(cm, 'Not an editor command ":' + input + '"'); return; } - exCommands[commandName](cm, parsedCommand); + exCommands[commandName](cm, params); }, parseInput_: function(input) { var result = {}; + result.input = input; + var idx = 0; // Trim preceding ':'. var colons = (/^:+/).exec(input); if (colons) { - input = input.substring(colons[0].length); + idx += colons[0].length; } // Parse range. - var numberMatch = (/^(\d+)/).exec(input); + var numberMatch = (/^(\d+)/).exec(input.substring(idx)); if (numberMatch) { - result['line'] = parseInt(numberMatch[1]); - input = input.substring(numberMatch[0].length); + result.line = parseInt(numberMatch[1], 10); + idx += numberMatch[0].length; } + // Parse command name. - var commandMatch = (/(\w+)/).exec(input); + var commandMatch = (/^(\w+)/).exec(input.substring(idx)); if (commandMatch) { - result['commandName'] = commandMatch[1]; + result.commandName = commandMatch[1]; + idx += commandMatch[1].length; + } + + // Parse command-line arguments + var args = trim(input.substring(idx)).split(/\s+/); + if (args.length && args[0]) { + result.commandArgs = args; } + return result; }, matchCommand_: function(commandName) { @@ -2038,7 +2075,7 @@ // prefix of the passed in command name. The match is guaranteed to be // unambiguous if the defaultExCommandMap's shortNames are set up // correctly. (see @code{defaultExCommandMap}). - for (var i = 1; i <= commandName.length; i++) { + for (var i = commandName.length; i > 0; i--) { var prefix = commandName.substring(0, i); if (this.commandMap_[prefix]) { var command = this.commandMap_[prefix]; @@ -2053,17 +2090,61 @@ this.commandMap_ = {}; for (var i = 0; i < defaultExCommandMap.length; i++) { var command = defaultExCommandMap[i]; - this.commandMap_[command.shortName] = command; + var key = command.shortName || command.name; + this.commandMap_[key] = command; + } + }, + map: function(lhs, rhs) { + if (lhs.charAt(0) == ':') { + var commandName = lhs.substring(1); + if (rhs.charAt(0) == ':') { + // Ex to Ex mapping + this.commandMap_[commandName] = { + name: commandName, + type: 'exToEx', + toInput: rhs.substring(1) + }; + } else { + // Ex to key mapping + this.commandMap_[commandName] = { + name: commandName, + type: 'exToKey', + toKeys: rhs + }; + } + } else { + if (rhs.charAt(0) == ':') { + // Key to Ex mapping. + defaultKeymap.unshift({ + keys: lhs.split(''), + type: 'keyToEx', + exArgs: { input: rhs.substring(1) }}); + } else { + // Key to key mapping + defaultKeymap.unshift({ + keys: lhs.split(''), type: 'keyToKey', toKeys: rhs.split('') + }); + } } } }; var exCommands = { - move: function(cm, args) { + map: function(cm, params) { + var mapArgs = params.commandArgs; + if (!mapArgs || mapArgs.length < 2) { + if (cm) { + showConfirm(cm, 'Invalid mapping: ' + params.input); + } + return; + } + exCommandDispatcher.map(mapArgs[0], mapArgs[1], cm); + }, + move: function(cm, params) { commandDispatcher.processMotion(cm, getVimState(cm), { motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, - linewise: true, repeat: args.line }}); + linewise: true, repeat: params.line }}); }, redo: CodeMirror.commands.redo, undo: CodeMirror.commands.undo, From ee4b750b272a54ba101042b8f4e72d559ae02d69 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Sun, 9 Dec 2012 20:51:28 -0500 Subject: [PATCH 51/63] [vim keymap] Add Vim key notation support --- keymap/vim.js | 47 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/keymap/vim.js b/keymap/vim.js index e1e957acdc..aa1856518b 100644 --- a/keymap/vim.js +++ b/keymap/vim.js @@ -2109,26 +2109,67 @@ this.commandMap_[commandName] = { name: commandName, type: 'exToKey', - toKeys: rhs + toKeys: parseKeyString(rhs) }; } } else { if (rhs.charAt(0) == ':') { // Key to Ex mapping. defaultKeymap.unshift({ - keys: lhs.split(''), + keys: parseKeyString(lhs), type: 'keyToEx', exArgs: { input: rhs.substring(1) }}); } else { // Key to key mapping defaultKeymap.unshift({ - keys: lhs.split(''), type: 'keyToKey', toKeys: rhs.split('') + keys: parseKeyString(lhs), + type: 'keyToKey', + toKeys: parseKeyString(rhs) }); } } } }; + // Converts a key string sequence of the form abd into Vim's + // keymap representation. + function parseKeyString(str) { + var idx = 0; + var keys = []; + while (idx < str.length) { + if (str.charAt(idx) != '<') { + keys.push(str.charAt(idx)); + idx++; + continue; + } + // Vim key notation here means desktop Vim key-notation. + // See :help key-notation in desktop Vim. + var vimKeyNotationStart = ++idx; + while (str.charAt(idx++) != '>') {} + var vimKeyNotation = str.substring(vimKeyNotationStart, idx - 1); + var match = (/^C-(.+)$/).exec(vimKeyNotation); + if (match) { + var key; + switch (match[1]) { + case 'BS': + key = 'Backspace'; + break; + case 'CR': + key = 'Enter'; + break; + case 'Del': + key = 'Delete'; + break; + default: + key = match[1]; + break; + } + keys.push('Ctrl-' + key); + } + } + return keys; + } + var exCommands = { map: function(cm, params) { var mapArgs = params.commandArgs; From a9c40d02c569768754401e55e13ee0ead9005646 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sat, 8 Dec 2012 19:18:03 +0100 Subject: [PATCH 52/63] Simple Hint: pass through given options --- lib/util/simple-hint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util/simple-hint.js b/lib/util/simple-hint.js index f08f311e2f..1565bd4785 100644 --- a/lib/util/simple-hint.js +++ b/lib/util/simple-hint.js @@ -18,7 +18,7 @@ return; } - var result = getHints(editor); + var result = getHints(editor, givenOptions); if (!result || !result.list.length) return; var completions = result.list; function insert(str) { From 5497f6881806a74592ed4e0b08c7000e822fcb18 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Dec 2012 11:56:09 +0100 Subject: [PATCH 53/63] [javascript hinter] Support additionalContext option Issue #1066 --- lib/util/javascript-hint.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/util/javascript-hint.js b/lib/util/javascript-hint.js index 22edf5f3fb..07caba8766 100644 --- a/lib/util/javascript-hint.js +++ b/lib/util/javascript-hint.js @@ -16,7 +16,7 @@ return arr.indexOf(item) != -1; } - function scriptHint(editor, keywords, getToken) { + function scriptHint(editor, keywords, getToken, options) { // Find the token at the cursor var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token; // If it's not a 'word-style' token, ignore the token. @@ -47,14 +47,15 @@ if (!context) var context = []; context.push(tprop); } - return {list: getCompletions(token, context, keywords), + return {list: getCompletions(token, context, keywords, options), from: {line: cur.line, ch: token.start}, to: {line: cur.line, ch: token.end}}; } - CodeMirror.javascriptHint = function(editor) { + CodeMirror.javascriptHint = function(editor, options) { return scriptHint(editor, javascriptKeywords, - function (e, cur) {return e.getTokenAt(cur);}); + function (e, cur) {return e.getTokenAt(cur);}, + options); }; function getCoffeeScriptToken(editor, cur) { @@ -75,8 +76,8 @@ return token; } - CodeMirror.coffeescriptHint = function(editor) { - return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken); + CodeMirror.coffeescriptHint = function(editor, options) { + return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options); }; var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " + @@ -89,7 +90,7 @@ var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " + "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" "); - function getCompletions(token, context, keywords) { + function getCompletions(token, context, keywords, options) { var found = [], start = token.string; function maybeAdd(str) { if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str); @@ -105,13 +106,15 @@ // If this is a property, see if it belongs to some object we can // find in the current environment. var obj = context.pop(), base; - if (obj.type == "variable") - base = window[obj.string]; - else if (obj.type == "string") + if (obj.type == "variable") { + if (options && options.additionalContext) + base = options.additionalContext[obj.string]; + base = base || window[obj.string]; + } else if (obj.type == "string") { base = ""; - else if (obj.type == "atom") + } else if (obj.type == "atom") { base = 1; - else if (obj.type == "function") { + } else if (obj.type == "function") { if (window.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') && (typeof window.jQuery == 'function')) base = window.jQuery(); From aa347a319fe938f1aed0f27439649ff24c6d2565 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Dec 2012 12:07:39 +0100 Subject: [PATCH 54/63] Don't autofocus on mobile devices --- lib/codemirror.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 27a8b83ae6..8e1b6b8c58 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -21,6 +21,8 @@ window.CodeMirror = (function() { var phantom = /PhantomJS/.test(navigator.userAgent); var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + // This is woefully incomplete. Suggestions for alternative methods welcome. + var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent); var mac = ios || /Mac/.test(navigator.platform); // Optimize some code when these features are not used @@ -40,7 +42,7 @@ window.CodeMirror = (function() { var display = this.display = makeDisplay(place); display.wrapper.CodeMirror = this; updateGutters(this); - if (options.autofocus) focusInput(this); + if (options.autofocus && !mobile) focusInput(this); this.view = makeView(new BranchChunk([new LeafChunk([makeLine("", null, textHeight(display))])])); this.nextOpId = 0; @@ -60,7 +62,7 @@ window.CodeMirror = (function() { // IE throws unspecified error in certain cases, when // trying to access activeElement before onload var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { } - if (hasFocus || options.autofocus) setTimeout(bind(onFocus, this), 20); + if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20); else onBlur(this); operation(this, function() { From 02fdac636a8833bcee99ad6d27d56c444f22c304 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Mon, 10 Dec 2012 00:21:16 -0500 Subject: [PATCH 55/63] Add contributing guidelines See https://github.com/blog/1184-contributing-guidelines --- CONTRIBUTING.md | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 5 ++-- 2 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..d3e5c5d525 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,80 @@ +# How to contribute + +- [Getting help](#getting-help-) +- [Submitting bug reports](#submitting-bug-reports-) +- [Contributing code](#contributing-code-) + +## Getting help [^](#how-to-contribute) + +Community discussion, questions, and informal bug reporting is done on the +[CodeMirror Google group](http://groups.google.com/group/codemirror). + +## Submitting bug reports [^](#how-to-contribute) + +The preferred way to report bugs is to use the +[GitHub issue tracker](http://github.com/marijnh/CodeMirror/issues). Before +reporting a bug, read these pointers. + +**Note:** The issue tracker is for *bugs*, not requests for help. Questions +should be asked on the +[CodeMirror Google group](http://groups.google.com/group/codemirror) instead. + +### Reporting bugs effectively + +- CodeMirror is maintained by volunteers. They don't owe you anything, so be + polite. Reports with an indignant or belligerent tone tend to be moved to the + bottom of the pile. + +- Include information about **the browser in which the problem occurred**. Even + if you tested several browsers, and the problem occurred in all of them, + mention this fact in the bug report. Also include browser version numbers and + the operating system that you're on. + +- Mention which release of CodeMirror you're using. Preferably, try also with + the current development snapshot, to ensure the problem has not already been + fixed. + +- Mention very precisely what went wrong. "X is broken" is not a good bug + report. What did you expect to happen? What happened instead? Describe the + exact steps a maintainer has to take to make the problem occur. We can not + fix something that we can not observe. + +- If the problem can not be reproduced in any of the demos included in the + CodeMirror distribution, please provide an HTML document that demonstrates + the problem. The best way to do this is to go to + [jsbin.com](http://jsbin.com/ihunin/edit), enter it there, press save, and + include the resulting link in your bug report. + +## Contributing code [^](#how-to-contribute) + +- Make sure you have a [GitHub Account](https://github.com/signup/free) +- Fork [CodeMirror](https://github.com/marijnh/CodeMirror/) + ([how to fork a repo](https://help.github.com/articles/fork-a-repo)) +- Make your changes + - If your change affects highlighting for one of the modes, please [add (or + change) tests](#adding-mode-highlighting-tests) for the changes. If the mode + doesn't already have highlighting tests, you *aren't* required to add any. +- Test your changes + -Visit `/path-to-code/test/index.html` to test your code. *All tests should + pass*. +- Submit a pull request +([how to create a pull request](https://help.github.com/articles/fork-a-repo)) + +### Adding mode highlighting tests + +- Create a `test.js` file in the corresponding mode directory + ([example](https://github.com/marijnh/CodeMirror/blob/master/mode/markdown/test.js)) +- Add script tags to `/test/index.html` to include the formatting code and + as well as the tests. +- Run the tests! + +### Code formatting standards + +- 2 spaces (no tabs) +- Wrap to 80 characters when possible (unless it affects readability negatively) +- No trailing whitespace + - Blank lines should be indented as if there *is* text on them +- Spacing + - `function someFunction(someVar, someOtherVar) {` + - `if (someVar === true) doThis(someVar, someOtherVar);` + - `if (!someVar || someOtherVar === 0) {` diff --git a/README.md b/README.md index 8ed9871a46..3e87272fd8 100644 --- a/README.md +++ b/README.md @@ -4,5 +4,6 @@ CodeMirror is a JavaScript component that provides a code editor in the browser. When a mode is available for the language you are coding in, it will color your code, and optionally help with indentation. -The project page is http://codemirror.net -The manual is at http://codemirror.net/doc/manual.html +The project page is http://codemirror.net +The manual is at http://codemirror.net/doc/manual.html +The contributing guidelines are in the CONTRIBUTING.md file From ecfe3d55df4b211439914d23afbfcf43fce03026 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Dec 2012 12:20:47 +0100 Subject: [PATCH 56/63] Modify CONTRIBUTING.md Don't emphasize mode test suites and coding standards so much, they are not a big issue. --- CONTRIBUTING.md | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d3e5c5d525..afc18373cc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,30 +51,20 @@ should be asked on the - Fork [CodeMirror](https://github.com/marijnh/CodeMirror/) ([how to fork a repo](https://help.github.com/articles/fork-a-repo)) - Make your changes - - If your change affects highlighting for one of the modes, please [add (or - change) tests](#adding-mode-highlighting-tests) for the changes. If the mode - doesn't already have highlighting tests, you *aren't* required to add any. -- Test your changes - -Visit `/path-to-code/test/index.html` to test your code. *All tests should - pass*. +- If your changes are easy to test or likely to regress, add tests. + Tests for the core go into `test/test.js`, some modes have their own + test suite under `mode/XXX/test.js`. Feel free to add new test + suites to modes that don't have one yet (be sure to link the new + tests into `test/index.html`). +- Make sure all tests pass. Visit `test/index.html` in your browser to + run them. - Submit a pull request ([how to create a pull request](https://help.github.com/articles/fork-a-repo)) -### Adding mode highlighting tests +### Coding standards -- Create a `test.js` file in the corresponding mode directory - ([example](https://github.com/marijnh/CodeMirror/blob/master/mode/markdown/test.js)) -- Add script tags to `/test/index.html` to include the formatting code and - as well as the tests. -- Run the tests! - -### Code formatting standards - -- 2 spaces (no tabs) -- Wrap to 80 characters when possible (unless it affects readability negatively) -- No trailing whitespace - - Blank lines should be indented as if there *is* text on them -- Spacing - - `function someFunction(someVar, someOtherVar) {` - - `if (someVar === true) doThis(someVar, someOtherVar);` - - `if (!someVar || someOtherVar === 0) {` +- 2 spaces per indentation level, no tabs. +- Include semicolons after statements. +- Note that the linter (`test/lint/lint.js`) which is run after each + commit complains about unused variables and functions. Prefix their + names with an underscore to muffle it. From d9b8b19a5284a5a51befa6ff3e4ee4efaf440293 Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Mon, 10 Dec 2012 00:01:09 -0500 Subject: [PATCH 57/63] Force correct line endings in text files --- .gitattributes | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..f8bdd60f49 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +*.txt text +*.js text +*.html text +*.md text +*.json text +*.yml text +*.css text +*.svg text From 7033793a94caed124b96191849e6d94dea4d72df Mon Sep 17 00:00:00 2001 From: Brandon Frohs Date: Mon, 10 Dec 2012 00:23:13 -0500 Subject: [PATCH 58/63] Normalize line endings --- mode/ecl/ecl.js | 384 ++++++++++++++++++++-------------------- mode/ecl/index.html | 78 ++++---- mode/verilog/verilog.js | 364 ++++++++++++++++++------------------- 3 files changed, 413 insertions(+), 413 deletions(-) diff --git a/mode/ecl/ecl.js b/mode/ecl/ecl.js index b1c8088655..3ee7a681de 100644 --- a/mode/ecl/ecl.js +++ b/mode/ecl/ecl.js @@ -1,192 +1,192 @@ -CodeMirror.defineMode("ecl", function(config) { - - function words(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - - function metaHook(stream, state) { - if (!state.startOfLine) return false; - stream.skipToEnd(); - return "meta"; - } - - var indentUnit = config.indentUnit; - var keyword = words("abs acos allnodes ascii asin asstring atan atan2 ave case choose choosen choosesets clustersize combine correlation cos cosh count covariance cron dataset dedup define denormalize distribute distributed distribution ebcdic enth error evaluate event eventextra eventname exists exp failcode failmessage fetch fromunicode getisvalid global graph group hash hash32 hash64 hashcrc hashmd5 having if index intformat isvalid iterate join keyunicode length library limit ln local log loop map matched matchlength matchposition matchtext matchunicode max merge mergejoin min nolocal nonempty normalize parse pipe power preload process project pull random range rank ranked realformat recordof regexfind regexreplace regroup rejected rollup round roundup row rowdiff sample set sin sinh sizeof soapcall sort sorted sqrt stepped stored sum table tan tanh thisnode topn tounicode transfer trim truncate typeof ungroup unicodeorder variance which workunit xmldecode xmlencode xmltext xmlunicode"); - var variable = words("apply assert build buildindex evaluate fail keydiff keypatch loadxml nothor notify output parallel sequential soapcall wait"); - var variable_2 = words("__compressed__ all and any as atmost before beginc++ best between case const counter csv descend encrypt end endc++ endmacro except exclusive expire export extend false few first flat from full function group header heading hole ifblock import in interface joined keep keyed last left limit load local locale lookup macro many maxcount maxlength min skew module named nocase noroot noscan nosort not of only opt or outer overwrite packed partition penalty physicallength pipe quote record relationship repeat return right scan self separator service shared skew skip sql store terminator thor threshold token transform trim true type unicodeorder unsorted validate virtual whole wild within xml xpath"); - var variable_3 = words("ascii big_endian boolean data decimal ebcdic integer pattern qstring real record rule set of string token udecimal unicode unsigned varstring varunicode"); - var builtin = words("checkpoint deprecated failcode failmessage failure global independent onwarning persist priority recovery stored success wait when"); - var blockKeywords = words("catch class do else finally for if switch try while"); - var atoms = words("true false null"); - var hooks = {"#": metaHook}; - var multiLineStrings; - var isOperatorChar = /[+\-*&%=<>!?|\/]/; - - var curPunc; - - function tokenBase(stream, state) { - var ch = stream.next(); - if (hooks[ch]) { - var result = hooks[ch](stream, state); - if (result !== false) return result; - } - if (ch == '"' || ch == "'") { - state.tokenize = tokenString(ch); - return state.tokenize(stream, state); - } - if (/[\[\]{}\(\),;\:\.]/.test(ch)) { - curPunc = ch; - return null; - } - if (/\d/.test(ch)) { - stream.eatWhile(/[\w\.]/); - return "number"; - } - if (ch == "/") { - if (stream.eat("*")) { - state.tokenize = tokenComment; - return tokenComment(stream, state); - } - if (stream.eat("/")) { - stream.skipToEnd(); - return "comment"; - } - } - if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return "operator"; - } - stream.eatWhile(/[\w\$_]/); - var cur = stream.current().toLowerCase(); - if (keyword.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "keyword"; - } else if (variable.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "variable"; - } else if (variable_2.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "variable-2"; - } else if (variable_3.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "variable-3"; - } else if (builtin.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "builtin"; - } else { //Data types are of from KEYWORD## - var i = cur.length - 1; - while(i >= 0 && (!isNaN(cur[i]) || cur[i] == '_')) - --i; - - if (i > 0) { - var cur2 = cur.substr(0, i + 1); - if (variable_3.propertyIsEnumerable(cur2)) { - if (blockKeywords.propertyIsEnumerable(cur2)) curPunc = "newstatement"; - return "variable-3"; - } - } - } - if (atoms.propertyIsEnumerable(cur)) return "atom"; - return null; - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, next, end = false; - while ((next = stream.next()) != null) { - if (next == quote && !escaped) {end = true; break;} - escaped = !escaped && next == "\\"; - } - if (end || !(escaped || multiLineStrings)) - state.tokenize = tokenBase; - return "string"; - }; - } - - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize = tokenBase; - break; - } - maybeEnd = (ch == "*"); - } - return "comment"; - } - - function Context(indented, column, type, align, prev) { - this.indented = indented; - this.column = column; - this.type = type; - this.align = align; - this.prev = prev; - } - function pushContext(state, col, type) { - return state.context = new Context(state.indented, col, type, null, state.context); - } - function popContext(state) { - var t = state.context.type; - if (t == ")" || t == "]" || t == "}") - state.indented = state.context.indented; - return state.context = state.context.prev; - } - - // Interface - - return { - startState: function(basecolumn) { - return { - tokenize: null, - context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), - indented: 0, - startOfLine: true - }; - }, - - token: function(stream, state) { - var ctx = state.context; - if (stream.sol()) { - if (ctx.align == null) ctx.align = false; - state.indented = stream.indentation(); - state.startOfLine = true; - } - if (stream.eatSpace()) return null; - curPunc = null; - var style = (state.tokenize || tokenBase)(stream, state); - if (style == "comment" || style == "meta") return style; - if (ctx.align == null) ctx.align = true; - - if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); - else if (curPunc == "{") pushContext(state, stream.column(), "}"); - else if (curPunc == "[") pushContext(state, stream.column(), "]"); - else if (curPunc == "(") pushContext(state, stream.column(), ")"); - else if (curPunc == "}") { - while (ctx.type == "statement") ctx = popContext(state); - if (ctx.type == "}") ctx = popContext(state); - while (ctx.type == "statement") ctx = popContext(state); - } - else if (curPunc == ctx.type) popContext(state); - else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) - pushContext(state, stream.column(), "statement"); - state.startOfLine = false; - return style; - }, - - indent: function(state, textAfter) { - if (state.tokenize != tokenBase && state.tokenize != null) return 0; - var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); - if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; - var closing = firstChar == ctx.type; - if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit); - else if (ctx.align) return ctx.column + (closing ? 0 : 1); - else return ctx.indented + (closing ? 0 : indentUnit); - }, - - electricChars: "{}" - }; -}); - -CodeMirror.defineMIME("text/x-ecl", "ecl"); +CodeMirror.defineMode("ecl", function(config) { + + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + function metaHook(stream, state) { + if (!state.startOfLine) return false; + stream.skipToEnd(); + return "meta"; + } + + var indentUnit = config.indentUnit; + var keyword = words("abs acos allnodes ascii asin asstring atan atan2 ave case choose choosen choosesets clustersize combine correlation cos cosh count covariance cron dataset dedup define denormalize distribute distributed distribution ebcdic enth error evaluate event eventextra eventname exists exp failcode failmessage fetch fromunicode getisvalid global graph group hash hash32 hash64 hashcrc hashmd5 having if index intformat isvalid iterate join keyunicode length library limit ln local log loop map matched matchlength matchposition matchtext matchunicode max merge mergejoin min nolocal nonempty normalize parse pipe power preload process project pull random range rank ranked realformat recordof regexfind regexreplace regroup rejected rollup round roundup row rowdiff sample set sin sinh sizeof soapcall sort sorted sqrt stepped stored sum table tan tanh thisnode topn tounicode transfer trim truncate typeof ungroup unicodeorder variance which workunit xmldecode xmlencode xmltext xmlunicode"); + var variable = words("apply assert build buildindex evaluate fail keydiff keypatch loadxml nothor notify output parallel sequential soapcall wait"); + var variable_2 = words("__compressed__ all and any as atmost before beginc++ best between case const counter csv descend encrypt end endc++ endmacro except exclusive expire export extend false few first flat from full function group header heading hole ifblock import in interface joined keep keyed last left limit load local locale lookup macro many maxcount maxlength min skew module named nocase noroot noscan nosort not of only opt or outer overwrite packed partition penalty physicallength pipe quote record relationship repeat return right scan self separator service shared skew skip sql store terminator thor threshold token transform trim true type unicodeorder unsorted validate virtual whole wild within xml xpath"); + var variable_3 = words("ascii big_endian boolean data decimal ebcdic integer pattern qstring real record rule set of string token udecimal unicode unsigned varstring varunicode"); + var builtin = words("checkpoint deprecated failcode failmessage failure global independent onwarning persist priority recovery stored success wait when"); + var blockKeywords = words("catch class do else finally for if switch try while"); + var atoms = words("true false null"); + var hooks = {"#": metaHook}; + var multiLineStrings; + var isOperatorChar = /[+\-*&%=<>!?|\/]/; + + var curPunc; + + function tokenBase(stream, state) { + var ch = stream.next(); + if (hooks[ch]) { + var result = hooks[ch](stream, state); + if (result !== false) return result; + } + if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + curPunc = ch; + return null; + } + if (/\d/.test(ch)) { + stream.eatWhile(/[\w\.]/); + return "number"; + } + if (ch == "/") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return "operator"; + } + stream.eatWhile(/[\w\$_]/); + var cur = stream.current().toLowerCase(); + if (keyword.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "keyword"; + } else if (variable.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "variable"; + } else if (variable_2.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "variable-2"; + } else if (variable_3.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "variable-3"; + } else if (builtin.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "builtin"; + } else { //Data types are of from KEYWORD## + var i = cur.length - 1; + while(i >= 0 && (!isNaN(cur[i]) || cur[i] == '_')) + --i; + + if (i > 0) { + var cur2 = cur.substr(0, i + 1); + if (variable_3.propertyIsEnumerable(cur2)) { + if (blockKeywords.propertyIsEnumerable(cur2)) curPunc = "newstatement"; + return "variable-3"; + } + } + } + if (atoms.propertyIsEnumerable(cur)) return "atom"; + return null; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || multiLineStrings)) + state.tokenize = tokenBase; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + function pushContext(state, col, type) { + return state.context = new Context(state.indented, col, type, null, state.context); + } + function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + } + if (stream.eatSpace()) return null; + curPunc = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment" || style == "meta") return style; + if (ctx.align == null) ctx.align = true; + + if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); + else if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "}") { + while (ctx.type == "statement") ctx = popContext(state); + if (ctx.type == "}") ctx = popContext(state); + while (ctx.type == "statement") ctx = popContext(state); + } + else if (curPunc == ctx.type) popContext(state); + else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) + pushContext(state, stream.column(), "statement"); + state.startOfLine = false; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase && state.tokenize != null) return 0; + var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); + if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; + var closing = firstChar == ctx.type; + if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit); + else if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indented + (closing ? 0 : indentUnit); + }, + + electricChars: "{}" + }; +}); + +CodeMirror.defineMIME("text/x-ecl", "ecl"); diff --git a/mode/ecl/index.html b/mode/ecl/index.html index 77006ae6e6..0ba88c3995 100644 --- a/mode/ecl/index.html +++ b/mode/ecl/index.html @@ -1,39 +1,39 @@ - - - - CodeMirror: ECL mode - - - - - - - -

    CodeMirror: ECL mode

    -
    - - -

    Based on CodeMirror's clike mode. For more information see HPCC Systems web site.

    -

    MIME types defined: text/x-ecl.

    - - - + + + + CodeMirror: ECL mode + + + + + + + +

    CodeMirror: ECL mode

    +
    + + +

    Based on CodeMirror's clike mode. For more information see HPCC Systems web site.

    +

    MIME types defined: text/x-ecl.

    + + + diff --git a/mode/verilog/verilog.js b/mode/verilog/verilog.js index 230e31d2dc..708de23f49 100644 --- a/mode/verilog/verilog.js +++ b/mode/verilog/verilog.js @@ -1,182 +1,182 @@ -CodeMirror.defineMode("verilog", function(config, parserConfig) { - var indentUnit = config.indentUnit, - keywords = parserConfig.keywords || {}, - blockKeywords = parserConfig.blockKeywords || {}, - atoms = parserConfig.atoms || {}, - hooks = parserConfig.hooks || {}, - multiLineStrings = parserConfig.multiLineStrings; - var isOperatorChar = /[&|~> Date: Mon, 10 Dec 2012 12:33:27 +0100 Subject: [PATCH 59/63] Fix call to updateScrollBars to pass doc height --- lib/codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/codemirror.js b/lib/codemirror.js index 8e1b6b8c58..1cdac59250 100644 --- a/lib/codemirror.js +++ b/lib/codemirror.js @@ -217,7 +217,7 @@ window.CodeMirror = (function() { } regChange(cm, 0, doc.size); clearCaches(cm); - setTimeout(bind(updateScrollbars, cm.display), 100); + setTimeout(function(){updateScrollbars(cm.display, cm.view.doc.height);}, 100); } function keyMapChanged(cm) { From fcbe3fd3875e25a4e3b5084364fc78c393413500 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 10 Dec 2012 12:34:19 +0100 Subject: [PATCH 60/63] Bump supported version of FF to 3 2 doesn't have getBoundingClientRect, which we use extensively. Closes #1062 --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 4dbc0659f0..b510b5f706 100644 --- a/index.html +++ b/index.html @@ -215,7 +215,7 @@

    Supported browsers

    The following desktop browsers are able to run CodeMirror: