All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.dirigible.dev-tools.source_frame.SourcesTextEditor.js Maven / Gradle / Ivy

There is a newer version: 10.6.27
Show newest version
// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as Common from '../common/common.js';
import * as TextEditor from '../text_editor/text_editor.js';
import * as TextUtils from '../text_utils/text_utils.js';
import * as UI from '../ui/ui.js';

/**
 * @unrestricted
 */
export class SourcesTextEditor extends TextEditor.CodeMirrorTextEditor.CodeMirrorTextEditor {
  /**
   * @param {!SourcesTextEditorDelegate} delegate
   * @param {!UI.TextEditor.Options=} codeMirrorOptions
   */
  constructor(delegate, codeMirrorOptions) {
    const defaultCodeMirrorOptions = {
      lineNumbers: true,
      lineWrapping: false,
      bracketMatchingSetting: Common.Settings.Settings.instance().moduleSetting('textEditorBracketMatching'),
      padBottom: Common.Settings.Settings.instance().moduleSetting('allowScrollPastEof').get()
    };
    if (codeMirrorOptions) {
      Object.assign(defaultCodeMirrorOptions, codeMirrorOptions);
    }

    super(defaultCodeMirrorOptions);

    this.codeMirror().addKeyMap({'Enter': 'smartNewlineAndIndent', 'Esc': 'sourcesDismiss'});

    this._delegate = delegate;

    this.codeMirror().on('cursorActivity', this._cursorActivity.bind(this));
    this.codeMirror().on('gutterClick', this._gutterClick.bind(this));
    this.codeMirror().on('scroll', this._scroll.bind(this));
    this.codeMirror().on('focus', this._focus.bind(this));
    this.codeMirror().on('blur', this._blur.bind(this));
    this.codeMirror().on('beforeSelectionChange', this._fireBeforeSelectionChanged.bind(this));
    this.element.addEventListener('contextmenu', this._contextMenu.bind(this), false);

    this._gutterMouseMove = event => {
      this.element.classList.toggle(
          'CodeMirror-gutter-hovered',
          event.clientX < this.codeMirror().getGutterElement().getBoundingClientRect().right);
    };
    this._gutterMouseOut = event => {
      this.element.classList.toggle('CodeMirror-gutter-hovered', false);
    };

    this.codeMirror().addKeyMap(_BlockIndentController);
    this._tokenHighlighter = new TokenHighlighter(this, this.codeMirror());

    /** @type {!Array} */
    this._gutters = [lineNumbersGutterType];
    this.codeMirror().setOption('gutters', this._gutters.slice());

    this.codeMirror().setOption('electricChars', false);
    this.codeMirror().setOption('smartIndent', false);

    /**
     * @this {SourcesTextEditor}
     */
    function updateAnticipateJumpFlag(value) {
      this._isHandlingMouseDownEvent = value;
    }

    this.element.addEventListener('mousedown', updateAnticipateJumpFlag.bind(this, true), true);
    this.element.addEventListener('mousedown', updateAnticipateJumpFlag.bind(this, false), false);
    Common.Settings.Settings.instance()
        .moduleSetting('textEditorIndent')
        .addChangeListener(this._onUpdateEditorIndentation, this);
    Common.Settings.Settings.instance()
        .moduleSetting('textEditorAutoDetectIndent')
        .addChangeListener(this._onUpdateEditorIndentation, this);
    Common.Settings.Settings.instance()
        .moduleSetting('showWhitespacesInEditor')
        .addChangeListener(this._updateWhitespace, this);
    Common.Settings.Settings.instance()
        .moduleSetting('textEditorCodeFolding')
        .addChangeListener(this._updateCodeFolding, this);
    Common.Settings.Settings.instance()
        .moduleSetting('allowScrollPastEof')
        .addChangeListener(this._updateScrollPastEof, this);
    this._updateCodeFolding();

    /** @type {?UI.TextEditor.AutocompleteConfig} */
    this._autocompleteConfig = {isWordChar: TextUtils.TextUtils.Utils.isWordChar};
    Common.Settings.Settings.instance()
        .moduleSetting('textEditorAutocompletion')
        .addChangeListener(this._updateAutocomplete, this);
    this._updateAutocomplete();

    this._onUpdateEditorIndentation();
    this._setupWhitespaceHighlight();

    /** @type {?Element} */
    this._infoBarDiv = null;
  }

