mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-27 10:37:38 +01:00
Implementation TM_SELECTED_TEXT snippets variable for working with overtyped text
This commit is contained in:
@@ -1137,6 +1137,12 @@ export interface ITextModel {
|
||||
*/
|
||||
_applyRedo(changes: TextChange[], eol: EndOfLineSequence, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void;
|
||||
|
||||
/**
|
||||
* Returns the text that has just been overtyped
|
||||
* @internal
|
||||
*/
|
||||
getOvertypedText(overtypeIdx: number, undoSearchLimit: number, editSizeLimit: number): string | undefined;
|
||||
|
||||
/**
|
||||
* Undo edit operations until the first previous stop point created by `pushStackElement`.
|
||||
* The inverse edit operations will be pushed on the redo stack.
|
||||
|
||||
@@ -198,6 +198,24 @@ export class SingleModelEditStackElement implements IResourceUndoRedoElement {
|
||||
}
|
||||
}
|
||||
|
||||
public getOvertypedTextAtEditingEnd(overtypeIdx: number, editingEnd: number): { text: string | undefined, continuousEditing: boolean, previousEditingEnd: number } {
|
||||
let continuousEditing = false;
|
||||
let overtypedText: string | undefined;
|
||||
const data = (this._data instanceof SingleModelEditStackData ? this._data : SingleModelEditStackData.deserialize(this._data));
|
||||
const change = data.changes[overtypeIdx >= data.changes.length ? 0 : overtypeIdx];
|
||||
const inserted = change.oldLength === 0;
|
||||
const deleted = change.newLength === 0;
|
||||
if (inserted !== deleted) {
|
||||
// If change is insert, and starts with new line, break the search
|
||||
if (!(inserted && /^[\r\n]/.test(change.newText))) {
|
||||
continuousEditing = editingEnd < 0 ? true : change.newEnd === editingEnd;
|
||||
}
|
||||
} else if (editingEnd < 0 || change.newEnd === editingEnd) {
|
||||
overtypedText = change.oldText;
|
||||
}
|
||||
return { text: overtypedText, continuousEditing: continuousEditing, previousEditingEnd: change.oldEnd };
|
||||
}
|
||||
|
||||
public undo(): void {
|
||||
if (URI.isUri(this.model)) {
|
||||
// don't have a model
|
||||
|
||||
@@ -16,7 +16,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 model from 'vs/editor/common/model';
|
||||
import { EditStack } from 'vs/editor/common/model/editStack';
|
||||
import { EditStack, SingleModelEditStackElement } from 'vs/editor/common/model/editStack';
|
||||
import { guessIndentation } from 'vs/editor/common/model/indentationGuesser';
|
||||
import { IntervalNode, IntervalTree, getNodeIsInOverviewRuler, recomputeMaxEnd } from 'vs/editor/common/model/intervalTree';
|
||||
import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder';
|
||||
@@ -34,7 +34,7 @@ import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer';
|
||||
import { TokensStore, MultilineTokens, countEOL, MultilineTokens2, TokensStore2 } from 'vs/editor/common/model/tokensStore';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { EditorTheme } from 'vs/editor/common/view/viewContext';
|
||||
import { IUndoRedoService, ResourceEditStackSnapshot } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { IUndoRedoService, ResourceEditStackSnapshot, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { TextChange } from 'vs/editor/common/model/textChange';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
|
||||
@@ -1491,6 +1491,33 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return (result.reverseEdits === null ? undefined : result.reverseEdits);
|
||||
}
|
||||
|
||||
public getOvertypedText(overtypeIdx: number, undoSearchLimit: number, editSizeLimit: number): string | undefined {
|
||||
const elements = this._undoRedoService.getElements(this.uri).past;
|
||||
if (elements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cycle through undo elements to find one containing text that was overtyped
|
||||
let editingEnd = -1;
|
||||
let elementIndex = elements.length;
|
||||
do {
|
||||
const element = elements[--elementIndex];
|
||||
if (!(element.type === UndoRedoElementType.Resource && element instanceof SingleModelEditStackElement)) {
|
||||
return;
|
||||
}
|
||||
const searchResult = element.getOvertypedTextAtEditingEnd(overtypeIdx, editingEnd);
|
||||
if (searchResult.text) {
|
||||
return searchResult.text;
|
||||
}
|
||||
if (!searchResult.continuousEditing || (editingEnd >= 0 && Math.abs(editingEnd - searchResult.previousEditingEnd) > editSizeLimit)) {
|
||||
return;
|
||||
}
|
||||
editingEnd = searchResult.previousEditingEnd;
|
||||
} while (elementIndex > 0 && --undoSearchLimit > 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public undo(): void {
|
||||
this._undoRedoService.undo(this.uri);
|
||||
}
|
||||
|
||||
@@ -415,6 +415,7 @@ export class SnippetSession {
|
||||
.map((selection, idx) => ({ selection, idx }))
|
||||
.sort((a, b) => Range.compareRangesUsingStarts(a.selection, b.selection));
|
||||
|
||||
let overtypedIdx = 0; // Makes overtyping snippets working with multiselections
|
||||
for (const { selection, idx } of indexedSelections) {
|
||||
|
||||
// extend selection with the `overwriteBefore` and `overwriteAfter` and then
|
||||
@@ -449,7 +450,7 @@ export class SnippetSession {
|
||||
snippet.resolveVariables(new CompositeSnippetVariableResolver([
|
||||
modelBasedVariableResolver,
|
||||
new ClipboardBasedVariableResolver(readClipboardText, idx, indexedSelections.length, editor.getOption(EditorOption.multiCursorPaste) === 'spread'),
|
||||
new SelectionBasedVariableResolver(model, selection),
|
||||
new SelectionBasedVariableResolver(model, selection, overtypedIdx++),
|
||||
new CommentBasedVariableResolver(model, selection),
|
||||
new TimeBasedVariableResolver,
|
||||
new WorkspaceBasedVariableResolver(workspaceService),
|
||||
|
||||
@@ -71,7 +71,8 @@ export class SelectionBasedVariableResolver implements VariableResolver {
|
||||
|
||||
constructor(
|
||||
private readonly _model: ITextModel,
|
||||
private readonly _selection: Selection
|
||||
private readonly _selection: Selection,
|
||||
private readonly _overtypeIdx: number
|
||||
) {
|
||||
//
|
||||
}
|
||||
@@ -82,14 +83,25 @@ export class SelectionBasedVariableResolver implements VariableResolver {
|
||||
|
||||
if (name === 'SELECTION' || name === 'TM_SELECTED_TEXT') {
|
||||
let value = this._model.getValueInRange(this._selection) || undefined;
|
||||
if (value && this._selection.startLineNumber !== this._selection.endLineNumber && variable.snippet) {
|
||||
|
||||
// If there is no selected text, try to get overtyped text
|
||||
let overtyped = false;
|
||||
if (!value) {
|
||||
const maxTypos = 10; // Allows the user to make 10 typos (delete+insert) when typing the snippet prefix
|
||||
const maxEditSize = 50; // Limits the searching by edit size less than 50 symbols
|
||||
value = this._model.getOvertypedText(this._overtypeIdx, 1 + 2 * maxTypos, maxEditSize);
|
||||
if (value) {
|
||||
overtyped = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (value && (overtyped || (this._selection.startLineNumber !== this._selection.endLineNumber)) && variable.snippet) {
|
||||
// Selection is a multiline string which we indentation we now
|
||||
// need to adjust. We compare the indentation of this variable
|
||||
// with the indentation at the editor position and add potential
|
||||
// extra indentation to the value
|
||||
|
||||
const line = this._model.getLineContent(this._selection.startLineNumber);
|
||||
const lineLeadingWhitespace = getLeadingWhitespace(line, 0, this._selection.startColumn - 1);
|
||||
const lineLeadingWhitespace = overtyped ? '' : getLeadingWhitespace(this._model.getLineContent(this._selection.startLineNumber), 0, this._selection.startColumn - 1);
|
||||
|
||||
let varLeadingWhitespace = lineLeadingWhitespace;
|
||||
variable.snippet.walk(marker => {
|
||||
|
||||
@@ -34,7 +34,7 @@ suite('Snippet Variables Resolver', function () {
|
||||
|
||||
resolver = new CompositeSnippetVariableResolver([
|
||||
new ModelBasedVariableResolver(labelService, model),
|
||||
new SelectionBasedVariableResolver(model, new Selection(1, 1, 1, 1)),
|
||||
new SelectionBasedVariableResolver(model, new Selection(1, 1, 1, 1), 0),
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -102,24 +102,24 @@ suite('Snippet Variables Resolver', function () {
|
||||
|
||||
test('editor variables, selection', function () {
|
||||
|
||||
resolver = new SelectionBasedVariableResolver(model, new Selection(1, 2, 2, 3));
|
||||
resolver = new SelectionBasedVariableResolver(model, new Selection(1, 2, 2, 3), 0);
|
||||
assertVariableResolve(resolver, 'TM_SELECTED_TEXT', 'his is line one\nth');
|
||||
assertVariableResolve(resolver, 'TM_CURRENT_LINE', 'this is line two');
|
||||
assertVariableResolve(resolver, 'TM_LINE_INDEX', '1');
|
||||
assertVariableResolve(resolver, 'TM_LINE_NUMBER', '2');
|
||||
|
||||
resolver = new SelectionBasedVariableResolver(model, new Selection(2, 3, 1, 2));
|
||||
resolver = new SelectionBasedVariableResolver(model, new Selection(2, 3, 1, 2), 0);
|
||||
assertVariableResolve(resolver, 'TM_SELECTED_TEXT', 'his is line one\nth');
|
||||
assertVariableResolve(resolver, 'TM_CURRENT_LINE', 'this is line one');
|
||||
assertVariableResolve(resolver, 'TM_LINE_INDEX', '0');
|
||||
assertVariableResolve(resolver, 'TM_LINE_NUMBER', '1');
|
||||
|
||||
resolver = new SelectionBasedVariableResolver(model, new Selection(1, 2, 1, 2));
|
||||
resolver = new SelectionBasedVariableResolver(model, new Selection(1, 2, 1, 2), 0);
|
||||
assertVariableResolve(resolver, 'TM_SELECTED_TEXT', undefined);
|
||||
|
||||
assertVariableResolve(resolver, 'TM_CURRENT_WORD', 'this');
|
||||
|
||||
resolver = new SelectionBasedVariableResolver(model, new Selection(3, 1, 3, 1));
|
||||
resolver = new SelectionBasedVariableResolver(model, new Selection(3, 1, 3, 1), 0);
|
||||
assertVariableResolve(resolver, 'TM_CURRENT_WORD', undefined);
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user