$("#editor-holder").on("contextmenu", function (e) { if ($(e.target).parents(".CodeMirror-gutter").length !== 0) { return; } // Note: on mousedown before this event, CodeMirror automatically checks mouse pos, and // if not clicking on a selection moves the cursor to click location. When triggered // from keyboard, no pre-processing occurs and the cursor/selection is left as is. var editor = EditorManager.getFocusedEditor(), inlineWidget = EditorManager.getFocusedInlineWidget(); if (editor) { // If there's just an insertion point select the word token at the cursor pos so // it's more clear what the context menu applies to. if (!editor.hasSelection()) { editor.selectWordAt(editor.getCursorPos()); // Prevent menu from overlapping text by moving it down a little // Temporarily backout this change for now to help mitigate issue #1111, // which only happens if mouse is not over context menu. Better fix // requires change to bootstrap, which is too risky for now. //e.pageY += 6; } // Inline text editors have a different context menu (safe to assume it's not some other // type of inline widget since we already know an Editor has focus) if (inlineWidget) { inline_editor_cmenu.open(e); } else { editor_cmenu.open(e); } } });
/** * Deletes the current line if there is no selection or the lines for the selection * (removing the end of line too) */ function deleteCurrentLines(editor) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var from, to, sel = editor.getSelection(), doc = editor.document; from = {line: sel.start.line, ch: 0}; to = {line: sel.end.line + 1, ch: 0}; if (to.line === editor.getLastVisibleLine() + 1) { // Instead of deleting the newline after the last line, delete the newline // before the first line--unless this is the entire visible content of the editor, // in which case just delete the line content. if (from.line > editor.getFirstVisibleLine()) { from.line -= 1; from.ch = doc.getLine(from.line).length; } to.line -= 1; to.ch = doc.getLine(to.line).length; } doc.replaceRange("", from, to); }
/** * Unindent a line of text if no selection. Otherwise, unindent all lines in selection. */ function unidentText() { var editor = EditorManager.getFocusedEditor(); if (!editor) { return; } editor._codeMirror.execCommand("indentLess"); }
/** * Moves the selected text, or current line if no selection. The cursor/selection * moves with the line/lines. * @param {Editor} editor - target editor * @param {Number} direction - direction of the move (-1,+1) => (Up,Down) */ function moveLine(editor, direction) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var doc = editor.document, sel = editor.getSelection(), originalSel = editor.getSelection(), hasSelection = (sel.start.line !== sel.end.line) || (sel.start.ch !== sel.end.ch); sel.start.ch = 0; // The end of the selection becomes the start of the next line, if it isn't already if (!hasSelection || sel.end.ch !== 0) { sel.end = {line: sel.end.line + 1, ch: 0}; } // Make the move switch (direction) { case DIRECTION_UP: if (sel.start.line !== 0) { doc.batchOperation(function () { var prevText = doc.getRange({ line: sel.start.line - 1, ch: 0 }, sel.start); if (sel.end.line === editor.lineCount()) { prevText = "\n" + prevText.substring(0, prevText.length - 1); } doc.replaceRange("", { line: sel.start.line - 1, ch: 0 }, sel.start); doc.replaceRange(prevText, { line: sel.end.line - 1, ch: 0 }); // Make sure CodeMirror hasn't expanded the selection to include // the line we inserted below. originalSel.start.line--; originalSel.end.line--; editor.setSelection(originalSel.start, originalSel.end); }); } break; case DIRECTION_DOWN: if (sel.end.line < editor.lineCount()) { doc.batchOperation(function () { var nextText = doc.getRange(sel.end, { line: sel.end.line + 1, ch: 0 }); var deletionStart = sel.end; if (sel.end.line === editor.lineCount() - 1) { nextText += "\n"; deletionStart = { line: sel.end.line - 1, ch: doc.getLine(sel.end.line - 1).length }; } doc.replaceRange("", deletionStart, { line: sel.end.line + 1, ch: 0 }); doc.replaceRange(nextText, { line: sel.start.line, ch: 0 }); }); } break; } }
/** * Creates a modal bar whose contents are the given template. * * Dispatches one event: * - close - When the bar is closed, either via close() or via autoClose. After this event, the * bar may remain visible and in the DOM while its closing animation is playing. However, * by the time "close" is fired, the bar has been "popped out" of the layout and the * editor scroll position has already been restored. * * @constructor * * @param {string} template The HTML contents of the modal bar. * @param {boolean} autoClose If true, then close the dialog if the user hits Esc * or if the bar loses focus. * @param {boolean} animate If true (the default), animate the dialog closed, otherwise * close it immediately. */ function ModalBar(template, autoClose, animate) { if (animate === undefined) { animate = true; } this._handleKeydown = this._handleKeydown.bind(this); this._handleFocusChange = this._handleFocusChange.bind(this); this._$root = $("<div class='modal-bar'/>") .html(template) .insertBefore("#editor-holder"); if (animate) { this._$root.addClass("popout offscreen"); // Forcing the renderer to do a layout, which will cause it to apply the transform for the "offscreen" // class, so it will animate when you remove the class. window.getComputedStyle(this._$root.get(0)).getPropertyValue("top"); this._$root.removeClass("popout offscreen"); } // If something *other* than an editor (like another modal bar) has focus, set the focus // to the editor here, before opening up the new modal bar. This ensures that the old // focused item has time to react and close before the new modal bar is opened. // See bugs #4287 and #3424 if (!EditorManager.getFocusedEditor()) { EditorManager.focusEditor(); } if (autoClose) { this._autoClose = true; this._$root.on("keydown", this._handleKeydown); window.document.body.addEventListener("focusin", this._handleFocusChange, true); // Set focus to the first input field, or the first button if there is no input field. // TODO: remove this logic? var $firstInput = $("input[type='text']", this._$root).first(); if ($firstInput.length > 0) { $firstInput.focus(); } else { $("button", this._$root).first().focus(); } } // Preserve scroll position of the current full editor across the editor refresh, adjusting for the // height of the modal bar so the code doesn't appear to shift if possible. var fullEditor = EditorManager.getCurrentFullEditor(), scrollPos; if (fullEditor) { scrollPos = fullEditor.getScrollPos(); } EditorManager.resizeEditor(); if (fullEditor) { fullEditor._codeMirror.scrollTo(scrollPos.x, scrollPos.y + this.height()); } }
Editor.prototype.focus = function () { // Capture the currently focused editor before making CodeMirror changes var previous = EditorManager.getFocusedEditor(); // Prevent duplicate focusedEditorChanged events with this _internalFocus flag this._internalFocus = true; this._codeMirror.focus(); this._internalFocus = false; EditorManager._doFocusedEditorChanged(this, previous); };
/** * Invokes a language-specific block-comment/uncomment handler * @param {?Editor} editor If unspecified, applies to the currently focused editor */ function blockComment(editor) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var language = editor.getLanguageForSelection(); if (language.blockComment) { blockCommentPrefixSuffix(editor, language.blockComment.prefix, language.blockComment.suffix, language.lineComment ? language.lineComment.prefix : null); } }
/** * Displays a non-modal embedded dialog above the code mirror editor that allows the user to do * a find operation across all files in the project. */ function doFindInFiles() { var dialog = new FindInFilesDialog(); // Default to searching for the current selection var currentEditor = EditorManager.getFocusedEditor(); var initialString = currentEditor && currentEditor.getSelectedText(); searchResults = []; dialog.showDialog(initialString) .done(function (query) { if (query) { StatusBar.showBusyIndicator(true); var queryExpr = _getQueryRegExp(query); FileIndexManager.getFileInfoList("all") .done(function (fileListResult) { Async.doInParallel(fileListResult, function (fileInfo) { var result = new $.Deferred(); DocumentManager.getDocumentForPath(fileInfo.fullPath) .done(function (doc) { var matches = _getSearchMatches(doc.getText(), queryExpr); if (matches && matches.length) { searchResults.push({ fullPath: fileInfo.fullPath, matches: matches }); } result.resolve(); }) .fail(function (error) { // Error reading this file. This is most likely because the file isn't a text file. // Resolve here so we move on to the next file. result.resolve(); }); return result.promise(); }) .done(function () { _showSearchResults(searchResults, query); StatusBar.hideBusyIndicator(); }) .fail(function () { console.log("find in files failed."); StatusBar.hideBusyIndicator(); }); }); } }); }
/** * Invokes a language-specific line-comment/uncomment handler * @param {?Editor} editor If unspecified, applies to the currently focused editor */ function lineComment(editor) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var mode = editor.getModeForSelection(); // Currently we only support languages with "//" commenting if (mode === "javascript" || mode === "less") { lineCommentSlashSlash(editor); } }
/** * Invokes a language-specific block-comment/uncomment handler * @param {?Editor} editor If unspecified, applies to the currently focused editor */ function blockComment(editor) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var language = editor.getLanguageForSelection(); if (language.hasBlockCommentSyntax()) { // getLineCommentPrefixes always return an array, and will be empty if no line comment syntax is defined blockCommentPrefixSuffix(editor, language.getBlockCommentPrefix(), language.getBlockCommentSuffix(), language.getLineCommentPrefixes()); } }
function handleUndoRedo(operation) { var editor = EditorManager.getFocusedEditor(); var result = new $.Deferred(); if (editor) { editor[operation](); result.resolve(); } else { result.reject(); } return result.promise(); }
function _handleSelectAll() { var result = new $.Deferred(), editor = EditorManager.getFocusedEditor(); if (editor) { editor.selectAllNoScroll(); result.resolve(); } else { result.reject(); // command not handled } return result.promise(); }
/** * Invokes a language-specific line-comment/uncomment handler * @param {?Editor} editor If unspecified, applies to the currently focused editor */ function lineComment(editor) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var language = editor.getLanguageForSelection(); if (language.hasLineCommentSyntax()) { lineCommentPrefix(editor, language.getLineCommentPrefixes()); } else if (language.hasBlockCommentSyntax()) { lineCommentPrefixSuffix(editor, language.getBlockCommentPrefix(), language.getBlockCommentSuffix()); } }
/** Launches CodeMirror's basic Find-within-single-editor feature */ function _launchFind() { var editor = EditorManager.getFocusedEditor(); if (editor) { var codeMirror = editor._codeMirror; // Bring up CodeMirror's existing search bar UI codeMirror.execCommand("find"); // Prepopulate the search field with the current selection, if any $(".CodeMirror-dialog input[type='text']") .attr("value", codeMirror.getSelection()) .get(0).select(); } }
/** * Explicitly start a new session. If we have an existing session, * then close the current one and restart a new one. * @param {Editor} editor */ function _startNewSession(editor) { if (!editor) { editor = EditorManager.getFocusedEditor(); } if (editor) { lastChar = null; if (_inSession(editor)) { _endSession(); } // Begin a new explicit session _beginSession(editor); } }
/** Invokes a language-specific line-comment/uncomment handler */ function lineComment() { var editor = EditorManager.getFocusedEditor(); if (!editor) { return; } // TODO: use mode *at cursor location*, so we can support mixed-mode e.g. JS in script blocks var mode = editor._codeMirror.getOption("mode"); // Currently we only support languages with "//" commenting if (mode === "javascript" || mode === "less") { lineCommentSlashSlash(editor); } }
function _launchFind() { var editor = EditorManager.getFocusedEditor(); if (editor) { var codeMirror = editor._codeMirror; // Bring up CodeMirror's existing search bar UI clearSearch(codeMirror); doSearch(codeMirror); // Prepopulate the search field with the current selection, if any getDialogTextField() .attr("value", codeMirror.getSelection()) .get(0).select(); } }
/** * Invokes a language-specific block-comment/uncomment handler * @param {?Editor} editor If unspecified, applies to the currently focused editor */ function blockComment(editor) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var mode = editor.getModeForSelection(); if (mode === "javascript" || mode === "less") { blockCommentPrefixSuffix(editor, "/*", "*/", true); } else if (mode === "css") { blockCommentPrefixSuffix(editor, "/*", "*/", false); } else if (mode === "html") { blockCommentPrefixSuffix(editor, "<!--", "-->", false); } }
/** * Invokes a language-specific line-comment/uncomment handler * @param {?Editor} editor If unspecified, applies to the currently focused editor */ function lineComment(editor) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var mode = editor.getModeForSelection(); // Currently we only support languages with "//" commenting if (mode === "javascript" || mode === "less") { lineCommentSlashSlash(editor); } else if (mode === "css") { lineCommentPrefixSuffix(editor, "/*", "*/"); } else if (mode === "html") { lineCommentPrefixSuffix(editor, "<!--", "-->"); } }
function selectLine(editor) { editor = editor || EditorManager.getFocusedEditor(); if (editor) { var sel = editor.getSelection(); var from = {line: sel.start.line, ch: 0}; var to = {line: sel.end.line + 1, ch: 0}; if (to.line === editor.getLastVisibleLine() + 1) { // Last line: select to end of line instead of start of (hidden/nonexistent) following line, // which due to how CM clips coords would only work some of the time to.line -= 1; to.ch = editor.document.getLine(to.line).length; } editor.setSelection(from, to); } }
/** * Saves the given file. If no file specified, assumes the current document. * @param {?{doc: Document}} commandData Document to close, or null * @return {$.Promise} a promise that is resolved after the save completes */ function handleFileSave(commandData) { // Default to current document if doc is null var doc = null; if (commandData) { doc = commandData.doc; } if (!doc) { var focusedEditor = EditorManager.getFocusedEditor(); if (focusedEditor) { doc = focusedEditor.document; } // doc may still be null, e.g. if no editors are open, but doSave() does a null check on // doc and makes sure the document is dirty before saving. } return doSave(doc); }
/** * @constructor * * Creates a modal bar whose contents are the given template. * @param {string} template The HTML contents of the modal bar. * @param {boolean} autoClose If true, then close the dialog if the user hits RETURN or ESC * in the first input field, or if the modal bar loses focus to an outside item. Dispatches * jQuery events for these cases: "closeOk" on RETURN, "closeCancel" on ESC, and "closeBlur" * on focus loss. */ function ModalBar(template, autoClose) { this._handleInputKeydown = this._handleInputKeydown.bind(this); this._handleFocusChange = this._handleFocusChange.bind(this); this._$root = $("<div class='modal-bar'/>") .html(template) .insertBefore("#editor-holder"); // If something *other* than an editor (like another modal bar) has focus, set the focus // to the editor here, before opening up the new modal bar. This ensures that the old // focused item has time to react and close before the new modal bar is opened. // See bugs #4287 and #3424 if (!EditorManager.getFocusedEditor()) { EditorManager.focusEditor(); } if (autoClose) { this._autoClose = true; var $firstInput = this._getFirstInput() .on("keydown", this._handleInputKeydown); window.document.body.addEventListener("focusin", this._handleFocusChange, true); // Set focus to the first input field, or the first button if there is no input field. if ($firstInput.length > 0) { $firstInput.focus(); } else { $("button", this._$root).first().focus(); } } // Preserve scroll position of the current full editor across the editor refresh, adjusting for the // height of the modal bar so the code doesn't appear to shift if possible. var fullEditor = EditorManager.getCurrentFullEditor(), scrollPos; if (fullEditor) { scrollPos = fullEditor.getScrollPos(); } EditorManager.resizeEditor(); if (fullEditor) { fullEditor._codeMirror.scrollTo(scrollPos.x, scrollPos.y + this.height()); } }
/** * Explicitly start a new session. If we have an existing session, * then close the current one and restart a new one. * @param {Editor} editor */ function _startNewSession(editor) { if (codeHintOpened) { return; } if (!editor) { editor = EditorManager.getFocusedEditor(); } if (editor) { lastChar = null; if (_inSession(editor)) { _endSession(); } // Begin a new explicit session _beginSession(editor); codeHintOpened = true; } }
/** * Duplicates the selected text, or current line if no selection. The cursor/selection is left * on the second copy. */ function duplicateText(editor) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var sel = editor.getSelection(); var hasSelection = (sel.start.line !== sel.end.line) || (sel.start.ch !== sel.end.ch); if (!hasSelection) { sel.start.ch = 0; sel.end = {line: sel.start.line + 1, ch: 0}; } // Make the edit var doc = editor.document; var selectedText = doc.getRange(sel.start, sel.end); doc.replaceRange(selectedText, sel.start); }
$("#editor-holder").on("contextmenu", function (e) { if ($(e.target).parents(".CodeMirror-gutter").length !== 0) { return; } // Note: on mousedown before this event, CodeMirror automatically checks mouse pos, and // if not clicking on a selection moves the cursor to click location. When triggered // from keyboard, no pre-processing occurs and the cursor/selection is left as is. var editor = EditorManager.getFocusedEditor(); if (editor) { // If there's just an insertion point select the word token at the cursor pos so // it's more clear what the context menu applies to. if (!editor.hasSelection()) { editor.selectWordAt(editor.getCursorPos()); // Prevent menu from overlapping text by moving it down a little e.pageY += 6; } editor_cmenu.open(e); } });
/** * Inserts a new and smart indented line above/below the selected text, or current line if no selection. * The cursor is moved in the new line. * @param {Editor} editor - target editor * @param {Number} direction - direction where to place the new line (-1,+1) => (Up,Down) */ function openLine(editor, direction) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var sel = editor.getSelection(), hasSelection = (sel.start.line !== sel.end.line) || (sel.start.ch !== sel.end.ch), isInlineWidget = !!EditorManager.getFocusedInlineWidget(), lastLine = editor.getLastVisibleLine(), cm = editor._codeMirror, doc = editor.document, line; // Insert the new line switch (direction) { case DIRECTION_UP: line = sel.start.line; break; case DIRECTION_DOWN: line = sel.end.line; if (!(hasSelection && sel.end.ch === 0)) { // If not linewise selection line++; } break; } if (line > lastLine && isInlineWidget) { doc.replaceRange("\n", {line: line - 1, ch: doc.getLine(line - 1).length}, null, "+input"); } else { doc.replaceRange("\n", {line: line, ch: 0}, null, "+input"); } cm.indentLine(line, "smart", false); editor.setSelection({line: line, ch: null}); }
/** * Deletes the current line if there is no selection or the lines for the selection * (removing the end of line too) */ function deleteCurrentLines(editor) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var from, to, sel = editor.getSelection(), doc = editor.document, endLine; from = {line: sel.start.line, ch: 0}; if (sel.end.line === editor.getLastVisibleLine()) { // Instead of deleting the newline after the last line, delete the newline // before the first line--unless this is the entire visible content of the editor, // in which case just delete the line content. if (from.line > editor.getFirstVisibleLine()) { from.line -= 1; from.ch = doc.getLine(from.line).length; } to = {line: sel.end.line, ch: doc.getLine(sel.end.line).length}; } else { // Don't delete the line that the selection ends on if the selection // starts on a previous line and ends right at the beginning of the // last line. endLine = sel.end.line + 1; if (sel.start.line < sel.end.line && sel.end.ch === 0) { endLine--; } to = {line: endLine, ch: 0}; } doc.replaceRange("", from, to); }
/** * Duplicates the selected text, or current line if no selection. The cursor/selection is left * on the second copy. */ function duplicateText(editor) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var sel = editor.getSelection(), hasSelection = (sel.start.line !== sel.end.line) || (sel.start.ch !== sel.end.ch), delimiter = ""; if (!hasSelection) { sel.start.ch = 0; sel.end = {line: sel.start.line + 1, ch: 0}; if (sel.end.line === editor.lineCount()) { delimiter = "\n"; } } // Make the edit var doc = editor.document; var selectedText = doc.getRange(sel.start, sel.end) + delimiter; doc.replaceRange(selectedText, sel.start); }
/** * Moves the selected text, or current line if no selection. The cursor/selection * moves with the line/lines. * @param {Editor} editor - target editor * @param {Number} direction - direction of the move (-1,+1) => (Up,Down) */ function moveLine(editor, direction) { editor = editor || EditorManager.getFocusedEditor(); if (!editor) { return; } var doc = editor.document, sel = editor.getSelection(), originalSel = editor.getSelection(), hasSelection = (sel.start.line !== sel.end.line) || (sel.start.ch !== sel.end.ch), isInlineWidget = !!EditorManager.getFocusedInlineWidget(), firstLine = editor.getFirstVisibleLine(), lastLine = editor.getLastVisibleLine(), totalLines = editor.lineCount(), lineLength = 0; sel.start.ch = 0; // The end of the selection becomes the start of the next line, if it isn't already if (!hasSelection || sel.end.ch !== 0) { sel.end = {line: sel.end.line + 1, ch: 0}; } // Make the move switch (direction) { case DIRECTION_UP: if (sel.start.line !== firstLine) { doc.batchOperation(function () { var prevText = doc.getRange({ line: sel.start.line - 1, ch: 0 }, sel.start); if (sel.end.line === lastLine + 1) { if (isInlineWidget) { prevText = prevText.substring(0, prevText.length - 1); lineLength = doc.getLine(sel.end.line - 1).length; doc.replaceRange("\n", { line: sel.end.line - 1, ch: lineLength }); } else { prevText = "\n" + prevText.substring(0, prevText.length - 1); } } doc.replaceRange("", { line: sel.start.line - 1, ch: 0 }, sel.start); doc.replaceRange(prevText, { line: sel.end.line - 1, ch: 0 }); // Make sure CodeMirror hasn't expanded the selection to include // the line we inserted below. originalSel.start.line--; originalSel.end.line--; }); // Update the selection after the document batch so it's not blown away on resynchronization // if this editor is not the master editor. editor.setSelection(originalSel.start, originalSel.end); } break; case DIRECTION_DOWN: if (sel.end.line <= lastLine) { doc.batchOperation(function () { var nextText = doc.getRange(sel.end, { line: sel.end.line + 1, ch: 0 }), deletionStart = sel.end; if (sel.end.line === lastLine) { if (isInlineWidget) { if (sel.end.line === totalLines - 1) { nextText += "\n"; } lineLength = doc.getLine(sel.end.line - 1).length; doc.replaceRange("\n", { line: sel.end.line, ch: doc.getLine(sel.end.line).length }); } else { nextText += "\n"; deletionStart = { line: sel.end.line - 1, ch: doc.getLine(sel.end.line - 1).length }; } } doc.replaceRange("", deletionStart, { line: sel.end.line + 1, ch: 0 }); if (lineLength) { doc.replaceRange("", { line: sel.end.line - 1, ch: lineLength }, { line: sel.end.line, ch: 0 }); } doc.replaceRange(nextText, { line: sel.start.line, ch: 0 }); }); } break; } }
function _handleSelectAll() { var editor = EditorManager.getFocusedEditor(); if (editor) { editor._selectAllVisible(); } }