提交 70a27a33 编写于 作者: J Johannes Rieken

add CLIPBOARD snippet variable, #40153

上级 50b7c7bd
......@@ -15,9 +15,11 @@ import { Range } from 'vs/editor/common/core/range';
import { IPosition } from 'vs/editor/common/core/position';
import { groupBy } from 'vs/base/common/arrays';
import { dispose } from 'vs/base/common/lifecycle';
import { EditorSnippetVariableResolver } from './snippetVariables';
import { SelectionBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, ClipboardBasedVariableResolver } from './snippetVariables';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { optional } from 'vs/platform/instantiation/common/instantiation';
export class OneSnippet {
......@@ -269,6 +271,9 @@ export class SnippetSession {
const edits: IIdentifiedSingleEditOperation[] = [];
const snippets: OneSnippet[] = [];
const modelBasedVariableResolver = new ModelBasedVariableResolver(model);
const clipboardVariableResolver = new ClipboardBasedVariableResolver(editor.invokeWithinContext(accessor => accessor.get(IClipboardService, optional)));
let delta = 0;
// know what text the overwrite[Before|After] extensions
......@@ -310,7 +315,11 @@ export class SnippetSession {
const snippet = new SnippetParser()
.parse(adjustedTemplate, true, enforceFinalTabstop)
.resolveVariables(new EditorSnippetVariableResolver(model, selection));
.resolveVariables(new CompositeSnippetVariableResolver([
modelBasedVariableResolver,
clipboardVariableResolver,
new SelectionBasedVariableResolver(model, selection)
]));
const offset = model.getOffsetAt(start) + delta;
delta += snippet.toString().length - model.getValueLengthInRange(snippetSelection);
......
......@@ -10,21 +10,40 @@ import { IModel } from 'vs/editor/common/editorCommon';
import { Selection } from 'vs/editor/common/core/selection';
import { VariableResolver, Variable, Text } from 'vs/editor/contrib/snippet/snippetParser';
import { getLeadingWhitespace, commonPrefixLength } from 'vs/base/common/strings';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
export const KnownSnippetVariableNames = Object.freeze({
'SELECTION': true,
'CLIPBOARD': true,
'TM_SELECTED_TEXT': true,
'TM_CURRENT_LINE': true,
'TM_CURRENT_WORD': true,
'TM_LINE_INDEX': true,
'TM_LINE_NUMBER': true,
'TM_FILENAME': true,
'TM_FILENAME_BASE': true,
'TM_DIRECTORY': true,
'TM_FILEPATH': true,
});
export class CompositeSnippetVariableResolver implements VariableResolver {
constructor(private readonly _delegates: VariableResolver[]) {
//
}
export class EditorSnippetVariableResolver implements VariableResolver {
static readonly VariableNames = Object.freeze({
'SELECTION': true,
'TM_SELECTED_TEXT': true,
'TM_CURRENT_LINE': true,
'TM_CURRENT_WORD': true,
'TM_LINE_INDEX': true,
'TM_LINE_NUMBER': true,
'TM_FILENAME': true,
'TM_FILENAME_BASE': true,
'TM_DIRECTORY': true,
'TM_FILEPATH': true,
});
resolve(variable: Variable): string {
for (const delegate of this._delegates) {
let value = delegate.resolve(variable);
if (value !== void 0) {
return value;
}
}
return undefined;
}
}
export class SelectionBasedVariableResolver implements VariableResolver {
constructor(
private readonly _model: IModel,
......@@ -82,8 +101,24 @@ export class EditorSnippetVariableResolver implements VariableResolver {
} else if (name === 'TM_LINE_NUMBER') {
return String(this._selection.positionLineNumber);
}
return undefined;
}
}
export class ModelBasedVariableResolver implements VariableResolver {
constructor(
private readonly _model: IModel
) {
//
}
} else if (name === 'TM_FILENAME') {
resolve(variable: Variable): string {
const { name } = variable;
if (name === 'TM_FILENAME') {
return basename(this._model.uri.fsPath);
} else if (name === 'TM_FILENAME_BASE') {
......@@ -101,9 +136,23 @@ export class EditorSnippetVariableResolver implements VariableResolver {
} else if (name === 'TM_FILEPATH') {
return this._model.uri.fsPath;
} else {
return undefined;
}
return undefined;
}
}
export class ClipboardBasedVariableResolver implements VariableResolver {
constructor(
private readonly _clipboardService: IClipboardService
) {
//
}
resolve(variable: Variable): string {
return (variable.name === 'CLIPBOARD' && this._clipboardService)
? this._clipboardService.readText() || undefined
: undefined;
}
}
......@@ -8,14 +8,15 @@ import * as assert from 'assert';
import { isWindows } from 'vs/base/common/platform';
import URI from 'vs/base/common/uri';
import { Selection } from 'vs/editor/common/core/selection';
import { EditorSnippetVariableResolver } from 'vs/editor/contrib/snippet/snippetVariables';
import { SnippetParser, Variable } from 'vs/editor/contrib/snippet/snippetParser';
import { SelectionBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, ClipboardBasedVariableResolver } from 'vs/editor/contrib/snippet/snippetVariables';
import { SnippetParser, Variable, VariableResolver } from 'vs/editor/contrib/snippet/snippetParser';
import { Model } from 'vs/editor/common/model/model';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
suite('Snippet Variables Resolver', function () {
let model: Model;
let resolver: EditorSnippetVariableResolver;
let resolver: VariableResolver;
setup(function () {
model = Model.createFromString([
......@@ -24,14 +25,17 @@ suite('Snippet Variables Resolver', function () {
' this is line three'
].join('\n'), undefined, undefined, URI.parse('file:///foo/files/text.txt'));
resolver = new EditorSnippetVariableResolver(model, new Selection(1, 1, 1, 1));
resolver = new CompositeSnippetVariableResolver([
new ModelBasedVariableResolver(model),
new SelectionBasedVariableResolver(model, new Selection(1, 1, 1, 1)),
]);
});
teardown(function () {
model.dispose();
});
function assertVariableResolve(resolver: EditorSnippetVariableResolver, varName: string, expected: string) {
function assertVariableResolve(resolver: VariableResolver, varName: string, expected: string) {
const snippet = new SnippetParser().parse(`$${varName}`);
const variable = <Variable>snippet.children[0];
variable.resolve(resolver);
......@@ -55,9 +59,8 @@ suite('Snippet Variables Resolver', function () {
assertVariableResolve(resolver, 'TM_FILEPATH', '/foo/files/text.txt');
}
resolver = new EditorSnippetVariableResolver(
Model.createFromString('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')),
new Selection(1, 1, 1, 1)
resolver = new ModelBasedVariableResolver(
Model.createFromString('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi'))
);
assertVariableResolve(resolver, 'TM_FILENAME', 'ghi');
if (!isWindows) {
......@@ -65,9 +68,8 @@ suite('Snippet Variables Resolver', function () {
assertVariableResolve(resolver, 'TM_FILEPATH', '/abc/def/ghi');
}
resolver = new EditorSnippetVariableResolver(
Model.createFromString('', undefined, undefined, URI.parse('mem:fff.ts')),
new Selection(1, 1, 1, 1)
resolver = new ModelBasedVariableResolver(
Model.createFromString('', undefined, undefined, URI.parse('mem:fff.ts'))
);
assertVariableResolve(resolver, 'TM_DIRECTORY', '');
assertVariableResolve(resolver, 'TM_FILEPATH', 'fff.ts');
......@@ -76,24 +78,24 @@ suite('Snippet Variables Resolver', function () {
test('editor variables, selection', function () {
resolver = new EditorSnippetVariableResolver(model, new Selection(1, 2, 2, 3));
resolver = new SelectionBasedVariableResolver(model, new Selection(1, 2, 2, 3));
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 EditorSnippetVariableResolver(model, new Selection(2, 3, 1, 2));
resolver = new SelectionBasedVariableResolver(model, new Selection(2, 3, 1, 2));
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 EditorSnippetVariableResolver(model, new Selection(1, 2, 1, 2));
resolver = new SelectionBasedVariableResolver(model, new Selection(1, 2, 1, 2));
assertVariableResolve(resolver, 'TM_SELECTED_TEXT', undefined);
assertVariableResolve(resolver, 'TM_CURRENT_WORD', 'this');
resolver = new EditorSnippetVariableResolver(model, new Selection(3, 1, 3, 1));
resolver = new SelectionBasedVariableResolver(model, new Selection(3, 1, 3, 1));
assertVariableResolve(resolver, 'TM_CURRENT_WORD', undefined);
});
......@@ -117,21 +119,18 @@ suite('Snippet Variables Resolver', function () {
assertVariableResolve(resolver, 'TM_FILENAME_BASE', 'text');
resolver = new EditorSnippetVariableResolver(
Model.createFromString('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')),
new Selection(1, 1, 1, 1)
resolver = new ModelBasedVariableResolver(
Model.createFromString('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi'))
);
assertVariableResolve(resolver, 'TM_FILENAME_BASE', 'ghi');
resolver = new EditorSnippetVariableResolver(
Model.createFromString('', undefined, undefined, URI.parse('mem:.git')),
new Selection(1, 1, 1, 1)
resolver = new ModelBasedVariableResolver(
Model.createFromString('', undefined, undefined, URI.parse('mem:.git'))
);
assertVariableResolve(resolver, 'TM_FILENAME_BASE', '.git');
resolver = new EditorSnippetVariableResolver(
Model.createFromString('', undefined, undefined, URI.parse('mem:foo.')),
new Selection(1, 1, 1, 1)
resolver = new ModelBasedVariableResolver(
Model.createFromString('', undefined, undefined, URI.parse('mem:foo.'))
);
assertVariableResolve(resolver, 'TM_FILENAME_BASE', 'foo');
});
......@@ -208,4 +207,30 @@ suite('Snippet Variables Resolver', function () {
);
});
test('Add variable to insert value from clipboard to a snippet #40153', function () {
let readTextResult: string;
let _throw = () => { throw new Error(); };
let resolver = new ClipboardBasedVariableResolver(new class implements IClipboardService {
_serviceBrand: any;
readText(): string { return readTextResult; }
writeText = _throw;
readFindText = _throw;
writeFindText = _throw;
});
readTextResult = undefined;
assertVariableResolve(resolver, 'CLIPBOARD', undefined);
readTextResult = null;
assertVariableResolve(resolver, 'CLIPBOARD', undefined);
readTextResult = '';
assertVariableResolve(resolver, 'CLIPBOARD', undefined);
readTextResult = 'foo';
assertVariableResolve(resolver, 'CLIPBOARD', 'foo');
assertVariableResolve(resolver, 'foo', undefined);
assertVariableResolve(resolver, 'cLIPBOARD', undefined);
});
});
......@@ -20,7 +20,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { LanguageId } from 'vs/editor/common/modes';
import { TPromise } from 'vs/base/common/winjs.base';
import { SnippetParser, Variable, Placeholder, Text } from 'vs/editor/contrib/snippet/snippetParser';
import { EditorSnippetVariableResolver } from 'vs/editor/contrib/snippet/snippetVariables';
import { KnownSnippetVariableNames } from 'vs/editor/contrib/snippet/snippetVariables';
export const ISnippetsService = createDecorator<ISnippetsService>('snippetService');
......@@ -107,7 +107,7 @@ export class Snippet {
if (
marker instanceof Variable
&& marker.children.length === 0
&& !EditorSnippetVariableResolver.VariableNames[marker.name]
&& !KnownSnippetVariableNames[marker.name]
) {
// a 'variable' without a default value and not being one of our supported
// variables is automatically turned into a placeholder. This is to restore
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册