提交 9c3880c5 编写于 作者: J Johannes Rieken

validate editor selections

上级 ff8b85ff
......@@ -290,6 +290,10 @@ export class Range {
// ---
public static fromPositions(start: IPosition, end: IPosition = start): Range {
return new Range(start.lineNumber, start.column, end.lineNumber, end.column);
}
/**
* Create a `Range` from an `IRange`.
*/
......
......@@ -20,14 +20,16 @@ class OneSnippet {
private readonly _snippet: TextmateSnippet;
private readonly _offset: number;
private _snippetDecoration: string;
private _placeholderDecorations: Map<Placeholder, string>;
private _placeholderGroups: Placeholder[][];
private _placeholderGroupsIdx: number;
private static readonly _decorations = {
private static readonly _decor = {
active: <IModelDecorationOptions>{ stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges },
activeFinal: <IModelDecorationOptions>{ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges },
inActive: <IModelDecorationOptions>{ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges },
inactive: <IModelDecorationOptions>{ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges },
snippet: <IModelDecorationOptions>{ stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges },
};
constructor(editor: ICommonCodeEditor, snippet: TextmateSnippet, offset: number) {
......@@ -52,22 +54,25 @@ class OneSnippet {
this._placeholderDecorations = new Map<Placeholder, string>();
const model = this._editor.getModel();
// create a decoration (tracked range) for each placeholder
this._editor.changeDecorations(accessor => {
let lastRange: Range;
// create one decoration for the whole snippets
const range = Range.fromPositions(
model.getPositionAt(this._offset),
model.getPositionAt(this._offset + this._snippet.text.length)
);
this._snippetDecoration = accessor.addDecoration(range, OneSnippet._decor.snippet);
// create a decoration for each placeholder
for (const placeholder of this._snippet.getPlaceholders()) {
const placeholderOffset = this._snippet.offset(placeholder);
const placeholderLen = this._snippet.len(placeholder);
const start = model.getPositionAt(this._offset + placeholderOffset);
const end = model.getPositionAt(this._offset + placeholderOffset + placeholderLen);
const range = new Range(start.lineNumber, start.column, end.lineNumber, end.column);
const handle = accessor.addDecoration(range, OneSnippet._decorations.inActive);
const range = Range.fromPositions(
model.getPositionAt(this._offset + placeholderOffset),
model.getPositionAt(this._offset + placeholderOffset + placeholderLen)
);
const handle = accessor.addDecoration(range, OneSnippet._decor.inactive);
this._placeholderDecorations.set(placeholder, handle);
lastRange = range;
}
});
......@@ -107,7 +112,7 @@ class OneSnippet {
if (prevGroupsIdx !== -1) {
for (const placeholder of this._placeholderGroups[prevGroupsIdx]) {
const id = this._placeholderDecorations.get(placeholder);
accessor.changeDecorationOptions(id, OneSnippet._decorations.inActive);
accessor.changeDecorationOptions(id, OneSnippet._decor.inactive);
}
}
......@@ -120,7 +125,7 @@ class OneSnippet {
const range = this._editor.getModel().getDecorationRange(id);
selections.push(new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn));
accessor.changeDecorationOptions(id, placeholder.isFinalTabstop ? OneSnippet._decorations.activeFinal : OneSnippet._decorations.active);
accessor.changeDecorationOptions(id, placeholder.isFinalTabstop ? OneSnippet._decor.activeFinal : OneSnippet._decor.active);
}
return selections;
});
......@@ -133,6 +138,10 @@ class OneSnippet {
return this._placeholderGroups[this._placeholderGroupsIdx][0].isFinalTabstop;
}
}
get range() {
return this._snippetDecoration !== undefined && this._editor.getModel().getDecorationRange(this._snippetDecoration);
}
}
export class SnippetSession {
......@@ -170,10 +179,10 @@ export class SnippetSession {
const snippet = SnippetParser.parse(adjustedTemplate);
const offset = model.getOffsetAt(start) + delta;
edits.push(EditOperation.replaceMove(selection, snippet.value));
edits.push(EditOperation.replaceMove(selection, snippet.text));
this._snippets.push(new OneSnippet(editor, snippet, offset));
delta += snippet.value.length - model.getValueLengthInRange(selection);
delta += snippet.text.length - model.getValueLengthInRange(selection);
}
// make insert edit and start with first selections
......@@ -211,4 +220,32 @@ export class SnippetSession {
get isAtFinalPlaceholder() {
return this._snippets[0].isAtFinalPlaceholder;
}
validateSelections(): boolean {
const selections = this._editor.getSelections();
if (selections.length < this._snippets.length) {
return false;
}
for (const selection of selections) {
let found = false;
for (const { range } of this._snippets) {
if (!range) {
// too early, not yet initialized
return true;
}
if (range.containsRange(selection)) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
}
......@@ -271,7 +271,7 @@ export class TextmateSnippet {
return ret;
}
get value() {
get text() {
return Marker.toString(this.marker);
}
......
......@@ -12,7 +12,7 @@ import { ICommonCodeEditor } from 'vs/editor/common/editorCommon';
import { mockCodeEditor } from 'vs/editor/test/common/mocks/mockCodeEditor';
import { Model } from "vs/editor/common/model/model";
suite('SnippetInsertion', function () {
suite('SnippetSession', function () {
let editor: ICommonCodeEditor;
let model: Model;
......@@ -234,5 +234,52 @@ suite('SnippetInsertion', function () {
assertSelections(editor, new Selection(1, 4, 1, 4), new Selection(2, 8, 2, 8));
});
test('snippets, overwriting nested placeholder', function () {
const session = new SnippetSession(editor, 'log(${1:"$2"});$0');
assertSelections(editor, new Selection(1, 5, 1, 7), new Selection(2, 9, 2, 11));
editor.trigger('test', 'type', { text: 'XXX' });
assert.equal(model.getValue(), 'log(XXX);function foo() {\n log(XXX);console.log(a);\n}');
session.next();
assert.equal(session.isAtFinalPlaceholder, false);
// assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
session.next();
assert.equal(session.isAtFinalPlaceholder, true);
assertSelections(editor, new Selection(1, 10, 1, 10), new Selection(2, 14, 2, 14));
});
test('snippets, selections and snippet ranges', function () {
const session = new SnippetSession(editor, '${1:foo}farboo${2:bar}$0');
assert.equal(model.getValue(), 'foofarboobarfunction foo() {\n foofarboobarconsole.log(a);\n}');
assertSelections(editor, new Selection(1, 1, 1, 4), new Selection(2, 5, 2, 8));
assert.equal(session.validateSelections(), true);
editor.setSelections([new Selection(1, 1, 1, 1)]);
assert.equal(session.validateSelections(), false);
editor.setSelections([new Selection(1, 6, 1, 6), new Selection(2, 10, 2, 10)]);
assert.equal(session.validateSelections(), true);
editor.setSelections([new Selection(1, 6, 1, 6), new Selection(2, 10, 2, 10), new Selection(1, 1, 1, 1)]);
assert.equal(session.validateSelections(), true);
editor.setSelections([new Selection(1, 6, 1, 6), new Selection(2, 10, 2, 10), new Selection(2, 20, 2, 21)]);
assert.equal(session.validateSelections(), false);
// reset selection to placeholder
session.next();
assert.equal(session.validateSelections(), true);
assertSelections(editor, new Selection(1, 10, 1, 13), new Selection(2, 14, 2, 17));
// reset selection to placeholder
session.next();
assert.equal(session.validateSelections(), true);
assert.equal(session.isAtFinalPlaceholder, true);
assertSelections(editor, new Selection(1, 13, 1, 13), new Selection(2, 17, 2, 17));
});
});
......@@ -351,30 +351,30 @@ suite('SnippetParser', () => {
test('TextmateSnippet#withIndentation', () => {
let snippet = SnippetParser.parse('foo\n bar');
assert.equal(snippet.value, 'foo\n bar');
assert.equal(snippet.text, 'foo\n bar');
let snippet1 = snippet.withIndentation(s => s.replace(/ /, '\t'));
let snippet2 = snippet.withIndentation(s => s.replace(/ /, ' '));
assert.equal(snippet.value, 'foo\n bar');
assert.equal(snippet1.value, 'foo\n\tbar');
assert.equal(snippet2.value, 'foo\n bar');
assert.equal(snippet.text, 'foo\n bar');
assert.equal(snippet1.text, 'foo\n\tbar');
assert.equal(snippet2.text, 'foo\n bar');
snippet = SnippetParser.parse('foo\n bar');
assert.equal(snippet.value, 'foo\n bar');
assert.equal(snippet.text, 'foo\n bar');
let newSnippet = snippet.withIndentation(s => s.replace(/ /, '\t'));
assert.equal(snippet.value, 'foo\n bar');
assert.equal(newSnippet.value, 'foo\n\tbar');
assert.equal(snippet.text, 'foo\n bar');
assert.equal(newSnippet.text, 'foo\n\tbar');
snippet = SnippetParser.parse('foo\r\n bar\r\n far');
assert.equal(snippet.value, 'foo\r\n bar\r\n far');
assert.equal(snippet.text, 'foo\r\n bar\r\n far');
newSnippet = snippet.withIndentation(s => s.replace(/ /, '\t'));
assert.equal(snippet.value, 'foo\r\n bar\r\n far');
assert.equal(newSnippet.value, 'foo\r\n\tbar\r\n\tfar');
assert.equal(snippet.text, 'foo\r\n bar\r\n far');
assert.equal(newSnippet.text, 'foo\r\n\tbar\r\n\tfar');
snippet = SnippetParser.parse('foo${1:bar\r far\r boo}');
assert.equal(snippet.value, 'foobar\r far\r boo');
assert.equal(snippet.text, 'foobar\r far\r boo');
newSnippet = snippet.withIndentation(s => s.replace(/ /, '\t'));
assert.equal(snippet.value, 'foobar\r far\r boo');
assert.equal(newSnippet.value, 'foobar\r\tfar\r\tboo');
assert.equal(snippet.text, 'foobar\r far\r boo');
assert.equal(newSnippet.text, 'foobar\r\tfar\r\tboo');
});
});
......@@ -624,6 +624,7 @@ declare module monaco {
* Create a new empty range using this range's start position.
*/
static collapseToStart(range: IRange): Range;
static fromPositions(start: IPosition, end?: IPosition): Range;
/**
* Create a `Range` from an `IRange`.
*/
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册