  /**
   * @param {!UI.Infobar.Infobar} infobar
   */
  attachInfobar(infobar) {
    if (!this._infoBarDiv) {
      this._infoBarDiv = createElementWithClass('div', 'flex-none');
      this.element.insertBefore(this._infoBarDiv, this.element.firstChild);
    }
    this._infoBarDiv.appendChild(infobar.element);
    infobar.setParentView(this);
    this.doResize();
  }

  /**
   * @param {!Array.} lines
   * @return {string}
   */
  static _guessIndentationLevel(lines) {
    const tabRegex = /^\t+/;
    let tabLines = 0;
    const indents = {};
    for (let lineNumber = 0; lineNumber < lines.length; ++lineNumber) {
      const text = lines[lineNumber];
      if (text.length === 0 || !TextUtils.TextUtils.Utils.isSpaceChar(text[0])) {
        continue;
      }
      if (tabRegex.test(text)) {
        ++tabLines;
        continue;
      }
      let i = 0;
      while (i < text.length && TextUtils.TextUtils.Utils.isSpaceChar(text[i])) {
        ++i;
      }
      if (i % 2 !== 0) {
        continue;
      }
      indents[i] = 1 + (indents[i] || 0);
    }
    const linesCountPerIndentThreshold = 3 * lines.length / 100;
    if (tabLines && tabLines > linesCountPerIndentThreshold) {
      return '\t';
    }
    let minimumIndent = Infinity;
    for (const i in indents) {
      if (indents[i] < linesCountPerIndentThreshold) {
        continue;
      }
      const indent = parseInt(i, 10);
      if (minimumIndent > indent) {
        minimumIndent = indent;
      }
    }
    if (minimumIndent === Infinity) {
      return Common.Settings.Settings.instance().moduleSetting('textEditorIndent').get();
    }
    return ' '.repeat(minimumIndent);
  }

  /**
   * @return {boolean}
   */
  _isSearchActive() {
    return !!this._tokenHighlighter.highlightedRegex();
  }

  /**
   * @override
   * @param {number} lineNumber
   */
  scrollToLine(lineNumber) {
    super.scrollToLine(lineNumber);
    this._scroll();
  }

  /**
   * @param {!RegExp} regex
   * @param {?TextUtils.TextRange.TextRange} range
   */
  highlightSearchResults(regex, range) {
    /**
     * @this {TextEditor.CodeMirrorTextEditor.CodeMirrorTextEditor}
     */
    function innerHighlightRegex() {
      if (range) {
        this.scrollLineIntoView(range.startLine);
        if (range.endColumn > TextEditor.CodeMirrorTextEditor.CodeMirrorTextEditor.maxHighlightLength) {
          this.setSelection(range);
        } else {
          this.setSelection(TextUtils.TextRange.TextRange.createFromLocation(range.startLine, range.startColumn));
        }
      }
      this._tokenHighlighter.highlightSearchResults(regex, range);
    }

    if (!this._selectionBeforeSearch) {
      this._selectionBeforeSearch = this.selection();
    }

    this.codeMirror().operation(innerHighlightRegex.bind(this));
  }

