CodeMirrorTextEditor.js 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560
  1. /*
  2. * Copyright (C) 2012 Google Inc. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions are
  6. * met:
  7. *
  8. * * Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * * Redistributions in binary form must reproduce the above
  11. * copyright notice, this list of conditions and the following disclaimer
  12. * in the documentation and/or other materials provided with the
  13. * distribution.
  14. * * Neither the name of Google Inc. nor the names of its
  15. * contributors may be used to endorse or promote products derived from
  16. * this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. */
  30. importScript("cm/codemirror.js");
  31. importScript("cm/css.js");
  32. importScript("cm/javascript.js");
  33. importScript("cm/xml.js");
  34. importScript("cm/htmlmixed.js");
  35. importScript("cm/matchbrackets.js");
  36. importScript("cm/closebrackets.js");
  37. importScript("cm/markselection.js");
  38. importScript("cm/comment.js");
  39. importScript("cm/overlay.js");
  40. importScript("cm/htmlembedded.js");
  41. importScript("cm/clike.js");
  42. importScript("cm/coffeescript.js");
  43. importScript("cm/php.js");
  44. importScript("cm/python.js");
  45. importScript("cm/shell.js");
  46. importScript("CodeMirrorUtils.js");
  47. /**
  48. * @constructor
  49. * @extends {WebInspector.View}
  50. * @implements {WebInspector.TextEditor}
  51. * @param {?string} url
  52. * @param {WebInspector.TextEditorDelegate} delegate
  53. */
  54. WebInspector.CodeMirrorTextEditor = function(url, delegate)
  55. {
  56. WebInspector.View.call(this);
  57. this._delegate = delegate;
  58. this._url = url;
  59. this.registerRequiredCSS("cm/codemirror.css");
  60. this.registerRequiredCSS("cm/cmdevtools.css");
  61. this._codeMirror = window.CodeMirror(this.element, {
  62. lineNumbers: true,
  63. gutters: ["CodeMirror-linenumbers"],
  64. matchBrackets: true,
  65. smartIndent: false,
  66. styleSelectedText: true,
  67. electricChars: false,
  68. autoCloseBrackets: { explode: false }
  69. });
  70. this._codeMirror._codeMirrorTextEditor = this;
  71. CodeMirror.keyMap["devtools-common"] = {
  72. "Left": "goCharLeft",
  73. "Right": "goCharRight",
  74. "Up": "goLineUp",
  75. "Down": "goLineDown",
  76. "End": "goLineEnd",
  77. "Home": "goLineStartSmart",
  78. "PageUp": "goPageUp",
  79. "PageDown": "goPageDown",
  80. "Delete": "delCharAfter",
  81. "Backspace": "delCharBefore",
  82. "Tab": "defaultTab",
  83. "Shift-Tab": "indentLess",
  84. "Enter": "smartNewlineAndIndent",
  85. "Ctrl-Space": "autocomplete"
  86. };
  87. CodeMirror.keyMap["devtools-pc"] = {
  88. "Ctrl-A": "selectAll",
  89. "Ctrl-Z": "undoAndReveal",
  90. "Shift-Ctrl-Z": "redoAndReveal",
  91. "Ctrl-Y": "redo",
  92. "Ctrl-Home": "goDocStart",
  93. "Ctrl-Up": "goDocStart",
  94. "Ctrl-End": "goDocEnd",
  95. "Ctrl-Down": "goDocEnd",
  96. "Ctrl-Left": "goGroupLeft",
  97. "Ctrl-Right": "goGroupRight",
  98. "Alt-Left": "goLineStart",
  99. "Alt-Right": "goLineEnd",
  100. "Ctrl-Backspace": "delGroupBefore",
  101. "Ctrl-Delete": "delGroupAfter",
  102. "Ctrl-/": "toggleComment",
  103. fallthrough: "devtools-common"
  104. };
  105. CodeMirror.keyMap["devtools-mac"] = {
  106. "Cmd-A" : "selectAll",
  107. "Cmd-Z" : "undoAndReveal",
  108. "Shift-Cmd-Z": "redoAndReveal",
  109. "Cmd-Up": "goDocStart",
  110. "Cmd-Down": "goDocEnd",
  111. "Alt-Left": "goGroupLeft",
  112. "Alt-Right": "goGroupRight",
  113. "Cmd-Left": "goLineStartSmart",
  114. "Cmd-Right": "goLineEnd",
  115. "Alt-Backspace": "delGroupBefore",
  116. "Alt-Delete": "delGroupAfter",
  117. "Cmd-/": "toggleComment",
  118. fallthrough: "devtools-common"
  119. };
  120. WebInspector.settings.textEditorIndent.addChangeListener(this._updateEditorIndentation, this);
  121. this._updateEditorIndentation();
  122. WebInspector.settings.showWhitespacesInEditor.addChangeListener(this._updateCodeMirrorMode, this);
  123. this._codeMirror.setOption("keyMap", WebInspector.isMac() ? "devtools-mac" : "devtools-pc");
  124. this._codeMirror.setOption("flattenSpans", false);
  125. this._codeMirror.setOption("maxHighlightLength", 1000);
  126. this._codeMirror.setOption("mode", null);
  127. this._shouldClearHistory = true;
  128. this._lineSeparator = "\n";
  129. this._tokenHighlighter = new WebInspector.CodeMirrorTextEditor.TokenHighlighter(this._codeMirror);
  130. this._blockIndentController = new WebInspector.CodeMirrorTextEditor.BlockIndentController(this._codeMirror);
  131. this._fixWordMovement = new WebInspector.CodeMirrorTextEditor.FixWordMovement(this._codeMirror);
  132. this._autocompleteController = new WebInspector.CodeMirrorTextEditor.AutocompleteController(this, this._codeMirror);
  133. this._codeMirror.on("change", this._change.bind(this));
  134. this._codeMirror.on("beforeChange", this._beforeChange.bind(this));
  135. this._codeMirror.on("gutterClick", this._gutterClick.bind(this));
  136. this._codeMirror.on("cursorActivity", this._cursorActivity.bind(this));
  137. this._codeMirror.on("scroll", this._scroll.bind(this));
  138. this._codeMirror.on("focus", this._focus.bind(this));
  139. this._codeMirror.on("blur", this._blur.bind(this));
  140. this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false);
  141. this.element.addStyleClass("fill");
  142. this.element.style.overflow = "hidden";
  143. this.element.firstChild.addStyleClass("source-code");
  144. this.element.firstChild.addStyleClass("fill");
  145. this._elementToWidget = new Map();
  146. this._nestedUpdatesCounter = 0;
  147. this.element.addEventListener("focus", this._handleElementFocus.bind(this), false);
  148. this.element.addEventListener("keydown", this._handleKeyDown.bind(this), true);
  149. this.element.tabIndex = 0;
  150. this._setupSelectionColor();
  151. this._setupWhitespaceHighlight();
  152. }
  153. WebInspector.CodeMirrorTextEditor.autocompleteCommand = function(codeMirror)
  154. {
  155. codeMirror._codeMirrorTextEditor._autocompleteController.autocomplete();
  156. }
  157. CodeMirror.commands.autocomplete = WebInspector.CodeMirrorTextEditor.autocompleteCommand;
  158. CodeMirror.commands.smartNewlineAndIndent = function(codeMirror)
  159. {
  160. codeMirror.operation(innerSmartNewlineAndIndent.bind(this, codeMirror));
  161. function countIndent(line)
  162. {
  163. for(var i = 0; i < line.length; ++i) {
  164. if (!WebInspector.TextUtils.isSpaceChar(line[i]))
  165. return i;
  166. }
  167. return line.length;
  168. }
  169. function innerSmartNewlineAndIndent(codeMirror)
  170. {
  171. var cur = codeMirror.getCursor("start");
  172. var line = codeMirror.getLine(cur.line);
  173. var indent = cur.line > 0 ? countIndent(line) : 0;
  174. if (cur.ch <= indent) {
  175. codeMirror.replaceSelection("\n" + line.substring(0, cur.ch), "end", "+input");
  176. codeMirror.setSelection(new CodeMirror.Pos(cur.line + 1, cur.ch));
  177. } else
  178. codeMirror.execCommand("newlineAndIndent");
  179. }
  180. }
  181. CodeMirror.commands.undoAndReveal = function(codemirror)
  182. {
  183. var scrollInfo = codemirror.getScrollInfo();
  184. codemirror.execCommand("undo");
  185. var cursor = codemirror.getCursor("start");
  186. codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo);
  187. }
  188. CodeMirror.commands.redoAndReveal = function(codemirror)
  189. {
  190. var scrollInfo = codemirror.getScrollInfo();
  191. codemirror.execCommand("redo");
  192. var cursor = codemirror.getCursor("start");
  193. codemirror._codeMirrorTextEditor._innerRevealLine(cursor.line, scrollInfo);
  194. }
  195. WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold = 2000;
  196. WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16;
  197. WebInspector.CodeMirrorTextEditor.prototype = {
  198. wasShown: function()
  199. {
  200. this._codeMirror.refresh();
  201. },
  202. _guessIndentationLevel: function()
  203. {
  204. var tabRegex = /^\t+/;
  205. var tabLines = 0;
  206. var indents = {};
  207. function processLine(lineHandle)
  208. {
  209. var text = lineHandle.text;
  210. if (text.length === 0 || !WebInspector.TextUtils.isSpaceChar(text[0]))
  211. return;
  212. if (tabRegex.test(text)) {
  213. ++tabLines;
  214. return;
  215. }
  216. var i = 0;
  217. while (i < text.length && WebInspector.TextUtils.isSpaceChar(text[i]))
  218. ++i;
  219. if (i % 2 !== 0)
  220. return;
  221. indents[i] = 1 + (indents[i] || 0);
  222. }
  223. this._codeMirror.eachLine(processLine);
  224. var onePercentFilterThreshold = this.linesCount / 100;
  225. if (tabLines && tabLines > onePercentFilterThreshold)
  226. return "\t";
  227. var minimumIndent = Infinity;
  228. for (var i in indents) {
  229. if (indents[i] < onePercentFilterThreshold)
  230. continue;
  231. var indent = parseInt(i, 10);
  232. if (minimumIndent > indent)
  233. minimumIndent = indent;
  234. }
  235. if (minimumIndent === Infinity)
  236. return WebInspector.TextUtils.Indent.FourSpaces;
  237. return new Array(minimumIndent + 1).join(" ");
  238. },
  239. _updateEditorIndentation: function()
  240. {
  241. var extraKeys = {};
  242. var indent = WebInspector.settings.textEditorIndent.get();
  243. if (WebInspector.settings.textEditorAutoDetectIndent.get())
  244. indent = this._guessIndentationLevel();
  245. if (indent === WebInspector.TextUtils.Indent.TabCharacter) {
  246. this._codeMirror.setOption("indentWithTabs", true);
  247. this._codeMirror.setOption("indentUnit", 4);
  248. } else {
  249. this._codeMirror.setOption("indentWithTabs", false);
  250. this._codeMirror.setOption("indentUnit", indent.length);
  251. extraKeys.Tab = function(codeMirror)
  252. {
  253. if (codeMirror.somethingSelected())
  254. return CodeMirror.Pass;
  255. var pos = codeMirror.getCursor("head");
  256. codeMirror.replaceRange(indent.substring(pos.ch % indent.length), codeMirror.getCursor());
  257. }
  258. }
  259. this._codeMirror.setOption("extraKeys", extraKeys);
  260. this._indentationLevel = indent;
  261. },
  262. /**
  263. * @return {string}
  264. */
  265. indent: function()
  266. {
  267. return this._indentationLevel;
  268. },
  269. /**
  270. * @param {!RegExp} regex
  271. * @param {WebInspector.TextRange} range
  272. */
  273. highlightSearchResults: function(regex, range)
  274. {
  275. function innerHighlightRegex()
  276. {
  277. if (range) {
  278. this.revealLine(range.startLine);
  279. this.setSelection(WebInspector.TextRange.createFromLocation(range.startLine, range.startColumn));
  280. } else {
  281. // Collapse selection to end on search start so that we jump to next occurence on the first enter press.
  282. this.setSelection(this.selection().collapseToEnd());
  283. }
  284. this._tokenHighlighter.highlightSearchResults(regex, range);
  285. }
  286. this._codeMirror.operation(innerHighlightRegex.bind(this));
  287. },
  288. cancelSearchResultsHighlight: function()
  289. {
  290. this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
  291. },
  292. undo: function()
  293. {
  294. this._codeMirror.undo();
  295. },
  296. redo: function()
  297. {
  298. this._codeMirror.redo();
  299. },
  300. _setupSelectionColor: function()
  301. {
  302. if (WebInspector.CodeMirrorTextEditor._selectionStyleInjected)
  303. return;
  304. WebInspector.CodeMirrorTextEditor._selectionStyleInjected = true;
  305. var backgroundColor = WebInspector.getSelectionBackgroundColor();
  306. var backgroundColorRule = backgroundColor ? ".CodeMirror .CodeMirror-selected { background-color: " + backgroundColor + ";}" : "";
  307. var foregroundColor = WebInspector.getSelectionForegroundColor();
  308. var foregroundColorRule = foregroundColor ? ".CodeMirror .CodeMirror-selectedtext:not(.CodeMirror-persist-highlight) { color: " + foregroundColor + "!important;}" : "";
  309. if (!foregroundColorRule && !backgroundColorRule)
  310. return;
  311. var style = document.createElement("style");
  312. style.textContent = backgroundColorRule + foregroundColorRule;
  313. document.head.appendChild(style);
  314. },
  315. _setupWhitespaceHighlight: function()
  316. {
  317. if (WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected || !WebInspector.settings.showWhitespacesInEditor.get())
  318. return;
  319. WebInspector.CodeMirrorTextEditor._whitespaceStyleInjected = true;
  320. const classBase = ".cm-whitespace-";
  321. const spaceChar = "·";
  322. var spaceChars = "";
  323. var rules = "";
  324. for(var i = 1; i <= WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan; ++i) {
  325. spaceChars += spaceChar;
  326. var rule = classBase + i + "::before { content: '" + spaceChars + "';}\n";
  327. rules += rule;
  328. }
  329. rules += ".cm-tab:before { display: block !important; }\n";
  330. var style = document.createElement("style");
  331. style.textContent = rules;
  332. document.head.appendChild(style);
  333. },
  334. _handleKeyDown: function(e)
  335. {
  336. if (this._autocompleteController.keyDown(e))
  337. e.consume(true);
  338. },
  339. _shouldProcessWordForAutocompletion: function(word)
  340. {
  341. return word.length && (word[0] < '0' || word[0] > '9');
  342. },
  343. /**
  344. * @param {string} text
  345. */
  346. _addTextToCompletionDictionary: function(text)
  347. {
  348. var words = WebInspector.TextUtils.textToWords(text);
  349. for(var i = 0; i < words.length; ++i) {
  350. if (this._shouldProcessWordForAutocompletion(words[i]))
  351. this._dictionary.addWord(words[i]);
  352. }
  353. },
  354. /**
  355. * @param {string} text
  356. */
  357. _removeTextFromCompletionDictionary: function(text)
  358. {
  359. var words = WebInspector.TextUtils.textToWords(text);
  360. for(var i = 0; i < words.length; ++i) {
  361. if (this._shouldProcessWordForAutocompletion(words[i]))
  362. this._dictionary.removeWord(words[i]);
  363. }
  364. },
  365. /**
  366. * @param {WebInspector.CompletionDictionary} dictionary
  367. */
  368. setCompletionDictionary: function(dictionary)
  369. {
  370. this._dictionary = dictionary;
  371. this._addTextToCompletionDictionary(this.text());
  372. },
  373. /**
  374. * @param {number} lineNumber
  375. * @param {number} column
  376. * @return {?{x: number, y: number, height: number}}
  377. */
  378. cursorPositionToCoordinates: function(lineNumber, column)
  379. {
  380. if (lineNumber >= this._codeMirror.lineCount() || lineNumber < 0 || column < 0 || column > this._codeMirror.getLine(lineNumber).length)
  381. return null;
  382. var metrics = this._codeMirror.cursorCoords(new CodeMirror.Pos(lineNumber, column));
  383. return {
  384. x: metrics.left,
  385. y: metrics.top,
  386. height: metrics.bottom - metrics.top
  387. };
  388. },
  389. /**
  390. * @param {number} x
  391. * @param {number} y
  392. * @return {?WebInspector.TextRange}
  393. */
  394. coordinatesToCursorPosition: function(x, y)
  395. {
  396. var element = document.elementFromPoint(x, y);
  397. if (!element || !element.isSelfOrDescendant(this._codeMirror.getWrapperElement()))
  398. return null;
  399. var gutterBox = this._codeMirror.getGutterElement().boxInWindow();
  400. if (x >= gutterBox.x && x <= gutterBox.x + gutterBox.width &&
  401. y >= gutterBox.y && y <= gutterBox.y + gutterBox.height)
  402. return null;
  403. var coords = this._codeMirror.coordsChar({left: x, top: y});
  404. return this._toRange(coords, coords);
  405. },
  406. /**
  407. * @param {number} lineNumber
  408. * @param {number} column
  409. * @return {?{startColumn: number, endColumn: number, type: string}}
  410. */
  411. tokenAtTextPosition: function(lineNumber, column)
  412. {
  413. if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
  414. return null;
  415. var token = this._codeMirror.getTokenAt(new CodeMirror.Pos(lineNumber, (column || 0) + 1));
  416. if (!token || !token.type)
  417. return null;
  418. var convertedType = WebInspector.CodeMirrorUtils.convertTokenType(token.type);
  419. if (!convertedType)
  420. return null;
  421. return {
  422. startColumn: token.start,
  423. endColumn: token.end - 1,
  424. type: convertedType
  425. };
  426. },
  427. /**
  428. * @param {WebInspector.TextRange} textRange
  429. * @return {string}
  430. */
  431. copyRange: function(textRange)
  432. {
  433. var pos = this._toPos(textRange.normalize());
  434. return this._codeMirror.getRange(pos.start, pos.end);
  435. },
  436. /**
  437. * @return {boolean}
  438. */
  439. isClean: function()
  440. {
  441. return this._codeMirror.isClean();
  442. },
  443. markClean: function()
  444. {
  445. this._codeMirror.markClean();
  446. },
  447. _hasLongLines: function()
  448. {
  449. function lineIterator(lineHandle)
  450. {
  451. if (lineHandle.text.length > WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold)
  452. hasLongLines = true;
  453. return hasLongLines;
  454. }
  455. var hasLongLines = false;
  456. this._codeMirror.eachLine(lineIterator);
  457. return hasLongLines;
  458. },
  459. /**
  460. * @param {string} mimeType
  461. * @return {string}
  462. */
  463. _whitespaceOverlayMode: function(mimeType)
  464. {
  465. var modeName = CodeMirror.mimeModes[mimeType] + "+whitespaces";
  466. if (CodeMirror.modes[modeName])
  467. return modeName;
  468. function modeConstructor(config, parserConfig)
  469. {
  470. function nextToken(stream)
  471. {
  472. if (stream.peek() === " ") {
  473. var spaces = 0;
  474. while (spaces < WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan && stream.peek() === " ") {
  475. ++spaces;
  476. stream.next();
  477. }
  478. return "whitespace whitespace-" + spaces;
  479. }
  480. while (!stream.eol() && stream.peek() !== " ")
  481. stream.next();
  482. return null;
  483. }
  484. var whitespaceMode = {
  485. token: nextToken
  486. };
  487. return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false);
  488. }
  489. CodeMirror.defineMode(modeName, modeConstructor);
  490. return modeName;
  491. },
  492. _enableLongLinesMode: function()
  493. {
  494. this._codeMirror.setOption("styleSelectedText", false);
  495. this._longLinesMode = true;
  496. },
  497. _disableLongLinesMode: function()
  498. {
  499. this._codeMirror.setOption("styleSelectedText", true);
  500. this._longLinesMode = false;
  501. },
  502. _updateCodeMirrorMode: function()
  503. {
  504. var showWhitespaces = WebInspector.settings.showWhitespacesInEditor.get();
  505. this._codeMirror.setOption("mode", showWhitespaces ? this._whitespaceOverlayMode(this._mimeType) : this._mimeType);
  506. },
  507. /**
  508. * @param {string} mimeType
  509. */
  510. setMimeType: function(mimeType)
  511. {
  512. this._mimeType = mimeType;
  513. if (this._hasLongLines())
  514. this._enableLongLinesMode();
  515. else
  516. this._disableLongLinesMode();
  517. this._updateCodeMirrorMode();
  518. },
  519. /**
  520. * @param {boolean} readOnly
  521. */
  522. setReadOnly: function(readOnly)
  523. {
  524. this.element.enableStyleClass("CodeMirror-readonly", readOnly)
  525. this._codeMirror.setOption("readOnly", readOnly);
  526. },
  527. /**
  528. * @return {boolean}
  529. */
  530. readOnly: function()
  531. {
  532. return !!this._codeMirror.getOption("readOnly");
  533. },
  534. /**
  535. * @param {Object} highlightDescriptor
  536. */
  537. removeHighlight: function(highlightDescriptor)
  538. {
  539. highlightDescriptor.clear();
  540. },
  541. /**
  542. * @param {WebInspector.TextRange} range
  543. * @param {string} cssClass
  544. * @return {Object}
  545. */
  546. highlightRange: function(range, cssClass)
  547. {
  548. cssClass = "CodeMirror-persist-highlight " + cssClass;
  549. var pos = this._toPos(range);
  550. ++pos.end.ch;
  551. return this._codeMirror.markText(pos.start, pos.end, {
  552. className: cssClass,
  553. startStyle: cssClass + "-start",
  554. endStyle: cssClass + "-end"
  555. });
  556. },
  557. /**
  558. * @param {string} regex
  559. * @param {string} cssClass
  560. * @return {Object}
  561. */
  562. highlightRegex: function(regex, cssClass) { },
  563. /**
  564. * @return {Element}
  565. */
  566. defaultFocusedElement: function()
  567. {
  568. return this.element;
  569. },
  570. focus: function()
  571. {
  572. this._codeMirror.focus();
  573. },
  574. _handleElementFocus: function()
  575. {
  576. this._codeMirror.focus();
  577. },
  578. beginUpdates: function()
  579. {
  580. ++this._nestedUpdatesCounter;
  581. },
  582. endUpdates: function()
  583. {
  584. if (!--this._nestedUpdatesCounter)
  585. this._codeMirror.refresh();
  586. },
  587. /**
  588. * @param {number} lineNumber
  589. */
  590. revealLine: function(lineNumber)
  591. {
  592. this._innerRevealLine(lineNumber, this._codeMirror.getScrollInfo());
  593. },
  594. /**
  595. * @param {number} lineNumber
  596. * @param {{left: number, top: number, width: number, height: number, clientWidth: number, clientHeight: number}} scrollInfo
  597. */
  598. _innerRevealLine: function(lineNumber, scrollInfo)
  599. {
  600. var topLine = this._codeMirror.lineAtHeight(scrollInfo.top, "local");
  601. var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
  602. var linesPerScreen = bottomLine - topLine + 1;
  603. if (lineNumber < topLine) {
  604. var topLineToReveal = Math.max(lineNumber - (linesPerScreen / 2) + 1, 0) | 0;
  605. this._codeMirror.scrollIntoView(new CodeMirror.Pos(topLineToReveal, 0));
  606. } else if (lineNumber > bottomLine) {
  607. var bottomLineToReveal = Math.min(lineNumber + (linesPerScreen / 2) - 1, this.linesCount - 1) | 0;
  608. this._codeMirror.scrollIntoView(new CodeMirror.Pos(bottomLineToReveal, 0));
  609. }
  610. },
  611. _gutterClick: function(instance, lineNumber, gutter, event)
  612. {
  613. this.dispatchEventToListeners(WebInspector.TextEditor.Events.GutterClick, { lineNumber: lineNumber, event: event });
  614. },
  615. _contextMenu: function(event)
  616. {
  617. var contextMenu = new WebInspector.ContextMenu(event);
  618. var target = event.target.enclosingNodeOrSelfWithClass("CodeMirror-gutter-elt");
  619. if (target)
  620. this._delegate.populateLineGutterContextMenu(contextMenu, parseInt(target.textContent, 10) - 1);
  621. else
  622. this._delegate.populateTextAreaContextMenu(contextMenu, 0);
  623. contextMenu.show();
  624. },
  625. /**
  626. * @param {number} lineNumber
  627. * @param {boolean} disabled
  628. * @param {boolean} conditional
  629. */
  630. addBreakpoint: function(lineNumber, disabled, conditional)
  631. {
  632. if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
  633. return;
  634. var className = "cm-breakpoint" + (conditional ? " cm-breakpoint-conditional" : "") + (disabled ? " cm-breakpoint-disabled" : "");
  635. this._codeMirror.addLineClass(lineNumber, "wrap", className);
  636. },
  637. /**
  638. * @param {number} lineNumber
  639. */
  640. removeBreakpoint: function(lineNumber)
  641. {
  642. if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount())
  643. return;
  644. var wrapClasses = this._codeMirror.getLineHandle(lineNumber).wrapClass;
  645. if (!wrapClasses)
  646. return;
  647. var classes = wrapClasses.split(" ");
  648. for(var i = 0; i < classes.length; ++i) {
  649. if (classes[i].startsWith("cm-breakpoint"))
  650. this._codeMirror.removeLineClass(lineNumber, "wrap", classes[i]);
  651. }
  652. },
  653. /**
  654. * @param {number} lineNumber
  655. */
  656. setExecutionLine: function(lineNumber)
  657. {
  658. this._executionLine = this._codeMirror.getLineHandle(lineNumber);
  659. this._codeMirror.addLineClass(this._executionLine, "wrap", "cm-execution-line");
  660. },
  661. clearExecutionLine: function()
  662. {
  663. if (this._executionLine)
  664. this._codeMirror.removeLineClass(this._executionLine, "wrap", "cm-execution-line");
  665. delete this._executionLine;
  666. },
  667. /**
  668. * @param {number} lineNumber
  669. * @param {Element} element
  670. */
  671. addDecoration: function(lineNumber, element)
  672. {
  673. var widget = this._codeMirror.addLineWidget(lineNumber, element);
  674. this._elementToWidget.put(element, widget);
  675. },
  676. /**
  677. * @param {number} lineNumber
  678. * @param {Element} element
  679. */
  680. removeDecoration: function(lineNumber, element)
  681. {
  682. var widget = this._elementToWidget.remove(element);
  683. if (widget)
  684. this._codeMirror.removeLineWidget(widget);
  685. },
  686. /**
  687. * @param {number} lineNumber
  688. * @param {number=} columnNumber
  689. */
  690. highlightPosition: function(lineNumber, columnNumber)
  691. {
  692. if (lineNumber < 0)
  693. return;
  694. lineNumber = Math.min(lineNumber, this._codeMirror.lineCount() - 1);
  695. if (typeof columnNumber !== "number" || columnNumber < 0 || columnNumber > this._codeMirror.getLine(lineNumber).length)
  696. columnNumber = 0;
  697. this.clearPositionHighlight();
  698. this._highlightedLine = this._codeMirror.getLineHandle(lineNumber);
  699. if (!this._highlightedLine)
  700. return;
  701. this.revealLine(lineNumber);
  702. this._codeMirror.addLineClass(this._highlightedLine, null, "cm-highlight");
  703. this._clearHighlightTimeout = setTimeout(this.clearPositionHighlight.bind(this), 2000);
  704. if (!this.readOnly())
  705. this._codeMirror.setSelection(new CodeMirror.Pos(lineNumber, columnNumber));
  706. },
  707. clearPositionHighlight: function()
  708. {
  709. if (this._clearHighlightTimeout)
  710. clearTimeout(this._clearHighlightTimeout);
  711. delete this._clearHighlightTimeout;
  712. if (this._highlightedLine)
  713. this._codeMirror.removeLineClass(this._highlightedLine, null, "cm-highlight");
  714. delete this._highlightedLine;
  715. },
  716. /**
  717. * @return {Array.<Element>}
  718. */
  719. elementsToRestoreScrollPositionsFor: function()
  720. {
  721. return [];
  722. },
  723. /**
  724. * @param {WebInspector.TextEditor} textEditor
  725. */
  726. inheritScrollPositions: function(textEditor)
  727. {
  728. },
  729. /**
  730. * @param {number} width
  731. * @param {number} height
  732. */
  733. _updatePaddingBottom: function(width, height)
  734. {
  735. var scrollInfo = this._codeMirror.getScrollInfo();
  736. var newPaddingBottom;
  737. var linesElement = this.element.firstChild.querySelector(".CodeMirror-lines");
  738. var lineCount = this._codeMirror.lineCount();
  739. if (lineCount <= 1)
  740. newPaddingBottom = 0;
  741. else
  742. newPaddingBottom = Math.max(scrollInfo.clientHeight - this._codeMirror.getLineHandle(this._codeMirror.lastLine()).height, 0);
  743. newPaddingBottom += "px";
  744. linesElement.style.paddingBottom = newPaddingBottom;
  745. this._codeMirror.setSize(width, height);
  746. },
  747. _resizeEditor: function()
  748. {
  749. var parentElement = this.element.parentElement;
  750. if (!parentElement || !this.isShowing())
  751. return;
  752. var scrollInfo = this._codeMirror.getScrollInfo();
  753. var width = parentElement.offsetWidth;
  754. var height = parentElement.offsetHeight;
  755. this._codeMirror.setSize(width, height);
  756. this._updatePaddingBottom(width, height);
  757. this._codeMirror.scrollTo(scrollInfo.left, scrollInfo.top);
  758. },
  759. onResize: function()
  760. {
  761. this._resizeEditor();
  762. },
  763. /**
  764. * @param {WebInspector.TextRange} range
  765. * @param {string} text
  766. * @return {WebInspector.TextRange}
  767. */
  768. editRange: function(range, text)
  769. {
  770. var pos = this._toPos(range);
  771. this._codeMirror.replaceRange(text, pos.start, pos.end);
  772. var newRange = this._toRange(pos.start, this._codeMirror.posFromIndex(this._codeMirror.indexFromPos(pos.start) + text.length));
  773. this._delegate.onTextChanged(range, newRange);
  774. if (WebInspector.settings.textEditorAutoDetectIndent.get())
  775. this._updateEditorIndentation();
  776. return newRange;
  777. },
  778. /**
  779. * @param {number} lineNumber
  780. * @param {number} column
  781. * @param {boolean=} prefixOnly
  782. * @return {?WebInspector.TextRange}
  783. */
  784. _wordRangeForCursorPosition: function(lineNumber, column, prefixOnly)
  785. {
  786. var line = this.line(lineNumber);
  787. if (column === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(column - 1)))
  788. return null;
  789. var wordStart = column - 1;
  790. while(wordStart > 0 && WebInspector.TextUtils.isWordChar(line.charAt(wordStart - 1)))
  791. --wordStart;
  792. if (prefixOnly)
  793. return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, column);
  794. var wordEnd = column;
  795. while(wordEnd < line.length && WebInspector.TextUtils.isWordChar(line.charAt(wordEnd)))
  796. ++wordEnd;
  797. return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, wordEnd);
  798. },
  799. _beforeChange: function(codeMirror, changeObject)
  800. {
  801. if (!this._dictionary)
  802. return;
  803. this._updatedLines = this._updatedLines || {};
  804. for(var i = changeObject.from.line; i <= changeObject.to.line; ++i)
  805. this._updatedLines[i] = this.line(i);
  806. },
  807. /**
  808. * @param {CodeMirror} codeMirror
  809. * @param {{origin: string, text: Array.<string>, removed: Array.<string>}} changeObject
  810. */
  811. _change: function(codeMirror, changeObject)
  812. {
  813. // We do not show "scroll beyond end of file" span for one line documents, so we need to check if "document has one line" changed.
  814. var hasOneLine = this._codeMirror.lineCount() === 1;
  815. if (hasOneLine !== this._hasOneLine)
  816. this._resizeEditor();
  817. this._hasOneLine = hasOneLine;
  818. var widgets = this._elementToWidget.values();
  819. for (var i = 0; i < widgets.length; ++i)
  820. this._codeMirror.removeLineWidget(widgets[i]);
  821. this._elementToWidget.clear();
  822. if (this._updatedLines) {
  823. for(var lineNumber in this._updatedLines)
  824. this._removeTextFromCompletionDictionary(this._updatedLines[lineNumber]);
  825. delete this._updatedLines;
  826. }
  827. var linesToUpdate = {};
  828. var singleCharInput = false;
  829. do {
  830. var oldRange = this._toRange(changeObject.from, changeObject.to);
  831. var newRange = oldRange.clone();
  832. var linesAdded = changeObject.text.length;
  833. singleCharInput = (changeObject.origin === "+input" && changeObject.text.length === 1 && changeObject.text[0].length === 1) ||
  834. (changeObject.origin === "+delete" && changeObject.removed.length === 1 && changeObject.removed[0].length === 1);
  835. if (linesAdded === 0) {
  836. newRange.endLine = newRange.startLine;
  837. newRange.endColumn = newRange.startColumn;
  838. } else if (linesAdded === 1) {
  839. newRange.endLine = newRange.startLine;
  840. newRange.endColumn = newRange.startColumn + changeObject.text[0].length;
  841. } else {
  842. newRange.endLine = newRange.startLine + linesAdded - 1;
  843. newRange.endColumn = changeObject.text[linesAdded - 1].length;
  844. }
  845. if (!this._muteTextChangedEvent)
  846. this._delegate.onTextChanged(oldRange, newRange);
  847. for(var i = newRange.startLine; i <= newRange.endLine; ++i) {
  848. linesToUpdate[i] = true;
  849. }
  850. if (this._dictionary) {
  851. for(var i = newRange.startLine; i <= newRange.endLine; ++i)
  852. linesToUpdate[i] = this.line(i);
  853. }
  854. } while (changeObject = changeObject.next);
  855. if (this._dictionary) {
  856. for(var lineNumber in linesToUpdate)
  857. this._addTextToCompletionDictionary(linesToUpdate[lineNumber]);
  858. }
  859. if (singleCharInput)
  860. this._autocompleteController.autocomplete();
  861. },
  862. _cursorActivity: function()
  863. {
  864. var start = this._codeMirror.getCursor("anchor");
  865. var end = this._codeMirror.getCursor("head");
  866. this._delegate.selectionChanged(this._toRange(start, end));
  867. if (!this._tokenHighlighter.highlightedRegex())
  868. this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
  869. },
  870. _scroll: function()
  871. {
  872. if (this._scrollTimer)
  873. clearTimeout(this._scrollTimer);
  874. var topmostLineNumber = this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
  875. this._scrollTimer = setTimeout(this._delegate.scrollChanged.bind(this._delegate, topmostLineNumber), 100);
  876. },
  877. _focus: function()
  878. {
  879. this._delegate.editorFocused();
  880. },
  881. _blur: function()
  882. {
  883. this._autocompleteController.finishAutocomplete();
  884. },
  885. /**
  886. * @param {number} lineNumber
  887. */
  888. scrollToLine: function(lineNumber)
  889. {
  890. var pos = new CodeMirror.Pos(lineNumber, 0);
  891. var coords = this._codeMirror.charCoords(pos, "local");
  892. this._codeMirror.scrollTo(0, coords.top);
  893. },
  894. /**
  895. * @return {number}
  896. */
  897. firstVisibleLine: function()
  898. {
  899. return this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local");
  900. },
  901. /**
  902. * @return {number}
  903. */
  904. lastVisibleLine: function()
  905. {
  906. var scrollInfo = this._codeMirror.getScrollInfo();
  907. return this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
  908. },
  909. /**
  910. * @return {WebInspector.TextRange}
  911. */
  912. selection: function()
  913. {
  914. var start = this._codeMirror.getCursor("anchor");
  915. var end = this._codeMirror.getCursor("head");
  916. return this._toRange(start, end);
  917. },
  918. /**
  919. * @return {WebInspector.TextRange?}
  920. */
  921. lastSelection: function()
  922. {
  923. return this._lastSelection;
  924. },
  925. /**
  926. * @param {WebInspector.TextRange} textRange
  927. */
  928. setSelection: function(textRange)
  929. {
  930. this._lastSelection = textRange;
  931. var pos = this._toPos(textRange);
  932. this._codeMirror.setSelection(pos.start, pos.end);
  933. },
  934. /**
  935. * @param {string} text
  936. */
  937. _detectLineSeparator: function(text)
  938. {
  939. this._lineSeparator = text.indexOf("\r\n") >= 0 ? "\r\n" : "\n";
  940. },
  941. /**
  942. * @param {string} text
  943. */
  944. setText: function(text)
  945. {
  946. this._muteTextChangedEvent = true;
  947. this._codeMirror.setValue(text);
  948. this._updateEditorIndentation();
  949. if (this._shouldClearHistory) {
  950. this._codeMirror.clearHistory();
  951. this._shouldClearHistory = false;
  952. }
  953. this._detectLineSeparator(text);
  954. delete this._muteTextChangedEvent;
  955. },
  956. /**
  957. * @return {string}
  958. */
  959. text: function()
  960. {
  961. return this._codeMirror.getValue().replace(/\n/g, this._lineSeparator);
  962. },
  963. /**
  964. * @return {WebInspector.TextRange}
  965. */
  966. range: function()
  967. {
  968. var lineCount = this.linesCount;
  969. var lastLine = this._codeMirror.getLine(lineCount - 1);
  970. return this._toRange(new CodeMirror.Pos(0, 0), new CodeMirror.Pos(lineCount - 1, lastLine.length));
  971. },
  972. /**
  973. * @param {number} lineNumber
  974. * @return {string}
  975. */
  976. line: function(lineNumber)
  977. {
  978. return this._codeMirror.getLine(lineNumber);
  979. },
  980. /**
  981. * @return {number}
  982. */
  983. get linesCount()
  984. {
  985. return this._codeMirror.lineCount();
  986. },
  987. /**
  988. * @param {number} line
  989. * @param {string} name
  990. * @param {Object?} value
  991. */
  992. setAttribute: function(line, name, value)
  993. {
  994. if (line < 0 || line >= this._codeMirror.lineCount())
  995. return;
  996. var handle = this._codeMirror.getLineHandle(line);
  997. if (handle.attributes === undefined) handle.attributes = {};
  998. handle.attributes[name] = value;
  999. },
  1000. /**
  1001. * @param {number} line
  1002. * @param {string} name
  1003. * @return {?Object} value
  1004. */
  1005. getAttribute: function(line, name)
  1006. {
  1007. if (line < 0 || line >= this._codeMirror.lineCount())
  1008. return null;
  1009. var handle = this._codeMirror.getLineHandle(line);
  1010. return handle.attributes && handle.attributes[name] !== undefined ? handle.attributes[name] : null;
  1011. },
  1012. /**
  1013. * @param {number} line
  1014. * @param {string} name
  1015. */
  1016. removeAttribute: function(line, name)
  1017. {
  1018. if (line < 0 || line >= this._codeMirror.lineCount())
  1019. return;
  1020. var handle = this._codeMirror.getLineHandle(line);
  1021. if (handle && handle.attributes)
  1022. delete handle.attributes[name];
  1023. },
  1024. /**
  1025. * @param {WebInspector.TextRange} range
  1026. * @return {{start: CodeMirror.Pos, end: CodeMirror.Pos}}
  1027. */
  1028. _toPos: function(range)
  1029. {
  1030. return {
  1031. start: new CodeMirror.Pos(range.startLine, range.startColumn),
  1032. end: new CodeMirror.Pos(range.endLine, range.endColumn)
  1033. }
  1034. },
  1035. _toRange: function(start, end)
  1036. {
  1037. return new WebInspector.TextRange(start.line, start.ch, end.line, end.ch);
  1038. },
  1039. __proto__: WebInspector.View.prototype
  1040. }
  1041. /**
  1042. * @constructor
  1043. * @param {CodeMirror} codeMirror
  1044. */
  1045. WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(codeMirror)
  1046. {
  1047. this._codeMirror = codeMirror;
  1048. }
  1049. WebInspector.CodeMirrorTextEditor.TokenHighlighter.prototype = {
  1050. /**
  1051. * @param {RegExp} regex
  1052. * @param {WebInspector.TextRange} range
  1053. */
  1054. highlightSearchResults: function(regex, range)
  1055. {
  1056. var oldRegex = this._highlightRegex;
  1057. this._highlightRegex = regex;
  1058. this._highlightRange = range;
  1059. if (this._searchResultMarker) {
  1060. this._searchResultMarker.clear();
  1061. delete this._searchResultMarker;
  1062. }
  1063. if (this._highlightDescriptor && this._highlightDescriptor.selectionStart)
  1064. this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection");
  1065. var selectionStart = this._highlightRange ? new CodeMirror.Pos(this._highlightRange.startLine, this._highlightRange.startColumn) : null;
  1066. if (selectionStart)
  1067. this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection");
  1068. if (this._highlightRegex === oldRegex) {
  1069. // Do not re-add overlay mode if regex did not change for better performance.
  1070. if (this._highlightDescriptor)
  1071. this._highlightDescriptor.selectionStart = selectionStart;
  1072. } else {
  1073. this._removeHighlight();
  1074. this._setHighlighter(this._searchHighlighter.bind(this, this._highlightRegex, this._highlightRange), selectionStart);
  1075. }
  1076. if (selectionStart) {
  1077. var pos = WebInspector.CodeMirrorTextEditor.prototype._toPos(this._highlightRange);
  1078. this._searchResultMarker = this._codeMirror.markText(pos.start, pos.end, {className: "cm-column-with-selection"});
  1079. }
  1080. },
  1081. highlightedRegex: function()
  1082. {
  1083. return this._highlightRegex;
  1084. },
  1085. highlightSelectedTokens: function()
  1086. {
  1087. delete this._highlightRegex;
  1088. delete this._highlightRange;
  1089. if (this._highlightDescriptor && this._highlightDescriptor.selectionStart)
  1090. this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, "wrap", "cm-line-with-selection");
  1091. this._removeHighlight();
  1092. var selectionStart = this._codeMirror.getCursor("start");
  1093. var selectionEnd = this._codeMirror.getCursor("end");
  1094. if (selectionStart.line !== selectionEnd.line)
  1095. return;
  1096. if (selectionStart.ch === selectionEnd.ch)
  1097. return;
  1098. var selectedText = this._codeMirror.getSelection();
  1099. if (this._isWord(selectedText, selectionStart.line, selectionStart.ch, selectionEnd.ch)) {
  1100. if (selectionStart)
  1101. this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection")
  1102. this._setHighlighter(this._tokenHighlighter.bind(this, selectedText, selectionStart), selectionStart);
  1103. }
  1104. },
  1105. /**
  1106. * @param {string} selectedText
  1107. * @param {number} lineNumber
  1108. * @param {number} startColumn
  1109. * @param {number} endColumn
  1110. */
  1111. _isWord: function(selectedText, lineNumber, startColumn, endColumn)
  1112. {
  1113. var line = this._codeMirror.getLine(lineNumber);
  1114. var leftBound = startColumn === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(startColumn - 1));
  1115. var rightBound = endColumn === line.length || !WebInspector.TextUtils.isWordChar(line.charAt(endColumn));
  1116. return leftBound && rightBound && WebInspector.TextUtils.isWord(selectedText);
  1117. },
  1118. _removeHighlight: function()
  1119. {
  1120. if (this._highlightDescriptor) {
  1121. this._codeMirror.removeOverlay(this._highlightDescriptor.overlay);
  1122. delete this._highlightDescriptor;
  1123. }
  1124. },
  1125. /**
  1126. * @param {RegExp} regex
  1127. * @param {WebInspector.TextRange} range
  1128. * @param {CodeMirror.StringStream} stream
  1129. */
  1130. _searchHighlighter: function(regex, range, stream)
  1131. {
  1132. if (stream.column() === 0)
  1133. delete this._searchMatchLength;
  1134. if (this._searchMatchLength) {
  1135. if (this._searchMatchLength > 1) {
  1136. for (var i = 0; i < this._searchMatchLength - 2; ++i)
  1137. stream.next();
  1138. this._searchMatchLength = 1;
  1139. return "search-highlight";
  1140. } else {
  1141. stream.next();
  1142. delete this._searchMatchLength;
  1143. return "search-highlight search-highlight-end";
  1144. }
  1145. }
  1146. var match = stream.match(regex, false);
  1147. if (match) {
  1148. stream.next();
  1149. var matchLength = match[0].length;
  1150. if (matchLength === 1)
  1151. return "search-highlight search-highlight-full";
  1152. this._searchMatchLength = matchLength;
  1153. return "search-highlight search-highlight-start";
  1154. }
  1155. while (!stream.match(regex, false) && stream.next()) {};
  1156. },
  1157. /**
  1158. * @param {string} token
  1159. * @param {CodeMirror.Pos} selectionStart
  1160. * @param {CodeMirror.StringStream} stream
  1161. */
  1162. _tokenHighlighter: function(token, selectionStart, stream)
  1163. {
  1164. var tokenFirstChar = token.charAt(0);
  1165. if (stream.match(token) && (stream.eol() || !WebInspector.TextUtils.isWordChar(stream.peek())))
  1166. return stream.column() === selectionStart.ch ? "token-highlight column-with-selection" : "token-highlight";
  1167. var eatenChar;
  1168. do {
  1169. eatenChar = stream.next();
  1170. } while (eatenChar && (WebInspector.TextUtils.isWordChar(eatenChar) || stream.peek() !== tokenFirstChar));
  1171. },
  1172. /**
  1173. * @param {function(CodeMirror.StringStream)} highlighter
  1174. */
  1175. _setHighlighter: function(highlighter, selectionStart)
  1176. {
  1177. var overlayMode = {
  1178. token: highlighter
  1179. };
  1180. this._codeMirror.addOverlay(overlayMode);
  1181. this._highlightDescriptor = {
  1182. overlay: overlayMode,
  1183. selectionStart: selectionStart
  1184. };
  1185. }
  1186. }
  1187. /**
  1188. * @constructor
  1189. * @param {CodeMirror} codeMirror
  1190. */
  1191. WebInspector.CodeMirrorTextEditor.BlockIndentController = function(codeMirror)
  1192. {
  1193. codeMirror.addKeyMap(this);
  1194. }
  1195. WebInspector.CodeMirrorTextEditor.BlockIndentController.prototype = {
  1196. name: "blockIndentKeymap",
  1197. Enter: function(codeMirror)
  1198. {
  1199. if (codeMirror.somethingSelected())
  1200. return CodeMirror.Pass;
  1201. var cursor = codeMirror.getCursor();
  1202. if (cursor.ch === 0)
  1203. return CodeMirror.Pass;
  1204. var line = codeMirror.getLine(cursor.line);
  1205. if (line.substr(cursor.ch - 1, 2) === "{}") {
  1206. codeMirror.execCommand("newlineAndIndent");
  1207. codeMirror.setCursor(cursor);
  1208. codeMirror.execCommand("newlineAndIndent");
  1209. codeMirror.execCommand("indentMore");
  1210. } else if (line.substr(cursor.ch - 1, 1) === "{") {
  1211. codeMirror.execCommand("newlineAndIndent");
  1212. codeMirror.execCommand("indentMore");
  1213. } else
  1214. return CodeMirror.Pass;
  1215. },
  1216. "'}'": function(codeMirror)
  1217. {
  1218. var cursor = codeMirror.getCursor();
  1219. var line = codeMirror.getLine(cursor.line);
  1220. for(var i = 0 ; i < line.length; ++i)
  1221. if (!WebInspector.TextUtils.isSpaceChar(line.charAt(i)))
  1222. return CodeMirror.Pass;
  1223. codeMirror.replaceRange("}", cursor);
  1224. var matchingBracket = codeMirror.findMatchingBracket();
  1225. if (!matchingBracket || !matchingBracket.match)
  1226. return;
  1227. line = codeMirror.getLine(matchingBracket.to.line);
  1228. var desiredIndentation = 0;
  1229. while (desiredIndentation < line.length && WebInspector.TextUtils.isSpaceChar(line.charAt(desiredIndentation)))
  1230. ++desiredIndentation;
  1231. codeMirror.replaceRange(line.substr(0, desiredIndentation) + "}", new CodeMirror.Pos(cursor.line, 0), new CodeMirror.Pos(cursor.line, cursor.ch + 1));
  1232. }
  1233. }
  1234. /**
  1235. * @constructor
  1236. * @param {CodeMirror} codeMirror
  1237. */
  1238. WebInspector.CodeMirrorTextEditor.FixWordMovement = function(codeMirror)
  1239. {
  1240. function moveLeft(shift, codeMirror)
  1241. {
  1242. var cursor = codeMirror.getCursor("head");
  1243. if (cursor.ch !== 0 || cursor.line === 0)
  1244. return CodeMirror.Pass;
  1245. codeMirror.setExtending(shift);
  1246. codeMirror.execCommand("goLineUp");
  1247. codeMirror.execCommand("goLineEnd")
  1248. codeMirror.setExtending(false);
  1249. }
  1250. function moveRight(shift, codeMirror)
  1251. {
  1252. var cursor = codeMirror.getCursor("head");
  1253. var line = codeMirror.getLine(cursor.line);
  1254. if (cursor.ch !== line.length || cursor.line + 1 === codeMirror.lineCount())
  1255. return CodeMirror.Pass;
  1256. codeMirror.setExtending(shift);
  1257. codeMirror.execCommand("goLineDown");
  1258. codeMirror.execCommand("goLineStart");
  1259. codeMirror.setExtending(false);
  1260. }
  1261. function delWordBack(codeMirror)
  1262. {
  1263. if (codeMirror.somethingSelected())
  1264. return CodeMirror.Pass;
  1265. var cursor = codeMirror.getCursor("head");
  1266. if (cursor.ch === 0)
  1267. codeMirror.execCommand("delCharBefore");
  1268. else
  1269. return CodeMirror.Pass;
  1270. }
  1271. var modifierKey = WebInspector.isMac() ? "Alt" : "Ctrl";
  1272. var leftKey = modifierKey + "-Left";
  1273. var rightKey = modifierKey + "-Right";
  1274. var keyMap = {};
  1275. keyMap[leftKey] = moveLeft.bind(this, false);
  1276. keyMap[rightKey] = moveRight.bind(this, false);
  1277. keyMap["Shift-" + leftKey] = moveLeft.bind(this, true);
  1278. keyMap["Shift-" + rightKey] = moveRight.bind(this, true);
  1279. keyMap[modifierKey + "-Backspace"] = delWordBack.bind(this);
  1280. codeMirror.addKeyMap(keyMap);
  1281. }
  1282. /**
  1283. * @constructor
  1284. * @implements {WebInspector.SuggestBoxDelegate}
  1285. * @param {WebInspector.CodeMirrorTextEditor} textEditor
  1286. * @param {CodeMirror} codeMirror
  1287. */
  1288. WebInspector.CodeMirrorTextEditor.AutocompleteController = function(textEditor, codeMirror)
  1289. {
  1290. this._textEditor = textEditor;
  1291. this._codeMirror = codeMirror;
  1292. this._codeMirror.on("scroll", this._onScroll.bind(this));
  1293. this._codeMirror.on("cursorActivity", this._onCursorActivity.bind(this));
  1294. }
  1295. WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = {
  1296. autocomplete: function()
  1297. {
  1298. var dictionary = this._textEditor._dictionary;
  1299. if (!dictionary || this._codeMirror.somethingSelected()) {
  1300. this.finishAutocomplete();
  1301. return;
  1302. }
  1303. var cursor = this._codeMirror.getCursor();
  1304. var substituteRange = this._textEditor._wordRangeForCursorPosition(cursor.line, cursor.ch, false);
  1305. if (!substituteRange || substituteRange.startColumn === cursor.ch) {
  1306. this.finishAutocomplete();
  1307. return;
  1308. }
  1309. var prefixRange = substituteRange.clone();
  1310. prefixRange.endColumn = cursor.ch;
  1311. var substituteWord = this._textEditor.copyRange(substituteRange);
  1312. var hasPrefixInDictionary = dictionary.hasWord(substituteWord);
  1313. if (hasPrefixInDictionary)
  1314. dictionary.removeWord(substituteWord);
  1315. var wordsWithPrefix = dictionary.wordsWithPrefix(this._textEditor.copyRange(prefixRange));
  1316. if (hasPrefixInDictionary)
  1317. dictionary.addWord(substituteWord);
  1318. function sortSuggestions(a, b)
  1319. {
  1320. return dictionary.wordCount(b) - dictionary.wordCount(a) || a.length - b.length;
  1321. }
  1322. wordsWithPrefix.sort(sortSuggestions);
  1323. if (!this._suggestBox) {
  1324. this._suggestBox = new WebInspector.SuggestBox(this, this._textEditor.element, "generic-suggest", 6);
  1325. this._anchorBox = this._anchorBoxForPosition(cursor.line, cursor.ch);
  1326. }
  1327. this._suggestBox.updateSuggestions(this._anchorBox, wordsWithPrefix, 0, true, this._textEditor.copyRange(prefixRange));
  1328. this._prefixRange = prefixRange;
  1329. if (!this._suggestBox.visible())
  1330. this.finishAutocomplete();
  1331. },
  1332. finishAutocomplete: function()
  1333. {
  1334. if (!this._suggestBox)
  1335. return;
  1336. this._suggestBox.hide();
  1337. this._suggestBox = null;
  1338. this._prefixRange = null;
  1339. this._anchorBox = null;
  1340. },
  1341. /**
  1342. * @param {Event} e
  1343. */
  1344. keyDown: function(e)
  1345. {
  1346. if (!this._suggestBox)
  1347. return false;
  1348. if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
  1349. this.finishAutocomplete();
  1350. return true;
  1351. }
  1352. if (e.keyCode === WebInspector.KeyboardShortcut.Keys.Tab.code) {
  1353. this._suggestBox.acceptSuggestion();
  1354. this.finishAutocomplete();
  1355. return true;
  1356. }
  1357. return this._suggestBox.keyPressed(e);
  1358. },
  1359. /**
  1360. * @param {string} suggestion
  1361. * @param {boolean=} isIntermediateSuggestion
  1362. */
  1363. applySuggestion: function(suggestion, isIntermediateSuggestion)
  1364. {
  1365. this._currentSuggestion = suggestion;
  1366. },
  1367. acceptSuggestion: function()
  1368. {
  1369. if (this._prefixRange.endColumn - this._prefixRange.startColumn !== this._currentSuggestion.length) {
  1370. var pos = this._textEditor._toPos(this._prefixRange);
  1371. this._codeMirror.replaceRange(this._currentSuggestion, pos.start, pos.end, "+autocomplete");
  1372. }
  1373. },
  1374. _onScroll: function()
  1375. {
  1376. if (!this._suggestBox)
  1377. return;
  1378. var cursor = this._codeMirror.getCursor();
  1379. var scrollInfo = this._codeMirror.getScrollInfo();
  1380. var topmostLineNumber = this._codeMirror.lineAtHeight(scrollInfo.top, "local");
  1381. var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local");
  1382. if (cursor.line < topmostLineNumber || cursor.line > bottomLine)
  1383. this.finishAutocomplete();
  1384. else {
  1385. this._anchorBox = this._anchorBoxForPosition(cursor.line, cursor.ch);
  1386. this._suggestBox.setPosition(this._anchorBox);
  1387. }
  1388. },
  1389. _onCursorActivity: function()
  1390. {
  1391. if (!this._suggestBox)
  1392. return;
  1393. var cursor = this._codeMirror.getCursor();
  1394. if (cursor.line !== this._prefixRange.startLine || cursor.ch > this._prefixRange.endColumn || cursor.ch < this._prefixRange.startColumn)
  1395. this.finishAutocomplete();
  1396. },
  1397. /**
  1398. * @param {number} line
  1399. * @param {number} column
  1400. * @return {AnchorBox}
  1401. */
  1402. _anchorBoxForPosition: function(line, column)
  1403. {
  1404. var metrics = this._textEditor.cursorPositionToCoordinates(line, column);
  1405. return metrics ? new AnchorBox(metrics.x, metrics.y, 0, metrics.height) : null;
  1406. },
  1407. }