tailwind-ctp-intellisense @master -
refs -
log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
use lexer for class attribute completions
6 changed files, 113 additions(+), 78 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index d3461d15178fbd81895623d655bde63ebf413264..f844afb78a3c31adf7b940746d88f9e90ad99f41 100755
--- a/package-lock.json
+++ b/package-lock.json
@@ -1033,6 +1033,12 @@ "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.1.tgz",
"integrity": "sha512-dOrgprHnkDaj1pmrwdcMAf0QRNQzqTB5rxJph+iIQshSmIvtgRqJ0nim8u1vvXU8iOXZrH96+M46JDFTPLingA==",
"dev": true
},
+ "@types/moo": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/@types/moo/-/moo-0.5.3.tgz",
+ "integrity": "sha512-PJJ/jvb5Gor8DWvXN3e75njfQyYNRz0PaFSZ3br9GfHM9N2FxvuJ/E/ytcQePJOLzHlvgFSsIJIvfUMUxWTbnA==",
+ "dev": true
+ },
"@types/node": {
"version": "13.13.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.4.tgz",
@@ -4941,6 +4947,12 @@ "mkdirp": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz",
"integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==",
+ "dev": true
+ },
+ "moo": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz",
+ "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==",
"dev": true
},
"ms": {
diff --git a/package.json b/package.json
index 69d62d97f70770c055be6a9d69bc98fb4d3f295a..139370c58cbe44bd4b75ede982513933acf9c4be 100755
--- a/package.json
+++ b/package.json
@@ -75,6 +75,7 @@ },
"devDependencies": {
"@ctrl/tinycolor": "^3.1.0",
"@types/mocha": "^5.2.0",
+ "@types/moo": "^0.5.3",
"@types/node": "^13.9.3",
"@types/vscode": "^1.32.0",
"@zeit/ncc": "^0.22.0",
@@ -93,6 +94,7 @@ "jest": "^25.5.4",
"line-column": "^1.0.2",
"mitt": "^1.2.0",
"mkdirp": "^1.0.3",
+ "moo": "^0.5.1",
"pkg-up": "^3.1.0",
"postcss": "^7.0.27",
"postcss-selector-parser": "^6.0.2",
diff --git a/src/lsp/providers/completionProvider.ts b/src/lsp/providers/completionProvider.ts
index 8f2c43d8325bc847a7b05a87dc180025a647f2a9..eae7546bffc68d70c05ef1ebbdd0353fac33a42d 100644
--- a/src/lsp/providers/completionProvider.ts
+++ b/src/lsp/providers/completionProvider.ts
@@ -12,7 +12,7 @@ import removeMeta from '../util/removeMeta'
import { getColor, getColorFromValue } from '../util/color'
import { isHtmlContext } from '../util/html'
import { isCssContext } from '../util/css'
-import { findLast, findJsxStrings, arrFindLast } from '../util/find'
+import { findLast } from '../util/find'
import { stringifyConfigValue, stringifyCss } from '../util/stringify'
import { stringifyScreen, Screen } from '../util/screens'
import isObject from '../../util/isObject'
@@ -23,6 +23,11 @@ import { isJsContext } from '../util/js'
import { naturalExpand } from '../util/naturalExpand'
import semver from 'semver'
import { docsUrl } from '../util/docsUrl'
+import { ensureArray } from '../../util/array'
+import {
+ getClassAttributeLexer,
+ getComputedClassAttributeLexer,
+ CompletionItemKind,
import { ensureArray } from '../../util/array'
function completionsFromClassList(
@@ -122,35 +127,41 @@ start: { line: Math.max(position.line - 10, 0), character: 0 },
end: position,
})
- const match = findLast(/\bclass(?:Name)?=(?<initial>['"`{])/gi, str)
+ const match = findLast(/[\s:]class(?:Name)?=['"`{]/gi, str)
if (match === null) {
return null
}
-import { State } from '../util/state'
+ const lexer =
+ match[0][0] === ':'
+ ? getComputedClassAttributeLexer()
+ start: {
import { State } from '../util/state'
CompletionItemKind,
+ filter?: (item: CompletionItem) => boolean
-import removeMeta from '../util/removeMeta'
+ try {
+ let tokens = Array.from(lexer)
+ start: {
CompletionParams,
-import removeMeta from '../util/removeMeta'
+ start: {
Range,
-import removeMeta from '../util/removeMeta'
+ start: {
MarkupKind,
-import removeMeta from '../util/removeMeta'
+ start: {
CompletionList,
-import removeMeta from '../util/removeMeta'
+ start: {
} from 'vscode-languageserver'
-import { getColor, getColorFromValue } from '../util/color'
+ classList = tokens[i].value + classList
+ CompletionList,
-import { getColor, getColorFromValue } from '../util/color'
+ ...classListRange.start,
import { State } from '../util/state'
- const classList = str.substr(
+ }
-import { getColor, getColorFromValue } from '../util/color'
+ Range,
CompletionItem,
-import { State } from '../util/state'
import {
- CompletionItemKind,
+ MarkupKind,
return completionsFromClassList(state, classList, {
start: {
line: position.line,
@@ -159,24 +169,13 @@ },
end: position,
})
}
- return null
CompletionItemKind,
- MarkupKind,
-
- if (rest.indexOf(match.groups.initial) !== -1) {
- return null
CompletionItemKind,
- MarkupKind,
+import {
import { State } from '../util/state'
- classList: string,
- start: {
- line: position.line,
- character: position.character - rest.length,
- CompletionItemKind,
Range,
- end: position,
- })
+ MarkupKind,
}
function provideAtApplyCompletions(
diff --git a/src/lsp/util/find.ts b/src/lsp/util/find.ts
index 3fb04517624e5208d2b2c4f2152e3285e1bb9d8a..bd770aa37cce04fb856e76bf21b272c6c8c4f07f 100644
--- a/src/lsp/util/find.ts
+++ b/src/lsp/util/find.ts
@@ -19,57 +19,6 @@ }
return matches[matches.length - 1]
}
-export function arrFindLast<T>(arr: T[], predicate: (item: T) => boolean): T {
- for (let i = arr.length - 1; i >= 0; --i) {
- const x = arr[i]
- if (predicate(x)) {
- return x
- }
- }
- return null
-}
-
-enum Quote {
- SINGLE = "'",
- DOUBLE = '"',
- TICK = '`',
-}
-type StringInfo = {
- start: number
- end?: number
- char: Quote
-}
-
-export function findJsxStrings(str: string): StringInfo[] {
- const chars = str.split('')
- const strings: StringInfo[] = []
- let bracketCount = 0
- for (let i = 0; i < chars.length; i++) {
- const char = chars[i]
- if (char === '{') {
- bracketCount += 1
- } else if (char === '}') {
- bracketCount -= 1
- } else if (
- char === Quote.SINGLE ||
- char === Quote.DOUBLE ||
- char === Quote.TICK
- ) {
- let open = arrFindLast(strings, (string) => string.char === char)
- if (strings.length === 0 || !open || (open && open.end)) {
- strings.push({ start: i + 1, char })
- } else {
- open.end = i
- }
- }
- if (i !== 0 && bracketCount === 0) {
- // end
- break
- }
- }
- return strings
-}
-
export function findClassNamesInRange(
doc: TextDocument,
range: Range
diff --git a/src/lsp/util/lazy.ts b/src/lsp/util/lazy.ts
new file mode 100644
index 0000000000000000000000000000000000000000..858dac58c1fcc51d0bddc53027a47816db0e942e
--- /dev/null
+++ b/src/lsp/util/lazy.ts
@@ -0,0 +1,19 @@
+// https://www.codementor.io/@agustinchiappeberrini/lazy-evaluation-and-javascript-a5m7g8gs3
+
+export interface Lazy<T> {
+ (): T
+ isLazy: boolean
+}
+
+export const lazy = <T>(getter: () => T): Lazy<T> => {
+ let evaluated: boolean = false
+ let _res: T = null
+ const res = <Lazy<T>>function (): T {
+ if (evaluated) return _res
+ _res = getter.apply(this, arguments)
+ evaluated = true
+ return _res
+ }
+ res.isLazy = true
+ return res
+}
diff --git a/src/lsp/util/lexers.ts b/src/lsp/util/lexers.ts
new file mode 100644
index 0000000000000000000000000000000000000000..65197b943a3ef18cb7b5ab1435c10235bca67bbe
--- /dev/null
+++ b/src/lsp/util/lexers.ts
@@ -0,0 +1,53 @@
+import moo from 'moo'
+import { lazy } from './lazy'
+
+const classAttributeStates: { [x: string]: moo.Rules } = {
+ doubleClassList: {
+ lbrace: { match: /(?<!\\)\{/, push: 'interp' },
+ rbrace: { match: /(?<!\\)\}/, pop: 1 },
+ end: { match: /(?<!\\)"/, pop: 1 },
+ classlist: { match: /[\s\S]/, lineBreaks: true },
+ },
+ singleClassList: {
+ lbrace: { match: /(?<!\\)\{/, push: 'interp' },
+ rbrace: { match: /(?<!\\)\}/, pop: 1 },
+ end: { match: /(?<!\\)'/, pop: 1 },
+ classlist: { match: /[\s\S]/, lineBreaks: true },
+ },
+ tickClassList: {
+ lbrace: { match: /(?<=(?<!\\)\$)\{/, push: 'interp' },
+ rbrace: { match: /(?<!\\)\}/, pop: 1 },
+ end: { match: /(?<!\\)`/, pop: 1 },
+ classlist: { match: /[\s\S]/, lineBreaks: true },
+ },
+ interp: {
+ startSingle: { match: /(?<!\\)'/, push: 'singleClassList' },
+ startDouble: { match: /(?<!\\)"/, push: 'doubleClassList' },
+ startTick: { match: /(?<!\\)`/, push: 'tickClassList' },
+ lbrace: { match: /(?<!\\)\{/, push: 'interp' },
+ rbrace: { match: /(?<!\\)\}/, pop: 1 },
+ text: { match: /[\s\S]/, lineBreaks: true },
+ },
+}
+
+export const getClassAttributeLexer = lazy(() =>
+ moo.states({
+ main: {
+ start1: { match: '"', push: 'doubleClassList' },
+ start2: { match: "'", push: 'singleClassList' },
+ start3: { match: '{', push: 'interp' },
+ },
+ ...classAttributeStates,
+ })
+)
+
+export const getComputedClassAttributeLexer = lazy(() =>
+ moo.states({
+ main: {
+ quote: { match: /['"{]/, push: 'interp' },
+ },
+ // TODO: really this should use a different interp definition that is
+ // terminated correctly based on the initial quote type
+ ...classAttributeStates,
+ })
+)