  cancelSearchResultsHighlight() {
    this.codeMirror().operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));

    if (this._selectionBeforeSearch) {
      this._reportJump(this._selectionBeforeSearch, this.selection());
      delete this._selectionBeforeSearch;
    }
  }

  /**
   * @param {!Object} highlightDescriptor
   */
  removeHighlight(highlightDescriptor) {
    highlightDescriptor.clear();
  }

  /**
   * @param {!TextUtils.TextRange.TextRange} range
   * @param {string} cssClass
   * @return {!Object}
   */
  highlightRange(range, cssClass) {
    cssClass = 'CodeMirror-persist-highlight ' + cssClass;
    const pos = TextEditor.CodeMirrorUtils.toPos(range);
    ++pos.end.ch;
    return this.codeMirror().markText(
        pos.start, pos.end, {className: cssClass, startStyle: cssClass + '-start', endStyle: cssClass + '-end'});
  }

  /**
   * @param {string} type
   * @param {boolean} leftToNumbers
   */
  installGutter(type, leftToNumbers) {
    if (this._gutters.indexOf(type) !== -1) {
      return;
    }

    if (leftToNumbers) {
      this._gutters.unshift(type);
    } else {
      this._gutters.push(type);
    }

    this.codeMirror().setOption('gutters', this._gutters.slice());
    this.refresh();
  }

  /**
   * @param {string} type
   */
  uninstallGutter(type) {
    const index = this._gutters.indexOf(type);
    if (index === -1) {
      return;
    }
    this.codeMirror().clearGutter(type);
    this._gutters.splice(index, 1);
    this.codeMirror().setOption('gutters', this._gutters.slice());
    this.refresh();
  }

  /**
   * @param {number} lineNumber
   * @param {string} type
   * @param {?Element} element
   */
  setGutterDecoration(lineNumber, type, element) {
    console.assert(this._gutters.indexOf(type) !== -1, 'Cannot decorate unexisting gutter.');
    this.codeMirror().setGutterMarker(lineNumber, type, element);
  }

  /**
   * @param {number} lineNumber
   * @param {number} columnNumber
   */
  setExecutionLocation(lineNumber, columnNumber) {
    this.clearPositionHighlight();

    this._executionLine = this.codeMirror().getLineHandle(lineNumber);
    if (!this._executionLine) {
      return;
    }

    this.showExecutionLineBackground();
    this.codeMirror().addLineClass(this._executionLine, 'wrap', 'cm-execution-line-outline');
    let token = this.tokenAtTextPosition(lineNumber, columnNumber);

    if (token && !token.type && token.startColumn + 1 === token.endColumn) {
      const tokenContent = this.codeMirror().getLine(lineNumber)[token.startColumn];
      if (tokenContent === '.' || tokenContent === '(') {
        token = this.tokenAtTextPosition(lineNumber, token.endColumn + 1);
      }
    }

    let endColumn;
    if (token && token.type) {
      endColumn = token.endColumn;
    } else {
      endColumn = this.codeMirror().getLine(lineNumber).length;
    }

    this._executionLineTailMarker = this.codeMirror().markText(
        {line: lineNumber, ch: columnNumber}, {line: lineNumber, ch: endColumn}, {className: 'cm-execution-line-tail'});
  }

  showExecutionLineBackground() {
    if (this._executionLine) {
      this.codeMirror().addLineClass(this._executionLine, 'wrap', 'cm-execution-line');
    }
  }

  hideExecutionLineBackground() {
    if (this._executionLine) {
      this.codeMirror().removeLineClass(this._executionLine, 'wrap', 'cm-execution-line');
    }
  }

  clearExecutionLine() {
    this.clearPositionHighlight();

    if (this._executionLine) {
      this.hideExecutionLineBackground();
      this.codeMirror().removeLineClass(this._executionLine, 'wrap', 'cm-execution-line-outline');
    }
    delete this._executionLine;

    if (this._executionLineTailMarker) {
      this._executionLineTailMarker.clear();
    }
    delete this._executionLineTailMarker;
  }

  /**
   * @param {number} lineNumber
   * @param {string} className
   * @param {boolean} toggled
   */
  toggleLineClass(lineNumber, className, toggled) {
    if (this.hasLineClass(lineNumber, className) === toggled) {
      return;
    }

    const lineHandle = this.codeMirror().getLineHandle(lineNumber);
    if (!lineHandle) {
      return;
    }

    if (toggled) {
      this.codeMirror().addLineClass(lineHandle, 'gutter', className);
      this.codeMirror().addLineClass(lineHandle, 'wrap', className);
    } else {
      this.codeMirror().removeLineClass(lineHandle, 'gutter', className);
      this.codeMirror().removeLineClass(lineHandle, 'wrap', className);
    }
  }

  /**
   * @param {number} lineNumber
   * @param {string} className
   * @return {boolean}
   */
  hasLineClass(lineNumber, className) {
    const lineInfo = this.codeMirror().lineInfo(lineNumber);
    const wrapClass = lineInfo.wrapClass || '';
    const classNames = wrapClass.split(' ');
    return classNames.indexOf(className) !== -1;
  }

  _gutterClick(instance, lineNumber, gutterType, event) {
    this.dispatchEventToListeners(Events.GutterClick, {gutterType, lineNumber, event});
  }

  _contextMenu(event) {
    const contextMenu = new UI.ContextMenu.ContextMenu(event);
    event.consume(true);  // Consume event now to prevent document from handling the async menu
    const wrapper = event.target.enclosingNodeOrSelfWithClass('CodeMirror-gutter-wrapper');
    const target = wrapper ? wrapper.querySelector('.CodeMirror-linenumber') : null;
    let promise;
    if (target) {
      promise = this._delegate.populateLineGutterContextMenu(contextMenu, parseInt(target.textContent, 10) - 1);
    } else {
      const textSelection = this.selection();
      promise =
          this._delegate.populateTextAreaContextMenu(contextMenu, textSelection.startLine, textSelection.startColumn);
    }
    promise.then(showAsync.bind(this));

    /**
     * @this {SourcesTextEditor}
     */
    function showAsync() {
      contextMenu.appendApplicableItems(this);
      contextMenu.show();
    }
  }

  /**
   * @override
   * @param {!TextUtils.TextRange.TextRange} range
   * @param {string} text
   * @param {string=} origin
   * @return {!TextUtils.TextRange.TextRange}
   */
  editRange(range, text, origin) {
    const newRange = super.editRange(range, text, origin);
    if (Common.Settings.Settings.instance().moduleSetting('textEditorAutoDetectIndent').get()) {
      this._onUpdateEditorIndentation();
    }

    return newRange;
  }

  _onUpdateEditorIndentation() {
    this._setEditorIndentation(
        TextEditor.CodeMirrorUtils.pullLines(this.codeMirror(), LinesToScanForIndentationGuessing));
  }

  /**
   * @param {!Array.} lines
   */
  _setEditorIndentation(lines) {
    const extraKeys = {};
    let indent = Common.Settings.Settings.instance().moduleSetting('textEditorIndent').get();
    if (Common.Settings.Settings.instance().moduleSetting('textEditorAutoDetectIndent').get()) {
      indent = SourcesTextEditor._guessIndentationLevel(lines);
    }

    if (indent === TextUtils.TextUtils.Utils.Indent.TabCharacter) {
      this.codeMirror().setOption('indentWithTabs', true);
      this.codeMirror().setOption('indentUnit', 4);
    } else {
      this.codeMirror().setOption('indentWithTabs', false);
      this.codeMirror().setOption('indentUnit', indent.length);
      extraKeys.Tab = function(codeMirror) {
        if (codeMirror.somethingSelected()) {
          return CodeMirror.Pass;
        }
        const pos = codeMirror.getCursor('head');
        codeMirror.replaceRange(indent.substring(pos.ch % indent.length), codeMirror.getCursor());
      };
    }

    this.codeMirror().setOption('extraKeys', extraKeys);
    this._indentationLevel = indent;
  }

  /**
   * @return {string}
   */
  indent() {
    return this._indentationLevel;
  }

  _onAutoAppendedSpaces() {
    this._autoAppendedSpaces = this._autoAppendedSpaces || [];

    for (let i = 0; i < this._autoAppendedSpaces.length; ++i) {
      const position = this._autoAppendedSpaces[i].resolve();
      if (!position) {
        continue;
      }
      const line = this.line(position.lineNumber);
      if (line.length === position.columnNumber && TextUtils.TextUtils.Utils.lineIndent(line).length === line.length) {
        this.codeMirror().replaceRange(
            '', new CodeMirror.Pos(position.lineNumber, 0),
            new CodeMirror.Pos(position.lineNumber, position.columnNumber));
      }
    }

    this._autoAppendedSpaces = [];
    const selections = this.selections();
    for (let i = 0; i < selections.length; ++i) {
      const selection = selections[i];
      this._autoAppendedSpaces.push(this.textEditorPositionHandle(selection.startLine, selection.startColumn));
    }
  }

  _cursorActivity() {
    if (!this._isSearchActive()) {
      this.codeMirror().operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter));
    }

    const start = this.codeMirror().getCursor('anchor');
    const end = this.codeMirror().getCursor('head');
    this.dispatchEventToListeners(Events.SelectionChanged, TextEditor.CodeMirrorUtils.toRange(start, end));
  }

  /**
   * @param {?TextUtils.TextRange.TextRange} from
   * @param {?TextUtils.TextRange.TextRange} to
   */
  _reportJump(from, to) {
    if (from && to && from.equal(to)) {
      return;
    }
    this.dispatchEventToListeners(Events.JumpHappened, {from: from, to: to});
  }

  _scroll() {
    const topmostLineNumber = this.codeMirror().lineAtHeight(this.codeMirror().getScrollInfo().top, 'local');
    this.dispatchEventToListeners(Events.ScrollChanged, topmostLineNumber);
  }

  _focus() {
    this.dispatchEventToListeners(Events.EditorFocused);
  }

  _blur() {
    this.dispatchEventToListeners(Events.EditorBlurred);
  }

  /**
   * @param {!CodeMirror} codeMirror
   * @param {{ranges: !Array.<{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>}} selection
   */
  _fireBeforeSelectionChanged(codeMirror, selection) {
    if (!this._isHandlingMouseDownEvent) {
      return;
    }
    if (!selection.ranges.length) {
      return;
    }

    const primarySelection = selection.ranges[0];
    this._reportJump(
        this.selection(), TextEditor.CodeMirrorUtils.toRange(primarySelection.anchor, primarySelection.head));
  }

  /**
   * @override
   */
  dispose() {
    super.dispose();
    Common.Settings.Settings.instance()
        .moduleSetting('textEditorIndent')
        .removeChangeListener(this._onUpdateEditorIndentation, this);
    Common.Settings.Settings.instance()
        .moduleSetting('textEditorAutoDetectIndent')
        .removeChangeListener(this._onUpdateEditorIndentation, this);
    Common.Settings.Settings.instance()
        .moduleSetting('showWhitespacesInEditor')
        .removeChangeListener(this._updateWhitespace, this);
    Common.Settings.Settings.instance()
        .moduleSetting('textEditorCodeFolding')
        .removeChangeListener(this._updateCodeFolding, this);
    Common.Settings.Settings.instance()
        .moduleSetting('allowScrollPastEof')
        .removeChangeListener(this._updateScrollPastEof, this);
  }

  /**
   * @override
   * @param {string} text
   */
  setText(text) {
    this._setEditorIndentation(text.split('\n').slice(0, LinesToScanForIndentationGuessing));
    super.setText(text);
  }

  _updateWhitespace() {
    this.setMimeType(this.mimeType());
  }

  _updateCodeFolding() {
    if (Common.Settings.Settings.instance().moduleSetting('textEditorCodeFolding').get()) {
      this.installGutter('CodeMirror-foldgutter', false);
      this.element.addEventListener('mousemove', this._gutterMouseMove);
      this.element.addEventListener('mouseout', this._gutterMouseOut);
      this.codeMirror().setOption('foldGutter', true);
      this.codeMirror().setOption('foldOptions', {minFoldSize: 1});
    } else {
      this.codeMirror().execCommand('unfoldAll');
      this.element.removeEventListener('mousemove', this._gutterMouseMove);
      this.element.removeEventListener('mouseout', this._gutterMouseOut);
      this.uninstallGutter('CodeMirror-foldgutter');
      this.codeMirror().setOption('foldGutter', false);
    }
  }

  _updateScrollPastEof() {
    this.toggleScrollPastEof(Common.Settings.Settings.instance().moduleSetting('allowScrollPastEof').get());
  }

  /**
   * @override
   * @param {string} mimeType
   * @return {string}
   */
  rewriteMimeType(mimeType) {
    this._setupWhitespaceHighlight();
    const whitespaceMode = Common.Settings.Settings.instance().moduleSetting('showWhitespacesInEditor').get();
    this.element.classList.toggle('show-whitespaces', whitespaceMode === 'all');

    if (whitespaceMode === 'all') {
      return this._allWhitespaceOverlayMode(mimeType);
    }
    if (whitespaceMode === 'trailing') {
      return this._trailingWhitespaceOverlayMode(mimeType);
    }

    return mimeType;
  }

  /**
   * @param {string} mimeType
   * @return {string}
   */
  _allWhitespaceOverlayMode(mimeType) {
    let modeName = CodeMirror.mimeModes[mimeType] ?
        (CodeMirror.mimeModes[mimeType].name || CodeMirror.mimeModes[mimeType]) :
        CodeMirror.mimeModes['text/plain'];
    modeName += '+all-whitespaces';
    if (CodeMirror.modes[modeName]) {
      return modeName;
    }

    function modeConstructor(config, parserConfig) {
      function nextToken(stream) {
        if (stream.peek() === ' ') {
          let spaces = 0;
          while (spaces < MaximumNumberOfWhitespacesPerSingleSpan && stream.peek() === ' ') {
            ++spaces;
            stream.next();
          }
          return 'whitespace whitespace-' + spaces;
        }
        while (!stream.eol() && stream.peek() !== ' ') {
          stream.next();
        }
        return null;
      }
      const whitespaceMode = {token: nextToken};
      return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false);
    }
    CodeMirror.defineMode(modeName, modeConstructor);
    return modeName;
  }

  /**
   * @param {string} mimeType
   * @return {string}
   */
  _trailingWhitespaceOverlayMode(mimeType) {
    let modeName = CodeMirror.mimeModes[mimeType] ?
        (CodeMirror.mimeModes[mimeType].name || CodeMirror.mimeModes[mimeType]) :
        CodeMirror.mimeModes['text/plain'];
    modeName += '+trailing-whitespaces';
    if (CodeMirror.modes[modeName]) {
      return modeName;
    }

    function modeConstructor(config, parserConfig) {
      function nextToken(stream) {
        if (stream.match(/^\s+$/, true)) {
          return true ? 'trailing-whitespace' : null;
        }
        do {
          stream.next();
        } while (!stream.eol() && stream.peek() !== ' ');
        return null;
      }
      const whitespaceMode = {token: nextToken};
      return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false);
    }
    CodeMirror.defineMode(modeName, modeConstructor);
    return modeName;
  }

  _setupWhitespaceHighlight() {
    const doc = this.element.ownerDocument;
    if (doc._codeMirrorWhitespaceStyleInjected ||
        !Common.Settings.Settings.instance().moduleSetting('showWhitespacesInEditor').get()) {
      return;
    }
    doc._codeMirrorWhitespaceStyleInjected = true;
    const classBase = '.show-whitespaces .CodeMirror .cm-whitespace-';
    const spaceChar = '·';
    let spaceChars = '';
    let rules = '';
    for (let i = 1; i <= MaximumNumberOfWhitespacesPerSingleSpan; ++i) {
      spaceChars += spaceChar;
      const rule = classBase + i + '::before { content: \'' + spaceChars + '\';}\n';
      rules += rule;
    }
    const style = doc.createElement('style');
    style.textContent = rules;
    doc.head.appendChild(style);
  }

  /**
   * @override
   * @param {?UI.TextEditor.AutocompleteConfig} config
   */
  configureAutocomplete(config) {
    this._autocompleteConfig = config;
    this._updateAutocomplete();
  }

  _updateAutocomplete() {
    super.configureAutocomplete(
        Common.Settings.Settings.instance().moduleSetting('textEditorAutocompletion').get() ? this._autocompleteConfig :
                                                                                              null);
  }
}

