Home

tailwind-ctp-intellisense @master - refs - log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
tree log patch
Add initial color decorators
Brad Cornes <bradlc41@gmail.com>
4 years ago
8 changed files, 300 additions(+), 1 deletions(-)
package.jsonsrc/extension.tssrc/lib/registerColorDecorator.tssrc/lsp/providers/documentColorProvider.tssrc/lsp/server.tssrc/lsp/util/find.tssrc/lsp/util/resolveRange.tssrc/lsp/util/state.ts
M package.jsonpackage.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
diff --git a/package.json b/package.json
index 0cdb709c6539c495c4114edaaf2222479b237252..13c2d39a2339a7689dcc9d6977af087000115b06 100755
--- a/package.json
+++ b/package.json
@@ -71,6 +71,21 @@           },
           "default": {},
           "markdownDescription": "Enable features in languages that are not supported by default. Add a mapping here between the new language and an already supported language.\n E.g.: `{\"plaintext\": \"html\"}`"
         },
+        "tailwindCSS.colorDecorators.enabled": {
+          "type": "boolean",
+          "default": true,
+          "scope": "language-overridable"
+        },
+        "tailwindCSS.colorDecorators.classes": {
+          "type": "boolean",
+          "default": true,
+          "scope": "language-overridable"
+        },
+        "tailwindCSS.colorDecorators.cssHelpers": {
+          "type": "boolean",
+          "default": true,
+          "scope": "language-overridable"
+        },
         "tailwindCSS.validate": {
           "type": "boolean",
           "default": true,
M src/extension.tssrc/extension.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
diff --git a/src/extension.ts b/src/extension.ts
index 279ec969590ba1e30120474c179722fa450c238d..fe982e92a2882d37e61976a3f1d2733cee6e01e2 100755
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -24,6 +24,7 @@ import isObject from './util/isObject'
 import { dedupe, equal } from './util/array'
 import { createEmitter } from './lib/emitter'
 import { onMessage } from './lsp/notifications'
+import { registerColorDecorator } from './lib/registerColorDecorator'
 
 const CLIENT_ID = 'tailwindcss-intellisense'
 const CLIENT_NAME = 'Tailwind CSS IntelliSense'
@@ -152,6 +153,7 @@ 
     client.onReady().then(() => {
       let emitter = createEmitter(client)
       registerConfigErrorHandler(emitter)
+      registerColorDecorator(client, context, emitter)
       onMessage(client, 'getConfiguration', async (scope) => {
         return Workspace.getConfiguration('tailwindCSS', scope)
       })
I src/lib/registerColorDecorator.ts
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
diff --git a/src/lib/registerColorDecorator.ts b/src/lib/registerColorDecorator.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cb01a7bb366da75bd8453c6f90d5a79b312f4daa
--- /dev/null
+++ b/src/lib/registerColorDecorator.ts
@@ -0,0 +1,125 @@
+import { window, workspace, ExtensionContext, TextEditor } from 'vscode'
+import { NotificationEmitter } from './emitter'
+import { LanguageClient } from 'vscode-languageclient'
+
+const colorDecorationType = window.createTextEditorDecorationType({
+  before: {
+    width: '0.8em',
+    height: '0.8em',
+    contentText: ' ',
+    border: '0.1em solid',
+    margin: '0.1em 0.2em 0',
+  },
+  dark: {
+    before: {
+      borderColor: '#eeeeee',
+    },
+  },
+  light: {
+    before: {
+      borderColor: '#000000',
+    },
+  },
+})
+
+export function registerColorDecorator(
+  client: LanguageClient,
+  context: ExtensionContext,
+  emitter: NotificationEmitter
+) {
+  let activeEditor = window.activeTextEditor
+  let timeout: NodeJS.Timer | undefined = undefined
+
+  async function updateDecorations() {
+    return updateDecorationsInEditor(activeEditor)
+  }
+
+  async function updateDecorationsInEditor(editor: TextEditor) {
+    if (!editor) return
+    if (editor.document.uri.scheme !== 'file') return
+
+    let workspaceFolder = workspace.getWorkspaceFolder(editor.document.uri)
+    if (
+      !workspaceFolder ||
+      workspaceFolder.uri.toString() !==
+        client.clientOptions.workspaceFolder.uri.toString()
+    ) {
+      return
+    }
+
+    let settings = workspace.getConfiguration(
+      'tailwindCSS.colorDecorators',
+      editor.document
+    )
+
+    if (settings.enabled !== true) {
+      editor.setDecorations(colorDecorationType, [])
+      return
+    }
+
+    let { colors } = await emitter.emit('getDocumentColors', {
+      document: editor.document.uri.toString(),
+      classes: settings.classes,
+      cssHelpers: settings.cssHelpers,
+    })
+
+    editor.setDecorations(
+      colorDecorationType,
+      colors
+        .filter(({ color }) => color !== 'rgba(0, 0, 0, 0.01)')
+        .map(({ range, color }) => ({
+          range,
+          renderOptions: { before: { backgroundColor: color } },
+        }))
+    )
+  }
+
+  function triggerUpdateDecorations() {
+    if (timeout) {
+      clearTimeout(timeout)
+      timeout = undefined
+    }
+    timeout = setTimeout(updateDecorations, 500)
+  }
+
+  if (activeEditor) {
+    triggerUpdateDecorations()
+  }
+
+  window.onDidChangeActiveTextEditor(
+    (editor) => {
+      activeEditor = editor
+      if (editor) {
+        triggerUpdateDecorations()
+      }
+    },
+    null,
+    context.subscriptions
+  )
+
+  workspace.onDidChangeTextDocument(
+    (event) => {
+      if (activeEditor && event.document === activeEditor.document) {
+        triggerUpdateDecorations()
+      }
+    },
+    null,
+    context.subscriptions
+  )
+
+  workspace.onDidOpenTextDocument(
+    (document) => {
+      if (activeEditor && document === activeEditor.document) {
+        triggerUpdateDecorations()
+      }
+    },
+    null,
+    context.subscriptions
+  )
+
+  workspace.onDidChangeConfiguration((e) => {
+    if (e.affectsConfiguration('tailwindCSS.colorDecorators')) {
+      window.visibleTextEditors.forEach(updateDecorationsInEditor)
+    }
+  })
+}
I src/lsp/providers/documentColorProvider.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
diff --git a/src/lsp/providers/documentColorProvider.ts b/src/lsp/providers/documentColorProvider.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5c9e9e34917301c3ba93b1cd70a8f4bf3ce6e9a9
--- /dev/null
+++ b/src/lsp/providers/documentColorProvider.ts
@@ -0,0 +1,63 @@
+import { onMessage } from '../notifications'
+import { State } from '../util/state'
+import {
+  findClassListsInDocument,
+  getClassNamesInClassList,
+  findHelperFunctionsInDocument,
+} from '../util/find'
+import { getClassNameParts } from '../util/getClassNameAtPosition'
+import { getColor, getColorFromValue } from '../util/color'
+import { logFull } from '../util/logFull'
+import { stringToPath } from '../util/stringToPath'
+const dlv = require('dlv')
+
+export function registerDocumentColorProvider(state: State) {
+  onMessage(
+    state.editor.connection,
+    'getDocumentColors',
+    async ({ document, classes, cssHelpers }) => {
+      let colors = []
+      let doc = state.editor.documents.get(document)
+      if (!doc) return { colors }
+
+      if (classes) {
+        let classLists = findClassListsInDocument(state, doc)
+        classLists.forEach((classList) => {
+          let classNames = getClassNamesInClassList(classList)
+          classNames.forEach((className) => {
+            let parts = getClassNameParts(state, className.className)
+            if (!parts) return
+            let color = getColor(state, parts)
+            if (!color) return
+            colors.push({ range: className.range, color: color.documentation })
+          })
+        })
+      }
+
+      if (cssHelpers) {
+        let helperFns = findHelperFunctionsInDocument(state, doc)
+        helperFns.forEach((fn) => {
+          let keys = stringToPath(fn.value)
+          let base = fn.helper === 'theme' ? ['theme'] : []
+          let value = dlv(state.config, [...base, ...keys])
+          let color = getColorFromValue(value)
+          if (color) {
+            // colors.push({
+            //   range: {
+            //     start: {
+            //       line: fn.valueRange.start.line,
+            //       character: fn.valueRange.start.character + 1,
+            //     },
+            //     end: fn.valueRange.end,
+            //   },
+            //   color,
+            // })
+            colors.push({ range: fn.valueRange, color })
+          }
+        })
+      }
+
+      return { colors }
+    }
+  )
+}
M src/lsp/server.tssrc/lsp/server.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
diff --git a/src/lsp/server.ts b/src/lsp/server.ts
index 4b149f82663e120586d51e1026c6f9f8eaf552f7..bd7cc22733c16d072d3935776c5c4d24c857855b 100644
--- a/src/lsp/server.ts
+++ b/src/lsp/server.ts
@@ -35,6 +35,7 @@   clearAllDiagnostics,
 } from './providers/diagnostics/diagnosticsProvider'
 import { createEmitter } from '../lib/emitter'
 import { provideCodeActions } from './providers/codeActions/codeActionProvider'
+import { registerDocumentColorProvider } from './providers/documentColorProvider'
 
 let connection = createConnection(ProposedFeatures.all)
 let state: State = { enabled: false, emitter: createEmitter(connection) }
@@ -195,6 +196,8 @@       state.configPath,
       state.config,
       state.plugins,
     ])
