Skip to content

Commit 32dca92

Browse files
author
Brad Cornes
committed
add emmet-style completions
1 parent 3b50a44 commit 32dca92

File tree

14 files changed

+1098
-38
lines changed

14 files changed

+1098
-38
lines changed

packages/emmet-helper/package-lock.json

Lines changed: 799 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/emmet-helper/package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "emmet-helper",
3+
"version": "0.0.1",
4+
"main": "dist/index.js",
5+
"scripts": {
6+
"build": "ncc build src/index.js -o dist --minify && replace-in-files --string='e(\"./format\")' --string='e(\"./edit\")' --replacement='undefined' dist/index.js"
7+
},
8+
"devDependencies": {
9+
"@zeit/ncc": "^0.22.1",
10+
"replace-in-files-cli": "^0.3.1",
11+
"vscode-emmet-helper": "^1.2.17"
12+
}
13+
}

packages/emmet-helper/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from 'vscode-emmet-helper'

packages/tailwindcss-language-server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@zeit/ncc": "^0.22.0",
2222
"css.escape": "^1.5.1",
2323
"dlv": "^1.1.3",
24+
"emmet-helper": "0.0.1",
2425
"glob-exec": "^0.1.1",
2526
"tailwindcss-class-names": "0.0.1",
2627
"typescript": "^3.8.3",

packages/tailwindcss-language-server/src/providers/completionProvider.ts

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import { isCssContext } from '../util/css'
1515
import { findLast, findJsxStrings, arrFindLast } from '../util/find'
1616
import { stringifyConfigValue, stringifyCss } from '../util/stringify'
1717
import isObject from '../util/isObject'
18+
import * as emmetHelper from 'emmet-helper'
19+
import { isValidLocationForEmmetAbbreviation } from '../util/isValidLocationForEmmetAbbreviation'
20+
import { getDocumentSettings } from '../util/getDocumentSettings'
21+
import { isJsContext } from '../util/js'
1822