/** @enum {symbol} */
export const Events = {
  GutterClick: Symbol('GutterClick'),
  SelectionChanged: Symbol('SelectionChanged'),
  ScrollChanged: Symbol('ScrollChanged'),
  EditorFocused: Symbol('EditorFocused'),
  EditorBlurred: Symbol('EditorBlurred'),
  JumpHappened: Symbol('JumpHappened')
};

/**
 * @interface
 */
export class SourcesTextEditorDelegate {
  /**
   * @param {!UI.ContextMenu.ContextMenu} contextMenu
   * @param {number} lineNumber
   * @return {!Promise}
   */
  populateLineGutterContextMenu(contextMenu, lineNumber) {
  }

  /**
   * @param {!UI.ContextMenu.ContextMenu} contextMenu
   * @param {number} lineNumber
   * @param {number} columnNumber
   * @return {!Promise}
   */
  populateTextAreaContextMenu(contextMenu, lineNumber, columnNumber) {
  }
}

/**
 * @param {!CodeMirror} codeMirror
 */
CodeMirror.commands.smartNewlineAndIndent = function(codeMirror) {
  codeMirror.operation(innerSmartNewlineAndIndent.bind(null, codeMirror));
  function innerSmartNewlineAndIndent(codeMirror) {
    const selections = codeMirror.listSelections();
    const replacements = [];
    for (let i = 0; i < selections.length; ++i) {
      const selection = selections[i];
      const cur = CodeMirror.cmpPos(selection.head, selection.anchor) < 0 ? selection.head : selection.anchor;
      const line = codeMirror.getLine(cur.line);
      const indent = TextUtils.TextUtils.Utils.lineIndent(line);
      replacements.push('\n' + indent.substring(0, Math.min(cur.ch, indent.length)));
    }
    codeMirror.replaceSelections(replacements);
    codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
  }
};