+
+    registerDocumentColorProvider(state)
   })
 
 connection.onDidChangeConfiguration((change) => {
M src/lsp/util/find.tssrc/lsp/util/find.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
diff --git a/src/lsp/util/find.ts b/src/lsp/util/find.ts
index db5609e3f06571bde42d9a555268d95f6318fc47..8ff4dedcaaa9d3ab1e20e53208b641fde0bf8a89 100644
--- a/src/lsp/util/find.ts
+++ b/src/lsp/util/find.ts
@@ -1,5 +1,10 @@
 import { TextDocument, Range, Position } from 'vscode-languageserver'
-import { DocumentClassName, DocumentClassList, State } from './state'
+import {
+  DocumentClassName,
+  DocumentClassList,
+  State,
+  DocumentHelperFunction,
+} from './state'
 import lineColumn from 'line-column'
 import { isCssContext, isCssDoc } from './css'
 import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html'
@@ -11,6 +16,7 @@   getClassAttributeLexer,
   getComputedClassAttributeLexer,
 } from './lexers'
 import { getLanguageBoundaries } from './getLanguageBoundaries'
+import { resolveRange } from './resolveRange'
 
 export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
   let match: RegExpMatchArray
@@ -252,6 +258,64 @@   return flatten([
     ...boundaries.html.map((range) => findClassListsInHtmlRange(doc, range)),
     ...boundaries.css.map((range) => findClassListsInCssRange(doc, range)),
   ])
+}
+
+export function findHelperFunctionsInDocument(
+  state: State,
+  doc: TextDocument
+): DocumentHelperFunction[] {
+  if (isCssDoc(state, doc)) {
+    return findHelperFunctionsInRange(doc)
+  }
+
+  let boundaries = getLanguageBoundaries(state, doc)
+  if (!boundaries) return []
+
+  return flatten(
+    boundaries.css.map((range) => findHelperFunctionsInRange(doc, range))
+  )
+}
+
+export function findHelperFunctionsInRange(
+  doc: TextDocument,
+  range?: Range
+): DocumentHelperFunction[] {
+  const text = doc.getText(range)
+  const matches = findAll(
+    /(?<before>^|\s)(?<helper>theme|config)\((?:(?<single>')([^']+)'|(?<double>")([^"]+)")\)/gm,
+    text
+  )
+
+  return matches.map((match) => {
+    let value = match[4] || match[6]
+    let startIndex = match.index + match.groups.before.length
+    return {
+      full: match[0].substr(match.groups.before.length),
+      value,
+      helper: match.groups.helper === 'theme' ? 'theme' : 'config',
+      quotes: match.groups.single ? "'" : '"',
+      range: resolveRange(
+        {
+          start: indexToPosition(text, startIndex),
+          end: indexToPosition(text, match.index + match[0].length),
+        },
+        range
+      ),
+      valueRange: resolveRange(
+        {
+          start: indexToPosition(
+            text,
+            startIndex + match.groups.helper.length + 1
+          ),
+          end: indexToPosition(
+            text,
+            startIndex + match.groups.helper.length + 1 + 1 + value.length + 1
+          ),
+        },
+        range
+      ),
+    }
+  })
 }
 
 export function indexToPosition(str: string, index: number): Position {
I src/lsp/util/resolveRange.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
diff --git a/src/lsp/util/resolveRange.ts b/src/lsp/util/resolveRange.ts
new file mode 100644
index 0000000000000000000000000000000000000000..96fe343afd5328b288c76f2fdc26e1d08ea1b004
--- /dev/null
+++ b/src/lsp/util/resolveRange.ts
@@ -0,0 +1,18 @@
+import { Range } from 'vscode-languageserver'
+
+export function resolveRange(range: Range, relativeTo?: Range) {
+  return {
+    start: {
+      line: (relativeTo?.start.line || 0) + range.start.line,
+      character:
+        (range.end.line === 0 ? relativeTo?.start.character || 0 : 0) +
+        range.start.character,
+    },
+    end: {
+      line: (relativeTo?.start.line || 0) + range.end.line,
+      character:
+        (range.end.line === 0 ? relativeTo?.start.character || 0 : 0) +
+        range.end.character,
+    },
+  }
+}
M src/lsp/util/state.tssrc/lsp/util/state.ts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
diff --git a/src/lsp/util/state.ts b/src/lsp/util/state.ts
index 09a02006721913e72c3a576ee1c30f997476c6b5..77afdfad4fd9faecb028f0ef8b6079fc04c34d40 100644
--- a/src/lsp/util/state.ts
+++ b/src/lsp/util/state.ts
@@ -74,6 +74,15 @@   relativeRange: Range
   classList: DocumentClassList
 }
 
+export type DocumentHelperFunction = {
+  full: string
+  helper: 'theme' | 'config'
+  value: string
+  quotes: '"' | "'"
+  range: Range
+  valueRange: Range
+}
+
 export type ClassNameMeta = {
   source: 'base' | 'components' | 'utilities'
   pseudo: string[]