提交 2bceb255 编写于 作者: A Alex Dima

Fixes #78975: Look for autoclosing pairs in edits coming in from suggestions

上级 8e55695f
......@@ -13,7 +13,7 @@ import { IPosition, Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, ICursorStateComputer } from 'vs/editor/common/model';
import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager';
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
......@@ -612,7 +612,7 @@ export interface ICodeEditor extends editorCommon.IEditor {
* @param edits The edits to execute.
* @param endCursorState Cursor state after the edits were applied.
*/
executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: Selection[]): boolean;
executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean;
/**
* Execute multiple (concomitant) commands on the editor.
......
......@@ -33,7 +33,7 @@ import { ISelection, Selection } from 'vs/editor/common/core/selection';
import { InternalEditorAction } from 'vs/editor/common/editorAction';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { EndOfLinePreference, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
import { EndOfLinePreference, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, ICursorStateComputer } from 'vs/editor/common/model';
import { ClassName } from 'vs/editor/common/model/intervalTree';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents';
......@@ -980,7 +980,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
return true;
}
public executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: Selection[]): boolean {
public executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean {
if (!this._modelData) {
return false;
}
......@@ -989,14 +989,16 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
return false;
}
this._modelData.model.pushEditOperations(this._modelData.cursor.getSelections(), edits, () => {
return endCursorState ? endCursorState : null;
});
if (endCursorState) {
this._modelData.cursor.setSelections(source, endCursorState);
let cursorStateComputer: ICursorStateComputer;
if (!endCursorState) {
cursorStateComputer = () => null;
} else if (Array.isArray(endCursorState)) {
cursorStateComputer = () => endCursorState;
} else {
cursorStateComputer = endCursorState;
}
this._modelData.cursor.executeEdits(source, edits, cursorStateComputer);
return true;
}
......
......@@ -15,7 +15,7 @@ import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness, IModelDeltaDecoration } from 'vs/editor/common/model';
import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer } from 'vs/editor/common/model';
import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
......@@ -429,6 +429,31 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
// ------ auxiliary handling logic
private _pushAutoClosedAction(autoClosedCharactersRanges: Range[], autoClosedEnclosingRanges: Range[]): void {
let autoClosedCharactersDeltaDecorations: IModelDeltaDecoration[] = [];
let autoClosedEnclosingDeltaDecorations: IModelDeltaDecoration[] = [];
for (let i = 0, len = autoClosedCharactersRanges.length; i < len; i++) {
autoClosedCharactersDeltaDecorations.push({
range: autoClosedCharactersRanges[i],
options: {
inlineClassName: 'auto-closed-character',
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
}
});
autoClosedEnclosingDeltaDecorations.push({
range: autoClosedEnclosingRanges[i],
options: {
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
}
});
}
const autoClosedCharactersDecorations = this._model.deltaDecorations([], autoClosedCharactersDeltaDecorations);
const autoClosedEnclosingDecorations = this._model.deltaDecorations([], autoClosedEnclosingDeltaDecorations);
this._autoClosedActions.push(new AutoClosedAction(this._model, autoClosedCharactersDecorations, autoClosedEnclosingDecorations));
}
private _executeEditOperation(opResult: EditOperationResult | null): void {
if (!opResult) {
......@@ -446,32 +471,19 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this._interpretCommandResult(result);
// Check for auto-closing closed characters
let autoClosedCharactersRanges: IModelDeltaDecoration[] = [];
let autoClosedEnclosingRanges: IModelDeltaDecoration[] = [];
let autoClosedCharactersRanges: Range[] = [];
let autoClosedEnclosingRanges: Range[] = [];
for (let i = 0; i < opResult.commands.length; i++) {
const command = opResult.commands[i];
if (command instanceof TypeWithAutoClosingCommand && command.enclosingRange && command.closeCharacterRange) {
autoClosedCharactersRanges.push({
range: command.closeCharacterRange,
options: {
inlineClassName: 'auto-closed-character',
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
}
});
autoClosedEnclosingRanges.push({
range: command.enclosingRange,
options: {
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
}
});
autoClosedCharactersRanges.push(command.closeCharacterRange);
autoClosedEnclosingRanges.push(command.enclosingRange);
}
}
if (autoClosedCharactersRanges.length > 0) {
const autoClosedCharactersDecorations = this._model.deltaDecorations([], autoClosedCharactersRanges);
const autoClosedEnclosingDecorations = this._model.deltaDecorations([], autoClosedEnclosingRanges);
this._autoClosedActions.push(new AutoClosedAction(this._model, autoClosedCharactersDecorations, autoClosedEnclosingDecorations));
this._pushAutoClosedAction(autoClosedCharactersRanges, autoClosedEnclosingRanges);
}
this._prevEditOperationType = opResult.type;
......@@ -563,6 +575,75 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
// -----------------------------------------------------------------------------------------------------------
// ----- handlers beyond this point
private _findAutoClosingPairs(edits: IIdentifiedSingleEditOperation[]): [number, number][] | null {
if (!edits.length) {
return null;
}
let indices: [number, number][] = [];
for (let i = 0, len = edits.length; i < len; i++) {
const edit = edits[i];
if (!edit.text || edit.text.indexOf('\n') >= 0) {
return null;
}
const m = edit.text.match(/([)\]}>'"`])([^)\]}>'"`]*)$/);
if (!m) {
return null;
}
const closeChar = m[1];
const openChar = this.context.config.autoClosingPairsClose[closeChar];
if (!openChar) {
return null;
}
const closeCharIndex = edit.text.length - m[2].length - 1;
const openCharIndex = edit.text.lastIndexOf(openChar, closeCharIndex - 1);
if (openCharIndex === -1) {
return null;
}
indices.push([openCharIndex, closeCharIndex]);
}
return indices;
}
public executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): void {
let autoClosingIndices: [number, number][] | null = null;
if (source === 'snippet') {
autoClosingIndices = this._findAutoClosingPairs(edits);
}
if (autoClosingIndices) {
edits[0]._isTracked = true;
}
let autoClosedCharactersRanges: Range[] = [];
let autoClosedEnclosingRanges: Range[] = [];
const selections = this._model.pushEditOperations(this.getSelections(), edits, (undoEdits) => {
if (autoClosingIndices) {
for (let i = 0, len = autoClosingIndices.length; i < len; i++) {
const [openCharInnerIndex, closeCharInnerIndex] = autoClosingIndices[i];
const undoEdit = undoEdits[i];
const lineNumber = undoEdit.range.startLineNumber;
const openCharIndex = undoEdit.range.startColumn - 1 + openCharInnerIndex;
const closeCharIndex = undoEdit.range.startColumn - 1 + closeCharInnerIndex;
autoClosedCharactersRanges.push(new Range(lineNumber, closeCharIndex + 1, lineNumber, closeCharIndex + 2));
autoClosedEnclosingRanges.push(new Range(lineNumber, openCharIndex + 1, lineNumber, closeCharIndex + 2));
}
}
return cursorStateComputer(undoEdits);
});
if (selections) {
this.setSelections(source, selections);
}
if (autoClosedCharactersRanges.length > 0) {
this._pushAutoClosedAction(autoClosedCharactersRanges, autoClosedEnclosingRanges);
}
}
public trigger(source: string, handlerId: string, payload: any): void {
const H = editorCommon.Handler;
......
......@@ -489,21 +489,18 @@ export class SnippetSession {
return;
}
const model = this._editor.getModel();
// make insert edit and start with first selections
const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, this._template, this._options.overwriteBefore, this._options.overwriteAfter, false, this._options.adjustWhitespace, this._options.clipboardText);
this._snippets = snippets;
const selections = model.pushEditOperations(this._editor.getSelections(), edits, undoEdits => {
this._editor.executeEdits('snippet', edits, undoEdits => {
if (this._snippets[0].hasPlaceholder) {
return this._move(true);
} else {
return undoEdits.map(edit => Selection.fromPositions(edit.range.getEndPosition()));
}
})!;
this._editor.setSelections(selections);
this._editor.revealRange(selections[0]);
});
this._editor.revealRange(this._editor.getSelections()[0]);
}
merge(template: string, options: ISnippetSessionInsertOptions = _defaultOptions): void {
......@@ -513,8 +510,7 @@ export class SnippetSession {
this._templateMerges.push([this._snippets[0]._nestingLevel, this._snippets[0]._placeholderGroupsIdx, template]);
const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, template, options.overwriteBefore, options.overwriteAfter, true, options.adjustWhitespace, options.clipboardText);
this._editor.setSelections(this._editor.getModel().pushEditOperations(this._editor.getSelections(), edits, undoEdits => {
this._editor.executeEdits('snippet', edits, undoEdits => {
for (const snippet of this._snippets) {
snippet.merge(snippets);
}
......@@ -525,7 +521,7 @@ export class SnippetSession {
} else {
return undoEdits.map(edit => Selection.fromPositions(edit.range.getEndPosition()));
}
})!);
});
}
next(): void {
......
......@@ -4692,6 +4692,28 @@ suite('autoClosingPairs', () => {
mode.dispose();
});
test('issue #78975 - Parentheses swallowing does not work when parentheses are inserted by autocomplete', () => {
let mode = new AutoClosingMode();
usingCursor({
text: [
'<div id'
],
languageIdentifier: mode.getLanguageIdentifier()
}, (model, cursor) => {
cursor.setSelections('test', [new Selection(1, 8, 1, 8)]);
cursor.executeEdits('snippet', [{ range: new Range(1, 6, 1, 8), text: 'id=""' }], () => [new Selection(1, 10, 1, 10)]);
assert.strictEqual(model.getLineContent(1), '<div id=""');
cursorCommand(cursor, H.Type, { text: 'a' }, 'keyboard');
assert.strictEqual(model.getLineContent(1), '<div id="a"');
cursorCommand(cursor, H.Type, { text: '"' }, 'keyboard');
assert.strictEqual(model.getLineContent(1), '<div id="a"');
});
mode.dispose();
});
test('issue #15825: accents on mac US intl keyboard', () => {
let mode = new AutoClosingMode();
usingCursor({
......
......@@ -4054,7 +4054,7 @@ declare namespace monaco.editor {
* @param edits The edits to execute.
* @param endCursorState Cursor state after the edits were applied.
*/
executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: Selection[]): boolean;
executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean;
/**
* Execute multiple (concomitant) commands on the editor.
* @param source The source of the call.
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册