/**
 * @return {!Object|undefined}
 */
CodeMirror.commands.sourcesDismiss = function(codemirror) {
  if (codemirror.listSelections().length === 1 && codemirror._codeMirrorTextEditor._isSearchActive()) {
    return CodeMirror.Pass;
  }
  return CodeMirror.commands.dismiss(codemirror);
};

export const _BlockIndentController = {
  name: 'blockIndentKeymap',

  /**
   * @return {*}
   */
  Enter: function(codeMirror) {
    let selections = codeMirror.listSelections();
    const replacements = [];
    let allSelectionsAreCollapsedBlocks = false;
    for (let i = 0; i < selections.length; ++i) {
      const selection = selections[i];
      const start = CodeMirror.cmpPos(selection.head, selection.anchor) < 0 ? selection.head : selection.anchor;
      const line = codeMirror.getLine(start.line);
      const indent = TextUtils.TextUtils.Utils.lineIndent(line);
      let indentToInsert = '\n' + indent + codeMirror._codeMirrorTextEditor.indent();
      let isCollapsedBlock = false;
      if (selection.head.ch === 0) {
        return CodeMirror.Pass;
      }
      if (line.substr(selection.head.ch - 1, 2) === '{}') {
        indentToInsert += '\n' + indent;
        isCollapsedBlock = true;
      } else if (line.substr(selection.head.ch - 1, 1) !== '{') {
        return CodeMirror.Pass;
      }
      if (i > 0 && allSelectionsAreCollapsedBlocks !== isCollapsedBlock) {
        return CodeMirror.Pass;
      }
      replacements.push(indentToInsert);
      allSelectionsAreCollapsedBlocks = isCollapsedBlock;
    }
    codeMirror.replaceSelections(replacements);
    if (!allSelectionsAreCollapsedBlocks) {
      codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
      return;
    }
    selections = codeMirror.listSelections();
    const updatedSelections = [];
    for (let i = 0; i < selections.length; ++i) {
      const selection = selections[i];
      const line = codeMirror.getLine(selection.head.line - 1);
      const position = new CodeMirror.Pos(selection.head.line - 1, line.length);
      updatedSelections.push({head: position, anchor: position});
    }
    codeMirror.setSelections(updatedSelections);
    codeMirror._codeMirrorTextEditor._onAutoAppendedSpaces();
  },

  /**
   * @return {*}
   */
  '\'}\'': function(codeMirror) {
    if (codeMirror.somethingSelected()) {
      return CodeMirror.Pass;
    }
    let selections = codeMirror.listSelections();
    let replacements = [];
    for (let i = 0; i < selections.length; ++i) {
      const selection = selections[i];
      const line = codeMirror.getLine(selection.head.line);
      if (line !== TextUtils.TextUtils.Utils.lineIndent(line)) {
        return CodeMirror.Pass;
      }
      replacements.push('}');
    }
    codeMirror.replaceSelections(replacements);
    selections = codeMirror.listSelections();
    replacements = [];
    const updatedSelections = [];
    for (let i = 0; i < selections.length; ++i) {
      const selection = selections[i];
      const matchingBracket = codeMirror.findMatchingBracket(selection.head);
      if (!matchingBracket || !matchingBracket.match) {
        return;
      }
      updatedSelections.push({head: selection.head, anchor: new CodeMirror.Pos(selection.head.line, 0)});
      const line = codeMirror.getLine(matchingBracket.to.line);
      const indent = TextUtils.TextUtils.Utils.lineIndent(line);
      replacements.push(indent + '}');
    }
    codeMirror.setSelections(updatedSelections);
    codeMirror.replaceSelections(replacements);
  }
};


