Home

tailwind-ctp-intellisense @master - refs - log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
tree log patch
use lexer for class attribute completions
Brad Cornes <brad@parall.ax>
4 years ago
6 changed files, 113 additions(+), 78 deletions(-)
M package-lock.json -> package-lock.json
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": {
M package.json -> package.json
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",
M src/lsp/providers/completionProvider.ts -> src/lsp/providers/completionProvider.ts
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(
M src/lsp/util/find.ts -> src/lsp/util/find.ts
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
I src/lsp/util/lazy.ts
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
+}
I src/lsp/util/lexers.ts
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,
+  })
+)