Chromium Code Reviews| Index: Source/devtools/front_end/CodeMirrorTextEditor.js |
| diff --git a/Source/devtools/front_end/CodeMirrorTextEditor.js b/Source/devtools/front_end/CodeMirrorTextEditor.js |
| index 031948f3a771fe003168e518dc5090976dcf0349..459b196b8e176714d669f41294f9bb0ca5a82bf4 100644 |
| --- a/Source/devtools/front_end/CodeMirrorTextEditor.js |
| +++ b/Source/devtools/front_end/CodeMirrorTextEditor.js |
| @@ -89,6 +89,7 @@ WebInspector.CodeMirrorTextEditor = function(url, delegate) |
| "Shift-Tab": "indentLess", |
| "Enter": "smartNewlineAndIndent", |
| "Ctrl-Space": "autocomplete", |
| + "Ctrl-D": "selectNextOccurence", |
|
apavlov
2014/04/01 08:24:58
ditto for this file
|
| "Esc": "dismissMultipleSelections" |
| }; |
| @@ -143,10 +144,11 @@ WebInspector.CodeMirrorTextEditor = function(url, delegate) |
| this._shouldClearHistory = true; |
| this._lineSeparator = "\n"; |
| - this._tokenHighlighter = new WebInspector.CodeMirrorTextEditor.TokenHighlighter(this._codeMirror); |
| + this._tokenHighlighter = new WebInspector.CodeMirrorTextEditor.TokenHighlighter(this, this._codeMirror); |
| this._blockIndentController = new WebInspector.CodeMirrorTextEditor.BlockIndentController(this._codeMirror); |
| this._fixWordMovement = new WebInspector.CodeMirrorTextEditor.FixWordMovement(this._codeMirror); |
| this._autocompleteController = new WebInspector.CodeMirrorTextEditor.AutocompleteController(this, this._codeMirror); |
| + this._selectNextOccurenceController = new WebInspector.CodeMirrorTextEditor.SelectNextOccurenceController(this, this._codeMirror); |
| this._codeMirror.on("changes", this._changes.bind(this)); |
| this._codeMirror.on("beforeChange", this._beforeChange.bind(this)); |
| @@ -189,12 +191,27 @@ WebInspector.CodeMirrorTextEditor.ChangeObject; |
| WebInspector.CodeMirrorTextEditor.maxHighlightLength = 1000; |
| +/** |
| + * @param {!CodeMirror} codeMirror |
| + */ |
| WebInspector.CodeMirrorTextEditor.autocompleteCommand = function(codeMirror) |
| { |
| codeMirror._codeMirrorTextEditor._autocompleteController.autocomplete(); |
| } |
| CodeMirror.commands.autocomplete = WebInspector.CodeMirrorTextEditor.autocompleteCommand; |
| +/** |
| + * @param {!CodeMirror} codeMirror |
| + */ |
| +WebInspector.CodeMirrorTextEditor.selectNextOccurenceCommand = function(codeMirror) |
| +{ |
| + codeMirror._codeMirrorTextEditor._selectNextOccurenceController.selectNextOccurence(); |
| +} |
| +CodeMirror.commands.selectNextOccurence = WebInspector.CodeMirrorTextEditor.selectNextOccurenceCommand; |
| + |
| +/** |
| + * @param {!CodeMirror} codeMirror |
| + */ |
| CodeMirror.commands.smartNewlineAndIndent = function(codeMirror) |
| { |
| codeMirror.operation(innerSmartNewlineAndIndent.bind(null, codeMirror)); |
| @@ -913,19 +930,17 @@ WebInspector.CodeMirrorTextEditor.prototype = { |
| /** |
| * @param {number} lineNumber |
| * @param {number} column |
| - * @param {boolean=} prefixOnly |
| * @return {?WebInspector.TextRange} |
| */ |
| - _wordRangeForCursorPosition: function(lineNumber, column, prefixOnly) |
| + _wordRangeForCursorPosition: function(lineNumber, column) |
| { |
| var line = this.line(lineNumber); |
| - if (column === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(column - 1))) |
| - return null; |
| - var wordStart = column - 1; |
| - while (wordStart > 0 && WebInspector.TextUtils.isWordChar(line.charAt(wordStart - 1))) |
| - --wordStart; |
| - if (prefixOnly) |
| - return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, column); |
| + var wordStart = column; |
| + if (column !== 0 && WebInspector.TextUtils.isWordChar(line.charAt(column - 1))) { |
| + wordStart = column - 1; |
| + while (wordStart > 0 && WebInspector.TextUtils.isWordChar(line.charAt(wordStart - 1))) |
| + --wordStart; |
| + } |
| var wordEnd = column; |
| while (wordEnd < line.length && WebInspector.TextUtils.isWordChar(line.charAt(wordEnd))) |
| ++wordEnd; |
| @@ -1020,6 +1035,7 @@ WebInspector.CodeMirrorTextEditor.prototype = { |
| */ |
| _beforeSelectionChange: function(codeMirror, selection) |
| { |
| + this._selectNextOccurenceController.selectionWillChange(); |
| if (!this._isHandlingMouseDownEvent) |
| return; |
| if (!selection.ranges.length) |
| @@ -1096,6 +1112,20 @@ WebInspector.CodeMirrorTextEditor.prototype = { |
| }, |
| /** |
| + * @return {!Array.<!WebInspector.TextRange>} |
| + */ |
| + selections: function() |
| + { |
| + var selectionList = this._codeMirror.listSelections(); |
| + var result = []; |
| + for (var i = 0; i < selectionList.length; ++i) { |
| + var selection = selectionList[i]; |
| + result.push(this._toRange(selection.anchor, selection.head)); |
| + } |
| + return result; |
| + }, |
| + |
| + /** |
| * @return {?WebInspector.TextRange} |
| */ |
| lastSelection: function() |
| @@ -1114,6 +1144,22 @@ WebInspector.CodeMirrorTextEditor.prototype = { |
| }, |
| /** |
| + * @param {!Array.<!WebInspector.TextRange>} ranges |
| + */ |
| + setSelections: function(ranges) |
| + { |
| + var selections = []; |
| + for (var i = 0; i < ranges.length; ++i) { |
| + var selection = this._toPos(ranges[i]); |
| + selections.push({ |
| + anchor: selection.start, |
| + head: selection.end |
| + }); |
| + } |
| + this._codeMirror.setSelections(selections); |
| + }, |
| + |
| + /** |
| * @param {string} text |
| */ |
| _detectLineSeparator: function(text) |
| @@ -1287,10 +1333,12 @@ WebInspector.CodeMirrorPositionHandle.prototype = { |
| /** |
| * @constructor |
| + * @param {!WebInspector.CodeMirrorTextEditor} textEditor |
| * @param {!CodeMirror} codeMirror |
| */ |
| -WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(codeMirror) |
| +WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(textEditor, codeMirror) |
| { |
| + this._textEditor = textEditor; |
| this._codeMirror = codeMirror; |
| } |
| @@ -1335,6 +1383,19 @@ WebInspector.CodeMirrorTextEditor.TokenHighlighter.prototype = { |
| return this._highlightRegex; |
| }, |
| + /** |
| + * @param {!Array.<string>} selections |
| + */ |
| + _validateSelections: function(selections) |
| + { |
| + var mainText = selections[0]; |
| + for (var i = 1; i < selections.length; ++i) { |
| + if (selections[i] !== mainText) |
| + return false; |
| + } |
| + return true; |
| + }, |
| + |
| highlightSelectedTokens: function() |
| { |
| delete this._highlightRegex; |
| @@ -1351,7 +1412,7 @@ WebInspector.CodeMirrorTextEditor.TokenHighlighter.prototype = { |
| return; |
| var selections = this._codeMirror.getSelections(); |
| - if (selections.length !== 1) |
| + if (!this._validateSelections(selections)) |
| return; |
| var selectedText = selections[0]; |
| if (this._isWord(selectedText, selectionStart.line, selectionStart.ch, selectionEnd.ch)) { |
| @@ -1585,7 +1646,7 @@ WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = { |
| { |
| var mainSelectionContext = this._textEditor.copyRange(mainSelection); |
| for (var i = 0; i < selections.length; ++i) { |
| - var wordRange = this._textEditor._wordRangeForCursorPosition(selections[i].head.line, selections[i].head.ch, false); |
| + var wordRange = this._textEditor._wordRangeForCursorPosition(selections[i].head.line, selections[i].head.ch); |
| if (!wordRange) |
| return false; |
| var context = this._textEditor.copyRange(wordRange); |
| @@ -1606,7 +1667,7 @@ WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = { |
| var selections = this._codeMirror.listSelections().slice(); |
| var topSelection = selections.shift(); |
| var cursor = topSelection.head; |
| - var substituteRange = this._textEditor._wordRangeForCursorPosition(cursor.line, cursor.ch, false); |
| + var substituteRange = this._textEditor._wordRangeForCursorPosition(cursor.line, cursor.ch); |
| if (!substituteRange || substituteRange.startColumn === cursor.ch || !this._validateSelectionsContexts(substituteRange, selections)) { |
| this.finishAutocomplete(); |
| return; |
| @@ -1731,6 +1792,139 @@ WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = { |
| } |
| /** |
| + * @constructor |
| + * @param {!WebInspector.CodeMirrorTextEditor} textEditor |
| + * @param {!CodeMirror} codeMirror |
| + */ |
| +WebInspector.CodeMirrorTextEditor.SelectNextOccurenceController = function(textEditor, codeMirror) |
| +{ |
| + this._textEditor = textEditor; |
| + this._codeMirror = codeMirror; |
| +} |
| + |
| +WebInspector.CodeMirrorTextEditor.SelectNextOccurenceController.prototype = { |
| + selectionWillChange: function() |
| + { |
| + if (!this._muteSelectionListener) |
| + delete this._fullWordSelection; |
| + }, |
| + |
| + /** |
| + * @param {!Array.<!WebInspector.TextRange>} selections |
| + * @param {!WebInspector.TextRange} range |
| + * @return {boolean} |
| + */ |
| + _findRange: function(selections, range) |
| + { |
| + for (var i = 0; i < selections.length; ++i) { |
| + if (range.equal(selections[i])) |
| + return true; |
| + } |
| + return false; |
| + }, |
| + |
| + selectNextOccurence: function() |
| + { |
| + var selections = this._textEditor.selections(); |
| + var anyEmptySelection = false; |
| + for (var i = 0; i < selections.length; ++i) { |
| + var selection = selections[i]; |
| + anyEmptySelection = anyEmptySelection || selection.isEmpty(); |
| + if (selection.startLine !== selection.endLine) |
| + return; |
| + } |
| + if (anyEmptySelection) { |
| + this._expandSelectionsToWords(selections); |
| + return; |
| + } |
| + |
| + var last = selections[selections.length - 1]; |
| + var next = last; |
| + do { |
| + next = this._findNextOccurence(next, !!this._fullWordSelection); |
| + } while (next && this._findRange(selections, next) && !next.equal(last)); |
| + |
| + if (!next) |
| + return; |
| + selections.push(next); |
| + |
| + this._muteSelectionListener = true; |
| + this._textEditor.setSelections(selections); |
| + delete this._muteSelectionListener; |
| + |
| + this._textEditor._revealLine(next.startLine); |
| + }, |
| + |
| + /** |
| + * @param {!Array.<!WebInspector.TextRange>} selections |
| + */ |
| + _expandSelectionsToWords: function(selections) |
| + { |
| + var newSelections = []; |
| + for (var i = 0; i < selections.length; ++i) { |
| + var selection = selections[i]; |
| + var startRangeWord = this._textEditor._wordRangeForCursorPosition(selection.startLine, selection.startColumn) |
| + || WebInspector.TextRange.createFromLocation(selection.startLine, selection.startColumn); |
| + var endRangeWord = this._textEditor._wordRangeForCursorPosition(selection.endLine, selection.endColumn) |
| + || WebInspector.TextRange.createFromLocation(selection.endLine, selection.endColumn); |
| + var newSelection = new WebInspector.TextRange(startRangeWord.startLine, startRangeWord.startColumn, endRangeWord.endLine, endRangeWord.endColumn); |
| + newSelections.push(newSelection); |
| + } |
| + this._textEditor.setSelections(newSelections); |
| + this._fullWordSelection = true; |
| + }, |
| + |
| + /** |
| + * @param {!WebInspector.TextRange} range |
| + * @param {boolean} fullWord |
| + * @return {?WebInspector.TextRange} |
| + */ |
| + _findNextOccurence: function(range, fullWord) |
| + { |
| + range = range.normalize(); |
| + var matchedLineNumber; |
| + var matchedColumnNumber; |
| + var textToFind = this._textEditor.copyRange(range); |
| + function findWordInLine(wordRegex, lineNumber, lineText, from, to) |
| + { |
| + if (typeof matchedLineNumber === "number") |
| + return true; |
| + wordRegex.lastIndex = from; |
| + var result = wordRegex.exec(lineText); |
| + if (!result || result.index + textToFind.length > to) |
| + return false; |
| + matchedLineNumber = lineNumber; |
| + matchedColumnNumber = result.index; |
| + return true; |
| + } |
| + |
| + var iteratedLineNumber; |
| + function lineIterator(regex, lineHandle) |
| + { |
| + if (findWordInLine(regex, iteratedLineNumber++, lineHandle.text, 0, lineHandle.text.length)) |
| + return true; |
| + } |
| + |
| + var regexSource = textToFind.escapeForRegExp(); |
| + if (fullWord) |
| + regexSource = "\\b" + regexSource + "\\b"; |
| + var wordRegex = new RegExp(regexSource, "gi"); |
| + var currentLineText = this._codeMirror.getLine(range.startLine); |
| + |
| + findWordInLine(wordRegex, range.startLine, currentLineText, range.endColumn, currentLineText.length); |
| + iteratedLineNumber = range.startLine + 1; |
| + this._codeMirror.eachLine(range.startLine + 1, this._codeMirror.lineCount(), lineIterator.bind(null, wordRegex)); |
| + iteratedLineNumber = 0; |
| + this._codeMirror.eachLine(0, range.startLine, lineIterator.bind(null, wordRegex)); |
| + findWordInLine(wordRegex, range.startLine, currentLineText, 0, range.startColumn); |
| + |
| + if (typeof matchedLineNumber !== "number") |
| + return null; |
| + return new WebInspector.TextRange(matchedLineNumber, matchedColumnNumber, matchedLineNumber, matchedColumnNumber + textToFind.length); |
| + } |
| +} |
| + |
| +/** |
| * @param {string} modeName |
| * @param {string} tokenPrefix |
| */ |