/**
 * @unrestricted
 */
export class TokenHighlighter {
  /**
   * @param {!SourcesTextEditor} textEditor
   * @param {!CodeMirror} codeMirror
   */
  constructor(textEditor, codeMirror) {
    this._textEditor = textEditor;
    this._codeMirror = codeMirror;
  }

  /**
   * @param {!RegExp} regex
   * @param {?TextUtils.TextRange.TextRange} range
   */
  highlightSearchResults(regex, range) {
    const oldRegex = this._highlightRegex;
    this._highlightRegex = regex;
    this._highlightRange = range;
    if (this._searchResultMarker) {
      this._searchResultMarker.clear();
      delete this._searchResultMarker;
    }
    if (this._highlightDescriptor && this._highlightDescriptor.selectionStart) {
      this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, 'wrap', 'cm-line-with-selection');
    }
    const selectionStart = this._highlightRange ?
        new CodeMirror.Pos(this._highlightRange.startLine, this._highlightRange.startColumn) :
        null;
    if (selectionStart) {
      this._codeMirror.addLineClass(selectionStart.line, 'wrap', 'cm-line-with-selection');
    }
    if (oldRegex && this._highlightRegex.toString() === oldRegex.toString()) {
      // Do not re-add overlay mode if regex did not change for better performance.
      if (this._highlightDescriptor) {
        this._highlightDescriptor.selectionStart = selectionStart;
      }
    } else {
      this._removeHighlight();
      this._setHighlighter(this._searchHighlighter.bind(this, this._highlightRegex), selectionStart);
    }
    if (this._highlightRange) {
      const pos = TextEditor.CodeMirrorUtils.toPos(this._highlightRange);
      this._searchResultMarker = this._codeMirror.markText(pos.start, pos.end, {className: 'cm-column-with-selection'});
    }
  }

  /**
   * @return {!RegExp|undefined}
   */
  highlightedRegex() {
    return this._highlightRegex;
  }

  highlightSelectedTokens() {
    delete this._highlightRegex;
    delete this._highlightRange;
    if (this._highlightDescriptor && this._highlightDescriptor.selectionStart) {
      this._codeMirror.removeLineClass(this._highlightDescriptor.selectionStart.line, 'wrap', 'cm-line-with-selection');
    }
    this._removeHighlight();
    const selectionStart = this._codeMirror.getCursor('start');
    const selectionEnd = this._codeMirror.getCursor('end');
    if (selectionStart.line !== selectionEnd.line) {
      return;
    }
    if (selectionStart.ch === selectionEnd.ch) {
      return;
    }
    const selections = this._codeMirror.getSelections();
    if (selections.length > 1) {
      return;
    }
    const selectedText = selections[0];
    if (this._isWord(selectedText, selectionStart.line, selectionStart.ch, selectionEnd.ch)) {
      if (selectionStart) {
        this._codeMirror.addLineClass(selectionStart.line, 'wrap', 'cm-line-with-selection');
      }
      this._setHighlighter(this._tokenHighlighter.bind(this, selectedText, selectionStart), selectionStart);
    }
  }

  /**
   * @param {string} selectedText
   * @param {number} lineNumber
   * @param {number} startColumn
   * @param {number} endColumn
   */
  _isWord(selectedText, lineNumber, startColumn, endColumn) {
    const line = this._codeMirror.getLine(lineNumber);
    const leftBound = startColumn === 0 || !TextUtils.TextUtils.Utils.isWordChar(line.charAt(startColumn - 1));
    const rightBound = endColumn === line.length || !TextUtils.TextUtils.Utils.isWordChar(line.charAt(endColumn));
    return leftBound && rightBound && TextUtils.TextUtils.Utils.isWord(selectedText);
  }

  _removeHighlight() {
    if (this._highlightDescriptor) {
      this._codeMirror.removeOverlay(this._highlightDescriptor.overlay);
      delete this._highlightDescriptor;
    }
  }

  /**
   * @param {!RegExp} regex
   * @param {!CodeMirror.StringStream} stream
   */
  _searchHighlighter(regex, stream) {
    if (stream.column() === 0) {
      delete this._searchMatchLength;
    }
    if (this._searchMatchLength) {
      if (this._searchMatchLength > 2) {
        for (let i = 0; i < this._searchMatchLength - 2; ++i) {
          stream.next();
        }
        this._searchMatchLength = 1;
        return 'search-highlight';
      }
      stream.next();
      delete this._searchMatchLength;
      return 'search-highlight search-highlight-end';
    }
    const match = stream.match(regex, false);
    if (match) {
      stream.next();
      const matchLength = match[0].length;
      if (matchLength === 1) {
        return 'search-highlight search-highlight-full';
      }
      this._searchMatchLength = matchLength;
      return 'search-highlight search-highlight-start';
    }
    while (!stream.match(regex, false) && stream.next()) {
    }
  }

  /**
   * @param {string} token
   * @param {!CodeMirror.Pos} selectionStart
   * @param {!CodeMirror.StringStream} stream
   */
  _tokenHighlighter(token, selectionStart, stream) {
    const tokenFirstChar = token.charAt(0);
    if (stream.match(token) && (stream.eol() || !TextUtils.TextUtils.Utils.isWordChar(stream.peek()))) {
      return stream.column() === selectionStart.ch ? 'token-highlight column-with-selection' : 'token-highlight';
    }
    let eatenChar;
    do {
      eatenChar = stream.next();
    } while (eatenChar && (TextUtils.TextUtils.Utils.isWordChar(eatenChar) || stream.peek() !== tokenFirstChar));
  }

  /**
   * @param {function(!CodeMirror.StringStream)} highlighter
   * @param {?CodeMirror.Pos} selectionStart
   */
  _setHighlighter(highlighter, selectionStart) {
    const overlayMode = {token: highlighter};
    this._codeMirror.addOverlay(overlayMode);
    this._highlightDescriptor = {overlay: overlayMode, selectionStart: selectionStart};
  }
}

const LinesToScanForIndentationGuessing = 1000;
const MaximumNumberOfWhitespacesPerSingleSpan = 16;
export const lineNumbersGutterType = 'CodeMirror-linenumbers';

/** @typedef {{gutterType: string, lineNumber: number, event: !Event}} */
export let GutterClickEventData;




© 2015 - 2024 Weber Informatics LLC | Privacy Policy