Skip to content

Commit a057a59

Browse files
committed
Prepping to move completion provider to a re-usable module
1 parent d51eaa4 commit a057a59

16 files changed

Lines changed: 343 additions & 280 deletions

extensions/emmet/src/abbreviationActions.ts

Lines changed: 16 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@
55

66
import * as vscode from 'vscode';
77
import { expand } from '@emmetio/expand-abbreviation';
8-
import * as extract from '@emmetio/extract-abbreviation';
98
import parseStylesheet from '@emmetio/css-parser';
109
import parse from '@emmetio/html-matcher';
1110
import Node from '@emmetio/node';
12-
13-
import { getSyntax, getProfile, getVariables, isStyleSheet, getNode, getInnerRange } from './util';
14-
import { DocumentStreamReader } from './bufferStream';
15-
16-
const field = (index, placeholder) => `\${${index}${placeholder ? ':' + placeholder : ''}}`;
11+
import { getSyntax } from './util';
12+
import { getExpandOptions, extractAbbreviation, isStyleSheet, getNode, getInnerRange } from './emmetForVSCode/emmetUtils';
13+
import { DocumentStreamReader } from './emmetForVSCode/bufferStream';
1714

1815
export function wrapWithAbbreviation() {
1916
let editor = vscode.window.activeTextEditor;
@@ -45,89 +42,37 @@ export function expandAbbreviation(args) {
4542
if (typeof args !== 'object' || !args['syntax']) {
4643
return;
4744
}
48-
let output = expandAbbreviationHelper(args['syntax'], editor.document, editor.selection);
49-
if (output) {
50-
editor.insertSnippet(new vscode.SnippetString(output.expandedText), output.abbreviationRange);
51-
}
52-
}
53-
54-
export interface ExpandAbbreviationHelperOutput {
55-
expandedText: string;
56-
abbreviationRange: vscode.Range;
57-
abbreviation: string;
58-
syntax: string;
59-
}
60-
61-
/**
62-
* Expands abbreviation at given range in the given document
63-
* @param syntax string syntax to be used for expanding abbreviations
64-
* @param document vscode.TextDocument
65-
* @param abbreviationRange vscode.Range range of the abbreviation that needs to be expanded
66-
* */
67-
export function expandAbbreviationHelper(syntax: string, document: vscode.TextDocument, abbreviationRange: vscode.Range): ExpandAbbreviationHelperOutput {
68-
if (!syntax) {
69-
return;
70-
}
71-
let abbreviation = document.getText(abbreviationRange);
45+
let syntax = args['syntax'];
46+
let abbreviationRange: vscode.Range = editor.selection;
47+
let position = editor.selection.isReversed ? editor.selection.anchor : editor.selection.active;
48+
let abbreviation = editor.document.getText(abbreviationRange);
7249
if (abbreviationRange.isEmpty) {
73-
[abbreviationRange, abbreviation] = extractAbbreviation(document, abbreviationRange.start);
50+
[abbreviationRange, abbreviation] = extractAbbreviation(editor.document, position);
7451
}
7552

76-
let expandedText = expand(abbreviation, getExpandOptions(syntax));
77-
return { expandedText, abbreviationRange, abbreviation, syntax };
78-
}
79-
80-
/**
81-
* Checks whether given position is valid for emmet abbreviation and returns appropriate syntax
82-
* @param syntax string language mode of current document
83-
* @param document vscode.Textdocument
84-
* @param position vscode.Position position of the abbreviation that needs to be expanded
85-
*/
86-
export function syntaxHelper(syntax: string, document: vscode.TextDocument, position: vscode.Position): string {
8753
let parseContent = isStyleSheet(syntax) ? parseStylesheet : parse;
88-
let rootNode: Node = parseContent(new DocumentStreamReader(document));
54+
let rootNode: Node = parseContent(new DocumentStreamReader(editor.document));
8955
let currentNode = getNode(rootNode, position);
9056

91-
if (forceCssSyntax(syntax, currentNode, position)) {
92-
return 'css';
93-
} else if (!isValidLocationForEmmetAbbreviation(currentNode, syntax, position)) {
57+
if (!isValidLocationForEmmetAbbreviation(currentNode, syntax, position)) {
9458
return;
9559
}
96-
return syntax;
97-
}
9860

99-
/**
100-
* Extracts abbreviation from the given position in the given document
101-
*/
102-
function extractAbbreviation(document: vscode.TextDocument, position: vscode.Position): [vscode.Range, string] {
103-
let currentLine = document.lineAt(position.line).text;
104-
let result = extract(currentLine, position.character, true);
105-
if (!result) {
106-
return [null, ''];
61+
let expandedText = expand(abbreviation, getExpandOptions(syntax));
62+
if (expandedText) {
63+
editor.insertSnippet(new vscode.SnippetString(expandedText), abbreviationRange);
10764
}
108-
109-
let rangeToReplace = new vscode.Range(position.line, result.location, position.line, result.location + result.abbreviation.length);
110-
return [rangeToReplace, result.abbreviation];
11165
}
11266

113-
/**
114-
* Inside <style> tag, force use of css abbreviations
115-
*/
116-
function forceCssSyntax(syntax: string, currentNode: Node, position: vscode.Position): boolean {
117-
return !isStyleSheet(syntax)
118-
&& currentNode
119-
&& currentNode.close
120-
&& currentNode.name === 'style'
121-
&& getInnerRange(currentNode).contains(position);
122-
}
12367

12468
/**
125-
* Checks if given position is a valid location to expand emmet abbreviation
69+
* Checks if given position is a valid location to expand emmet abbreviation.
70+
* Works only on html and css/less/scss syntax
12671
* @param currentNode parsed node at given position
12772
* @param syntax syntax of the abbreviation
12873
* @param position position to validate
12974
*/
130-
function isValidLocationForEmmetAbbreviation(currentNode: Node, syntax: string, position: vscode.Position): boolean {
75+
export function isValidLocationForEmmetAbbreviation(currentNode: Node, syntax: string, position: vscode.Position): boolean {
13176
if (!currentNode) {
13277
return true;
13378
}
@@ -142,15 +87,4 @@ function isValidLocationForEmmetAbbreviation(currentNode: Node, syntax: string,
14287
}
14388

14489
return false;
145-
}
146-
147-
export function getExpandOptions(syntax: string, textToReplace?: string) {
148-
return {
149-
field: field,
150-
syntax: syntax,
151-
profile: getProfile(syntax),
152-
addons: syntax === 'jsx' ? { 'jsx': true } : null,
153-
variables: getVariables(),
154-
text: textToReplace ? textToReplace : ''
155-
};
15690
}

extensions/emmet/src/balance.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as vscode from 'vscode';
7-
import { getNode, isStyleSheet } from './util';
87
import parse from '@emmetio/html-matcher';
98
import Node from '@emmetio/node';
10-
import { DocumentStreamReader } from './bufferStream';
9+
import { DocumentStreamReader } from './emmetForVSCode/bufferStream';
10+
import { getNode, isStyleSheet } from './emmetForVSCode/emmetUtils';
1111

1212
export function balanceOut() {
1313
balance(true);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import parseStylesheet from '@emmetio/css-parser';
8+
import parse from '@emmetio/html-matcher';
9+
import Node from '@emmetio/node';
10+
import { DocumentStreamReader } from './emmetForVSCode/bufferStream';
11+
import { isStyleSheet, getNode, getInnerRange } from './emmetForVSCode/emmetUtils';
12+
import { EmmetCompletionItemProvider } from './emmetForVSCode/emmetCompletionProvider';
13+
import { isValidLocationForEmmetAbbreviation } from './abbreviationActions';
14+
import { getSyntax } from './util';
15+
16+
export class DefaultCompletionItemProvider implements vscode.CompletionItemProvider {
17+
private _mappedSyntax = false;
18+
constructor(mappedSyntax?: boolean) {
19+
if (mappedSyntax) {
20+
this._mappedSyntax = mappedSyntax;
21+
}
22+
}
23+
public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable<vscode.CompletionList> {
24+
let syntax = getSyntax(document);
25+
if (!this._mappedSyntax) {
26+
syntax = this.syntaxHelper(syntax, document, position);
27+
}
28+
if (!syntax) {
29+
return;
30+
}
31+
32+
const emmetCompletionProvider = new EmmetCompletionItemProvider(syntax);
33+
return emmetCompletionProvider.provideCompletionItems(document, position, token);
34+
}
35+
36+
/**
37+
* Checks whether given position is valid for emmet abbreviation and returns appropriate syntax
38+
* @param syntax string language mode of current document
39+
* @param document vscode.Textdocument
40+
* @param position vscode.Position position of the abbreviation that needs to be expanded
41+
*/
42+
private syntaxHelper(syntax: string, document: vscode.TextDocument, position: vscode.Position): string {
43+
let parseContent = isStyleSheet(syntax) ? parseStylesheet : parse;
44+
let rootNode: Node = parseContent(new DocumentStreamReader(document));
45+
let currentNode = getNode(rootNode, position);
46+
47+
if (!isStyleSheet(syntax)
48+
&& currentNode
49+
&& currentNode.close
50+
&& currentNode.name === 'style'
51+
&& getInnerRange(currentNode).contains(position)) {
52+
return 'css';
53+
}
54+
55+
if (!isValidLocationForEmmetAbbreviation(currentNode, syntax, position)) {
56+
return;
57+
}
58+
return syntax;
59+
}
60+
61+
62+
63+
64+
}
File renamed without changes.

extensions/emmet/src/emmetCompletionProvider.ts renamed to extensions/emmet/src/emmetForVSCode/emmetCompletionProvider.ts

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@
66

77
import * as vscode from 'vscode';
88
import { expand, createSnippetsRegistry } from '@emmetio/expand-abbreviation';
9-
import { getSyntax, isStyleSheet } from './util';
10-
import { syntaxHelper, expandAbbreviationHelper, ExpandAbbreviationHelperOutput, getExpandOptions } from './abbreviationActions';
9+
import { isStyleSheet, extractAbbreviation, getExpandOptions } from './emmetUtils';
1110

1211
const snippetKeyCache = new Map<string, string[]>();
1312

1413
export class EmmetCompletionItemProvider implements vscode.CompletionItemProvider {
15-
private _mappedSyntax = false;
16-
constructor(mappedSyntax?: boolean) {
17-
if (mappedSyntax) {
18-
this._mappedSyntax = true;
14+
private _syntax: string;
15+
constructor(syntax: string) {
16+
if (syntax) {
17+
this._syntax = syntax;
1918
}
2019
}
2120
public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable<vscode.CompletionList> {
@@ -25,32 +24,26 @@ export class EmmetCompletionItemProvider implements vscode.CompletionItemProvide
2524
return Promise.resolve(null);
2625
}
2726

28-
let syntax = getSyntax(document);
29-
let abbreviationRange = new vscode.Range(position, position);
30-
if (!this._mappedSyntax) {
31-
syntax = syntaxHelper(syntax, document, position);
32-
}
27+
let [abbreviationRange, abbreviation] = extractAbbreviation(document, position);
28+
let expandedText = expand(abbreviation, getExpandOptions(this._syntax));
3329

34-
let output: ExpandAbbreviationHelperOutput = expandAbbreviationHelper(syntax, document, abbreviationRange);
35-
if (!output) {
30+
if (!expandedText) {
3631
return;
3732
}
3833

39-
let expandedAbbr = new vscode.CompletionItem(output.abbreviation);
40-
expandedAbbr.insertText = new vscode.SnippetString(output.expandedText);
41-
expandedAbbr.documentation = removeTabStops(output.expandedText);
42-
expandedAbbr.range = output.abbreviationRange;
34+
let expandedAbbr = new vscode.CompletionItem(abbreviation);
35+
expandedAbbr.insertText = new vscode.SnippetString(expandedText);
36+
expandedAbbr.documentation = this.removeTabStops(expandedText);
37+
expandedAbbr.range = abbreviationRange;
4338
expandedAbbr.detail = 'Emmet Abbreviation';
4439

45-
// Workaround for completion items appearing out of order
40+
// Workaround for the main expanded abbr not appearing before the snippet suggestions
4641
expandedAbbr.sortText = '0' + expandedAbbr.label;
4742

48-
syntax = output.syntax;
49-
5043
let completionItems: vscode.CompletionItem[] = expandedAbbr ? [expandedAbbr] : [];
51-
if (!isStyleSheet(syntax)) {
52-
let currentWord = getCurrentWord(document, position);
53-
let abbreviationSuggestions = this.getAbbreviationSuggestions(syntax, currentWord, output.abbreviation, output.abbreviationRange);
44+
if (!isStyleSheet(this._syntax)) {
45+
let currentWord = this.getCurrentWord(document, position);
46+
let abbreviationSuggestions = this.getAbbreviationSuggestions(this._syntax, currentWord, abbreviation, abbreviationRange);
5447
completionItems = completionItems.concat(abbreviationSuggestions);
5548
}
5649
return Promise.resolve(new vscode.CompletionList(completionItems, true));
@@ -81,15 +74,15 @@ export class EmmetCompletionItemProvider implements vscode.CompletionItemProvide
8174
let expandedAbbr = expand(currentAbbr, getExpandOptions(syntax));
8275

8376
let item = new vscode.CompletionItem(snippetKey);
84-
item.documentation = removeTabStops(expandedAbbr);
77+
item.documentation = this.removeTabStops(expandedAbbr);
8578
item.detail = 'Emmet Abbreviation';
8679
item.insertText = new vscode.SnippetString(expandedAbbr);
8780
item.range = abbreviationRange;
8881

89-
// Workaround for completion items getting filtered out
82+
// Workaround for snippet suggestions items getting filtered out as the complete abbr does not start with snippetKey
9083
item.filterText = abbreviation;
9184

92-
// Workaround for completion items appearing in wrong order
85+
// Workaround for the main expanded abbr not appearing before the snippet suggestions
9386
item.sortText = '9' + abbreviation;
9487

9588
snippetCompletions.push(item);
@@ -99,24 +92,26 @@ export class EmmetCompletionItemProvider implements vscode.CompletionItemProvide
9992

10093
}
10194

102-
}
103-
95+
private getCurrentWord(document: vscode.TextDocument, position: vscode.Position): string {
96+
let wordAtPosition = document.getWordRangeAtPosition(position);
97+
let currentWord = '';
98+
if (wordAtPosition && wordAtPosition.start.character < position.character) {
99+
let word = document.getText(wordAtPosition);
100+
currentWord = word.substr(0, position.character - wordAtPosition.start.character);
101+
}
104102

103+
return currentWord;
104+
}
105105

106-
function getCurrentWord(document: vscode.TextDocument, position: vscode.Position): string {
107-
let wordAtPosition = document.getWordRangeAtPosition(position);
108-
let currentWord = '';
109-
if (wordAtPosition && wordAtPosition.start.character < position.character) {
110-
let word = document.getText(wordAtPosition);
111-
currentWord = word.substr(0, position.character - wordAtPosition.start.character);
106+
private removeTabStops(expandedWord: string): string {
107+
return expandedWord.replace(/\$\{\d+\}/g, '').replace(/\$\{\d+:([^\}]+)\}/g, '$1');
112108
}
113109

114-
return currentWord;
115110
}
116111

117-
function removeTabStops(expandedWord: string): string {
118-
return expandedWord.replace(/\$\{\d+\}/g, '').replace(/\$\{\d+:([^\}]+)\}/g, '$1');
119-
}
112+
113+
114+
120115

121116

122117

0 commit comments

Comments
 (0)