1923
function completionsFromClassList(
2024
state: State,
@@ -174,7 +178,10 @@ function provideClassNameCompletions(
174178
): CompletionList {
175179
let doc = state.editor.documents.get(params.textDocument.uri)
176180

177-
if (isHtmlContext(doc, params.position)) {
181+
if (
182+
isHtmlContext(doc, params.position) ||
183+
isJsContext(doc, params.position)
184+
) {
178185
return provideClassAttributeCompletions(state, params)
179186
}
180187

@@ -490,19 +497,88 @@ function provideCssDirectiveCompletions(
490497
}
491498
}
492499

493-
export function provideCompletions(
500+
async function provideEmmetCompletions(
501+
state: State,
502+
{ position, textDocument }: CompletionParams
503+
): Promise<CompletionList> {
504+
let settings = await getDocumentSettings(state, textDocument.uri)
505+
if (settings.emmetCompletions !== true) return null
506+
507+
let doc = state.editor.documents.get(textDocument.uri)
508+
509+
const syntax = isHtmlContext(doc, position)
510+
? 'html'
511+
: isJsContext(doc, position)
512+
? 'jsx'
513+
: null
514+
515+
if (syntax === null) {
516+
return null
517+
}
518+
519+
const extractAbbreviationResults = emmetHelper.extractAbbreviation(
520+
doc,
521+
position,
522+
true
523+
)
524+
if (
525+
!extractAbbreviationResults ||
526+
!emmetHelper.isAbbreviationValid(
527+
syntax,
528+
extractAbbreviationResults.abbreviation
529+
)
530+
) {
531+
return null
532+
}
533+
534+
if (
535+
!isValidLocationForEmmetAbbreviation(
536+
doc,
537+
extractAbbreviationResults.abbreviationRange
538+
)
539+
) {
540+
return null
541+
}
542+
543+
const emmetItems = emmetHelper.doComplete(doc, position, syntax, {})
544+
545+
if (!emmetItems || !emmetItems.items || emmetItems.items.length !== 1) {
546+
return null
547+
}
548+
549+
// https://github.com/microsoft/vscode/issues/86941
550+
if (emmetItems.items[0].label === 'widows: ;') {
551+
return null
552+
}
553+
554+
const parts = emmetItems.items[0].label.split('.')
555+
if (parts.length < 2) return null
556+
557+
return completionsFromClassList(state, parts[parts.length - 1], {
558+
start: {
559+
line: position.line,
560+
character: position.character - parts[parts.length - 1].length,
561+
},
562+
end: position,
563+
})
564+
}
565+
566+
export async function provideCompletions(
494567
state: State,
495568
params: CompletionParams
496-
): CompletionList {
569+
): Promise<CompletionList> {
497570
if (state === null) return { items: [], isIncomplete: false }
498571

499-
return (
572+
const result =
500573
provideClassNameCompletions(state, params) ||
501574
provideCssHelperCompletions(state, params) ||
502575
provideCssDirectiveCompletions(state, params) ||
503576
provideScreenDirectiveCompletions(state, params) ||
504577
provideVariantsDirectiveCompletions(state, params)
505-
)
578+
579+
if (result) return result
580+
581+
return provideEmmetCompletions(state, params)
506582
}
507583

508584
export function resolveCompletionItem(

packages/tailwindcss-language-server/src/providers/hoverProvider.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { stringifyCss, stringifyConfigValue } from '../util/stringify'
88
const dlv = require('dlv')
99
import { isHtmlContext } from '../util/html'
1010
import { isCssContext } from '../util/css'
11+
import { isJsContext } from '../util/js'
1112

1213
export function provideHover(
1314
state: State,
@@ -78,7 +79,7 @@ function provideClassNameHover(
7879
): Hover {
7980
let doc = state.editor.documents.get(textDocument.uri)
8081

81-
if (!isHtmlContext(doc, position)) return null
82+
if (!isHtmlContext(doc, position) && !isJsContext(doc, position)) return null
8283

8384
let hovered = getClassNameAtPosition(doc, position)
8485
if (!hovered) return null

packages/tailwindcss-language-server/src/server.ts

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,32 @@ import {
1515
CompletionList,
1616
Hover,
1717
TextDocumentPositionParams,
18+
DidChangeConfigurationNotification,
1819
} from 'vscode-languageserver'
1920
import getTailwindState from 'tailwindcss-class-names'
20-
import { State } from './util/state'
21+
import { State, Settings } from './util/state'
2122
import {
2223
provideCompletions,
2324
resolveCompletionItem,
2425
} from './providers/completionProvider'
2526
import { provideHover } from './providers/hoverProvider'
2627
import { URI } from 'vscode-uri'
28+
import { getDocumentSettings } from './util/getDocumentSettings'
2729

2830
let state: State = null
2931
let connection = createConnection(ProposedFeatures.all)
3032
let documents = new TextDocuments()
3133
let workspaceFolder: string | null
3234

35+
const defaultSettings: Settings = { emmetCompletions: false }
36+
let globalSettings: Settings = defaultSettings
37+
let documentSettings: Map<string, Settings> = new Map()
38+
3339
documents.onDidOpen((event) => {
34-
connection.console.log(
35-
`[Server(${process.pid}) ${workspaceFolder}] Document opened: ${event.document.uri}`
36-
)
40+
getDocumentSettings(state, event.document.uri)
41+
})
42+
documents.onDidClose((event) => {
43+
documentSettings.delete(event.document.uri)
3744
})
3845
documents.listen(connection)
3946

@@ -52,7 +59,16 @@ connection.onInitialize(
5259
},
5360
}
5461
)
55-
state.editor = { connection, documents }
62+
63+
const capabilities = params.capabilities
64+
65+
state.editor = {
66+
connection,
67+
documents,
68+
documentSettings,
69+
globalSettings,
70+
capabilities: { configuration: capabilities.workspace && !!capabilities.workspace.configuration },
71+
}
5672

5773
return {
5874
capabilities: {
@@ -73,15 +89,37 @@ connection.onInitialize(
7389

7490
connection.onInitialized &&
7591
connection.onInitialized(async () => {
92+
if (state.editor.capabilities.configuration) {
93+
connection.client.register(
94+
DidChangeConfigurationNotification.type,
95+
undefined
96+
)
97+
}
98+
7699
connection.sendNotification('tailwindcss/configUpdated', [
77100
state.dependencies[0],
78101
state.config,
79102
state.plugins,
80103
])
81104
})
82105

106+
connection.onDidChangeConfiguration((change) => {
107+
if (state.editor.capabilities.configuration) {
108+
// Reset all cached document settings
109+
state.editor.documentSettings.clear()
110+
} else {
111+
state.editor.globalSettings = <Settings>(
112+
(change.settings.tailwindCSS || defaultSettings)
113+
)
114+
}
115+
116+
state.editor.documents
117+
.all()
118+
.forEach((doc) => getDocumentSettings(state, doc.uri))
119+
})
120+
83121
connection.onCompletion(
84-
(params: CompletionParams): CompletionList => {
122+
(params: CompletionParams): Promise<CompletionList> => {
85123
return provideCompletions(state, params)
86124
}
87125
)

packages/tailwindcss-language-server/src/util/css.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { TextDocument, Position } from 'vscode-languageserver'
2-
import { isMixedDoc, isInsideTag } from './html'
2+
import { isInsideTag, isVueDoc, isSvelteDoc } from './html'
33

44
export const CSS_LANGUAGES = [
55
'css',
@@ -19,7 +19,7 @@ export function isCssContext(doc: TextDocument, position: Position): boolean {
1919
return true
2020
}
2121

22-
if (isMixedDoc(doc)) {
22+
if (isVueDoc(doc) || isSvelteDoc(doc)) {
2323
let str = doc.getText({
2424
start: { line: 0, character: 0 },
2525
end: position,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { State, Settings } from './state'
2+
3+
export async function getDocumentSettings(
4+
state: State,
5+
resource: string
6+
): Promise<Settings> {
7+
if (!state.editor.capabilities.configuration) {
8+
return Promise.resolve(state.editor.globalSettings)
9+
}
10+
let result = state.editor.documentSettings.get(resource)
11+
if (!result) {
12+
result = await state.editor.connection.workspace.getConfiguration({
13+
scopeUri: resource,
14+
section: 'tailwindCSS',
15+
})
16+
state.editor.documentSettings.set(resource, result)
17+
}
18+
return result
19+
}

packages/tailwindcss-language-server/src/util/html.ts

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { TextDocument, Position } from 'vscode-languageserver'
2-
import { JS_LANGUAGES } from './js'
32

43
export const HTML_LANGUAGES = [
54
'blade',
@@ -20,43 +19,36 @@ export const HTML_LANGUAGES = [
2019
'razor',
2120
'slim',
2221
'twig',
23-
...JS_LANGUAGES,
2422
]
2523

26-
function isHtmlDoc(doc: TextDocument): boolean {
24+
export function isHtmlDoc(doc: TextDocument): boolean {
2725
return HTML_LANGUAGES.indexOf(doc.languageId) !== -1
2826
}
2927

30-
function isVueDoc(doc: TextDocument): boolean {
28+
export function isVueDoc(doc: TextDocument): boolean {
3129
return doc.languageId === 'vue'
3230
}
3331

34-
function isSvelteDoc(doc: TextDocument): boolean {
32+
export function isSvelteDoc(doc: TextDocument): boolean {
3533
return doc.languageId === 'svelte'
3634
}
3735

38-
export function isMixedDoc(doc: TextDocument): boolean {
39-
return isVueDoc(doc) || isSvelteDoc(doc)
40-
}
41-
4236
export function isHtmlContext(doc: TextDocument, position: Position): boolean {
43-
if (isHtmlDoc(doc)) {
37+
let str = doc.getText({
38+
start: { line: 0, character: 0 },
39+
end: position,
40+
})
41+
42+
if (isHtmlDoc(doc) && !isInsideTag(str, ['script', 'style'])) {
4443
return true
4544
}
4645

47-
if (isMixedDoc(doc)) {
48-
let str = doc.getText({
49-
start: { line: 0, character: 0 },
50-
end: position,
51-
})
52-
53-
if (isVueDoc(doc)) {
54-
return isInsideTag(str, ['template', 'script'])
55-
}
46+
if (isVueDoc(doc)) {
47+
return isInsideTag(str, ['template'])
48+
}
5649

57-
if (isSvelteDoc(doc)) {
58-
return !isInsideTag(str, ['style'])
59-
}
50+
if (isSvelteDoc(doc)) {
51+
return !isInsideTag(str, ['script', 'style'])
6052
}
6153

6254
return false

0 commit comments

Comments
 (0)