"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const arrays = require("../../../../base/common/arrays.js");
const async = require("../../../../base/common/async.js");
const cancellation = require("../../../../base/common/cancellation.js");
const color = require("../../../../base/common/color.js");
const errors = require("../../../../base/common/errors.js");
const event = require("../../../../base/common/event.js");
const lifecycle = require("../../../../base/common/lifecycle.js");
const strings = require("../../../../base/common/strings.js");
const uri = require("../../../../base/common/uri.js");
const editorExtensions = require("../../../browser/editorExtensions.js");
const codeEditorService = require("../../../browser/services/codeEditorService.js");
const position = require("../../../common/core/position.js");
const range = require("../../../common/core/range.js");
const editorContextKeys = require("../../../common/editorContextKeys.js");
const textModel = require("../../../common/model/textModel.js");
const languageConfigurationRegistry = require("../../../common/languages/languageConfigurationRegistry.js");
const nls = require("../../../../nls.js");
const contextkey = require("../../../../platform/contextkey/common/contextkey.js");
const colorRegistry = require("../../../../platform/theme/common/colorRegistry.js");
const themeService = require("../../../../platform/theme/common/themeService.js");
const languageFeatures = require("../../../common/services/languageFeatures.js");
const languageFeatureDebounce = require("../../../common/services/languageFeatureDebounce.js");
const stopwatch = require("../../../../base/common/stopwatch.js");
var __decorate = globalThis && globalThis.__decorate || function(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
    r = Reflect.decorate(decorators, target, key, desc);
  else
    for (var i = decorators.length - 1; i >= 0; i--)
      if (d = decorators[i])
        r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = globalThis && globalThis.__param || function(paramIndex, decorator) {
  return function(target, key) {
    decorator(target, key, paramIndex);
  };
};
var __awaiter = globalThis && globalThis.__awaiter || function(thisArg, _arguments, P, generator) {
  function adopt(value) {
    return value instanceof P ? value : new P(function(resolve) {
      resolve(value);
    });
  }
  return new (P || (P = Promise))(function(resolve, reject) {
    function fulfilled(value) {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    }
    function rejected(value) {
      try {
        step(generator["throw"](value));
      } catch (e) {
        reject(e);
      }
    }
    function step(result) {
      result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
    }
    step((generator = generator.apply(thisArg, _arguments || [])).next());
  });
};
const CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE = new contextkey.RawContextKey("LinkedEditingInputVisible", false);
const DECORATION_CLASS_NAME = "linked-editing-decoration";
exports.LinkedEditingContribution = class LinkedEditingContribution extends lifecycle.Disposable {
  constructor(editor, contextKeyService, languageFeaturesService, languageConfigurationService, languageFeatureDebounceService) {
    super();
    this.languageConfigurationService = languageConfigurationService;
    this._localToDispose = this._register(new lifecycle.DisposableStore());
    this._editor = editor;
    this._providers = languageFeaturesService.linkedEditingRangeProvider;
    this._enabled = false;
    this._visibleContextKey = CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE.bindTo(contextKeyService);
    this._debounceInformation = languageFeatureDebounceService.for(this._providers, "Linked Editing", { min: 200 });
    this._currentDecorations = [];
    this._languageWordPattern = null;
    this._currentWordPattern = null;
    this._ignoreChangeEvent = false;
    this._localToDispose = this._register(new lifecycle.DisposableStore());
    this._rangeUpdateTriggerPromise = null;
    this._rangeSyncTriggerPromise = null;
    this._currentRequest = null;
    this._currentRequestPosition = null;
    this._currentRequestModelVersion = null;
    this._register(this._editor.onDidChangeModel(() => this.reinitialize(true)));
    this._register(this._editor.onDidChangeConfiguration((e) => {
      if (e.hasChanged(
        62
        /* linkedEditing */
      ) || e.hasChanged(
        82
        /* renameOnType */
      )) {
        this.reinitialize(false);
      }
    }));
    this._register(this._providers.onDidChange(() => this.reinitialize(false)));
    this._register(this._editor.onDidChangeModelLanguage(() => this.reinitialize(true)));
    this.reinitialize(true);
  }
  static get(editor) {
    return editor.getContribution(LinkedEditingContribution.ID);
  }
  reinitialize(forceRefresh) {
    const model = this._editor.getModel();
    const isEnabled = model !== null && (this._editor.getOption(
      62
      /* linkedEditing */
    ) || this._editor.getOption(
      82
      /* renameOnType */
    )) && this._providers.has(model);
    if (isEnabled === this._enabled && !forceRefresh) {
      return;
    }
    this._enabled = isEnabled;
    this.clearRanges();
    this._localToDispose.clear();
    if (!isEnabled || model === null) {
      return;
    }
    this._localToDispose.add(event.Event.runAndSubscribe(model.onDidChangeLanguageConfiguration, () => {
      this._languageWordPattern = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition();
    }));
    const rangeUpdateScheduler = new async.Delayer(this._debounceInformation.get(model));
    const triggerRangeUpdate = () => {
      var _a;
      this._rangeUpdateTriggerPromise = rangeUpdateScheduler.trigger(() => this.updateRanges(), (_a = this._debounceDuration) !== null && _a !== void 0 ? _a : this._debounceInformation.get(model));
    };
    const rangeSyncScheduler = new async.Delayer(0);
    const triggerRangeSync = (decorations) => {
      this._rangeSyncTriggerPromise = rangeSyncScheduler.trigger(() => this._syncRanges(decorations));
    };
    this._localToDispose.add(this._editor.onDidChangeCursorPosition(() => {
      triggerRangeUpdate();
    }));
    this._localToDispose.add(this._editor.onDidChangeModelContent((e) => {
      if (!this._ignoreChangeEvent) {
        if (this._currentDecorations.length > 0) {
          const referenceRange = model.getDecorationRange(this._currentDecorations[0]);
          if (referenceRange && e.changes.every((c) => referenceRange.intersectRanges(c.range))) {
            triggerRangeSync(this._currentDecorations);
            return;
          }
        }
      }
      triggerRangeUpdate();
    }));
    this._localToDispose.add({
      dispose: () => {
        rangeUpdateScheduler.dispose();
        rangeSyncScheduler.dispose();
      }
    });
    this.updateRanges();
  }
  _syncRanges(decorations) {
    if (!this._editor.hasModel() || decorations !== this._currentDecorations || decorations.length === 0) {
      return;
    }
    const model = this._editor.getModel();
    const referenceRange = model.getDecorationRange(decorations[0]);
    if (!referenceRange || referenceRange.startLineNumber !== referenceRange.endLineNumber) {
      return this.clearRanges();
    }
    const referenceValue = model.getValueInRange(referenceRange);
    if (this._currentWordPattern) {
      const match = referenceValue.match(this._currentWordPattern);
      const matchLength = match ? match[0].length : 0;
      if (matchLength !== referenceValue.length) {
        return this.clearRanges();
      }
    }
    let edits = [];
    for (let i = 1, len = decorations.length; i < len; i++) {
      const mirrorRange = model.getDecorationRange(decorations[i]);
      if (!mirrorRange) {
        continue;
      }
      if (mirrorRange.startLineNumber !== mirrorRange.endLineNumber) {
        edits.push({
          range: mirrorRange,
          text: referenceValue
        });
      } else {
        let oldValue = model.getValueInRange(mirrorRange);
        let newValue = referenceValue;
        let rangeStartColumn = mirrorRange.startColumn;
        let rangeEndColumn = mirrorRange.endColumn;
        const commonPrefixLength = strings.commonPrefixLength(oldValue, newValue);
        rangeStartColumn += commonPrefixLength;
        oldValue = oldValue.substr(commonPrefixLength);
        newValue = newValue.substr(commonPrefixLength);
        const commonSuffixLength = strings.commonSuffixLength(oldValue, newValue);
        rangeEndColumn -= commonSuffixLength;
        oldValue = oldValue.substr(0, oldValue.length - commonSuffixLength);
        newValue = newValue.substr(0, newValue.length - commonSuffixLength);
        if (rangeStartColumn !== rangeEndColumn || newValue.length !== 0) {
          edits.push({
            range: new range.Range(mirrorRange.startLineNumber, rangeStartColumn, mirrorRange.endLineNumber, rangeEndColumn),
            text: newValue
          });
        }
      }
    }
    if (edits.length === 0) {
      return;
    }
    try {
      this._editor.popUndoStop();
      this._ignoreChangeEvent = true;
      const prevEditOperationType = this._editor._getViewModel().getPrevEditOperationType();
      this._editor.executeEdits("linkedEditing", edits);
      this._editor._getViewModel().setPrevEditOperationType(prevEditOperationType);
    } finally {
      this._ignoreChangeEvent = false;
    }
  }
  dispose() {
    this.clearRanges();
    super.dispose();
  }
  clearRanges() {
    this._visibleContextKey.set(false);
    this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, []);
    if (this._currentRequest) {
      this._currentRequest.cancel();
      this._currentRequest = null;
      this._currentRequestPosition = null;
    }
  }
  updateRanges(force = false) {
    return __awaiter(this, void 0, void 0, function* () {
      if (!this._editor.hasModel()) {
        this.clearRanges();
        return;
      }
      const position2 = this._editor.getPosition();
      if (!this._enabled && !force || this._editor.getSelections().length > 1) {
        this.clearRanges();
        return;
      }
      const model = this._editor.getModel();
      const modelVersionId = model.getVersionId();
      if (this._currentRequestPosition && this._currentRequestModelVersion === modelVersionId) {
        if (position2.equals(this._currentRequestPosition)) {
          return;
        }
        if (this._currentDecorations && this._currentDecorations.length > 0) {
          const range2 = model.getDecorationRange(this._currentDecorations[0]);
          if (range2 && range2.containsPosition(position2)) {
            return;
          }
        }
      }
      this._currentRequestPosition = position2;
      this._currentRequestModelVersion = modelVersionId;
      const request = async.createCancelablePromise((token) => __awaiter(this, void 0, void 0, function* () {
        try {
          const sw = new stopwatch.StopWatch(false);
          const response = yield getLinkedEditingRanges(this._providers, model, position2, token);
          this._debounceInformation.update(model, sw.elapsed());
          if (request !== this._currentRequest) {
            return;
          }
          this._currentRequest = null;
          if (modelVersionId !== model.getVersionId()) {
            return;
          }
          let ranges = [];
          if (response === null || response === void 0 ? void 0 : response.ranges) {
            ranges = response.ranges;
          }
          this._currentWordPattern = (response === null || response === void 0 ? void 0 : response.wordPattern) || this._languageWordPattern;
          let foundReferenceRange = false;
          for (let i = 0, len = ranges.length; i < len; i++) {
            if (range.Range.containsPosition(ranges[i], position2)) {
              foundReferenceRange = true;
              if (i !== 0) {
                const referenceRange = ranges[i];
                ranges.splice(i, 1);
                ranges.unshift(referenceRange);
              }
              break;
            }
          }
          if (!foundReferenceRange) {
            this.clearRanges();
            return;
          }
          const decorations = ranges.map((range2) => ({ range: range2, options: LinkedEditingContribution.DECORATION }));
          this._visibleContextKey.set(true);
          this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, decorations);
        } catch (err) {
          if (!errors.isCancellationError(err)) {
            errors.onUnexpectedError(err);
          }
          if (this._currentRequest === request || !this._currentRequest) {
            this.clearRanges();
          }
        }
      }));
      this._currentRequest = request;
      return request;
    });
  }
};
exports.LinkedEditingContribution.ID = "editor.contrib.linkedEditing";
exports.LinkedEditingContribution.DECORATION = textModel.ModelDecorationOptions.register({
  description: "linked-editing",
  stickiness: 0,
  className: DECORATION_CLASS_NAME
});
exports.LinkedEditingContribution = __decorate([
  __param(1, contextkey.IContextKeyService),
  __param(2, languageFeatures.ILanguageFeaturesService),
  __param(3, languageConfigurationRegistry.ILanguageConfigurationService),
  __param(4, languageFeatureDebounce.ILanguageFeatureDebounceService)
], exports.LinkedEditingContribution);
class LinkedEditingAction extends editorExtensions.EditorAction {
  constructor() {
    super({
      id: "editor.action.linkedEditing",
      label: nls.localize("linkedEditing.label", "Start Linked Editing"),
      alias: "Start Linked Editing",
      precondition: contextkey.ContextKeyExpr.and(editorContextKeys.EditorContextKeys.writable, editorContextKeys.EditorContextKeys.hasRenameProvider),
      kbOpts: {
        kbExpr: editorContextKeys.EditorContextKeys.editorTextFocus,
        primary: 2048 | 1024 | 60,
        weight: 100
        /* EditorContrib */
      }
    });
  }
  runCommand(accessor, args) {
    const editorService = accessor.get(codeEditorService.ICodeEditorService);
    const [uri$1, pos] = Array.isArray(args) && args || [void 0, void 0];
    if (uri.URI.isUri(uri$1) && position.Position.isIPosition(pos)) {
      return editorService.openCodeEditor({ resource: uri$1 }, editorService.getActiveCodeEditor()).then((editor) => {
        if (!editor) {
          return;
        }
        editor.setPosition(pos);
        editor.invokeWithinContext((accessor2) => {
          this.reportTelemetry(accessor2, editor);
          return this.run(accessor2, editor);
        });
      }, errors.onUnexpectedError);
    }
    return super.runCommand(accessor, args);
  }
  run(_accessor, editor) {
    const controller = exports.LinkedEditingContribution.get(editor);
    if (controller) {
      return Promise.resolve(controller.updateRanges(true));
    }
    return Promise.resolve();
  }
}
const LinkedEditingCommand = editorExtensions.EditorCommand.bindToContribution(exports.LinkedEditingContribution.get);
editorExtensions.registerEditorCommand(new LinkedEditingCommand({
  id: "cancelLinkedEditingInput",
  precondition: CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE,
  handler: (x) => x.clearRanges(),
  kbOpts: {
    kbExpr: editorContextKeys.EditorContextKeys.editorTextFocus,
    weight: 100 + 99,
    primary: 9,
    secondary: [
      1024 | 9
      /* Escape */
    ]
  }
}));
function getLinkedEditingRanges(providers, model, position2, token) {
  const orderedByScore = providers.ordered(model);
  return async.first(orderedByScore.map((provider) => () => __awaiter(this, void 0, void 0, function* () {
    try {
      return yield provider.provideLinkedEditingRanges(model, position2, token);
    } catch (e) {
      errors.onUnexpectedExternalError(e);
      return void 0;
    }
  })), (result) => !!result && arrays.isNonEmptyArray(result === null || result === void 0 ? void 0 : result.ranges));
}
const editorLinkedEditingBackground = colorRegistry.registerColor("editor.linkedEditingBackground", { dark: color.Color.fromHex("#f00").transparent(0.3), light: color.Color.fromHex("#f00").transparent(0.3), hc: color.Color.fromHex("#f00").transparent(0.3) }, nls.localize("editorLinkedEditingBackground", "Background color when the editor auto renames on type."));
themeService.registerThemingParticipant((theme, collector) => {
  const editorLinkedEditingBackgroundColor = theme.getColor(editorLinkedEditingBackground);
  if (editorLinkedEditingBackgroundColor) {
    collector.addRule(`.monaco-editor .${DECORATION_CLASS_NAME} { background: ${editorLinkedEditingBackgroundColor}; border-left-color: ${editorLinkedEditingBackgroundColor}; }`);
  }
});
editorExtensions.registerModelAndPositionCommand("_executeLinkedEditingProvider", (_accessor, model, position2) => {
  const { linkedEditingRangeProvider } = _accessor.get(languageFeatures.ILanguageFeaturesService);
  return getLinkedEditingRanges(linkedEditingRangeProvider, model, position2, cancellation.CancellationToken.None);
});
editorExtensions.registerEditorContribution(exports.LinkedEditingContribution.ID, exports.LinkedEditingContribution);
editorExtensions.registerEditorAction(LinkedEditingAction);
exports.CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE = CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE;
exports.LinkedEditingAction = LinkedEditingAction;
exports.editorLinkedEditingBackground = editorLinkedEditingBackground;
