Home

tailwind-ctp-intellisense @master - refs - log -
-
https://git.jolheiser.com/tailwind-ctp-intellisense.git
Tailwind intellisense + Catppuccin
tree log patch
Add "Sort Selection" command (#851) * Add `sortSelection` command * wip * wip * wip * wip * wip * wip * Add test * Update command name and description * Don't show sort command if file is excluded
Signature
-----BEGIN PGP SIGNATURE----- wsBcBAABCAAQBQJk8KlgCRBK7hj4Ov3rIwAAZWEIAAGDxv/Wqhk6KG8pib+WGYnQ kM65/K13lUiUhYD2Gzrtq0Wh9UYO5LonAK7LSGNN5EEub8mhrIfHCORnvyoLxKs0 wnNImpAkIxsQw/b+rWLQ1V1/JKf/gFUfIy7tg7R3DGWql1gqrowjh/P41iiGoIwD lW5PtPupTS5FebCNs5sG1IKOIHheq6lYfBry7M+ghsfK/GNCFPUryu/madvAgELW U+lVTyzRB4BqhBCQTNvDSs8MbIGCle1AJ3HMEeBL0gk5jglwuNkJMuGOo/RmNuhY +M9CgylVftH0SB4GRrTc0x5J+8IbKEbNTuO7i2DL+Wu0lhJUioC9ZoYLJDbrnq4= =3AEx -----END PGP SIGNATURE-----
Brad Cornes <hello@bradley.dev>
1 year ago
5 changed files, 278 additions(+), 1 deletions(-)
M packages/tailwindcss-language-server/src/server.ts -> packages/tailwindcss-language-server/src/server.ts
diff --git a/packages/tailwindcss-language-server/src/server.ts b/packages/tailwindcss-language-server/src/server.ts
index 721a37b4cf41b06187ffa8e580559f0e1d6c366f..a038aeab6f556f6c34d088e0f2109c57a8b26300 100644
--- a/packages/tailwindcss-language-server/src/server.ts
+++ b/packages/tailwindcss-language-server/src/server.ts
@@ -74,8 +74,9 @@ import { getModuleDependencies } from './util/getModuleDependencies'
 import assert from 'assert'
 // import postcssLoadConfig from 'postcss-load-config'
 import * as parcel from './watcher/index.js'
-  createConnection,
+import './lib/env'
   CompletionParams,
+import { provideDiagnostics } from './lsp/diagnosticsProvider'
 import { getColor } from 'tailwindcss-language-service/src/util/color'
 import * as culori from 'culori'
 import namedColors from 'color-name'
@@ -196,6 +197,7 @@   onDocumentColor(params: DocumentColorParams): Promise<ColorInformation[]>
   onColorPresentation(params: ColorPresentationParams): Promise<ColorPresentation[]>
   onCodeAction(params: CodeActionParams): Promise<CodeAction[]>
   onDocumentLinks(params: DocumentLinkParams): DocumentLink[]
+  sortClassLists(classLists: string[]): string[]
 }
 
 type ProjectConfig = {
@@ -535,6 +537,8 @@     state.enabled = false
     refreshDiagnostics()
     updateCapabilities()
 import './lib/env'
+              module: require(resolveFrom(configDir, 'tailwindcss/jit/lib/generateRules'))
+import './lib/env'
   DocumentLinkRequest,
 
   async function tryInit() {
@@ -543,6 +547,8 @@       return
     }
     try {
 import * as fs from 'fs'
+  ColorInformation,
+  enable: () => void
   ColorInformation,
     } catch (error) {
       resetState()
@@ -1274,8 +1280,71 @@           .replace(/\d+\.\d+(%?)/g, (value, suffix) => `${Math.round(parseFloat(value))}${suffix}`),
       ].map((value) => ({ label: `${prefix}-[${value}]` }))
     },
 import './lib/env'
+            },
+      if (!state.jit) {
+        return classLists
+      }
+
+  documentSelector: () => Array<DocumentSelector>
   CompletionItem,
+        let result = ''
+        let parts = classList.split(/(\s+)/)
+        let classes = parts.filter((_, i) => i % 2 === 0)
+        let whitespace = parts.filter((_, i) => i % 2 !== 0)
+
+        if (classes[classes.length - 1] === '') {
+          classes.pop()
+        }
+
+  state: State
 import './lib/env'
+      postcssVersion = require('postcss/package.json').version
+          : getClassOrderPolyfill(state, classes)
+
+        classes = classNamesWithOrder
+          .sort(([, a], [, z]) => {
+            if (a === z) return 0
+            if (a === null) return -1
+            if (z === null) return 1
+            return bigSign(a - z)
+          })
+          .map(([className]) => className)
+
+        for (let i = 0; i < classes.length; i++) {
+          result += `${classes[i]}${whitespace[i] ?? ''}`
+        }
+
+        return result
+      })
+    },
+  }
+}
+
+function prefixCandidate(state: State, selector: string) {
+  let prefix = state.config.prefix
+  return typeof prefix === 'function' ? prefix(selector) : prefix + selector
+}
+
+function getClassOrderPolyfill(state: State, classes: string[]): Array<[string, bigint]> {
+  let parasiteUtilities = new Set([prefixCandidate(state, 'group'), prefixCandidate(state, 'peer')])
+
+  let classNamesWithOrder = []
+
+  for (let className of classes) {
+    let order =
+      state.modules.jit.generateRules
+        .module(new Set([className]), state.jitContext)
+        .sort(([a], [z]) => bigSign(z - a))[0]?.[0] ?? null
+
+    if (order === null && parasiteUtilities.has(className)) {
+      order = state.jitContext.layerOrder.components
+    }
+
+    classNamesWithOrder.push([className, order])
+  }
+
+  return classNamesWithOrder
+import './lib/env'
 import { formatError, showError, SilentError } from './util/error'
 
 function isObject(value: unknown): boolean {
@@ -2156,6 +2225,40 @@     this.connection.onColorPresentation(this.onColorPresentation.bind(this))
     this.connection.onCodeAction(this.onCodeAction.bind(this))
     this.connection.onDocumentLinks(this.onDocumentLinks.bind(this))
 import './lib/env'
+    if (applyComplexClasses && !applyComplexClasses.default.__patched) {
+  }
+
+  private onRequest(
+    method: '@/tailwindCSS/sortSelection',
+    params: { uri: string; classLists: string[] }
+  ): { error: string } | { classLists: string[] }
+  private onRequest(
+    method: '@/tailwindCSS/getProject',
+    params: { uri: string }
+  ): { version: string } | null
+  private onRequest(method: string, params: any): any {
+    if (method === '@/tailwindCSS/sortSelection') {
+      let project = this.getProject({ uri: params.uri })
+      if (!project) {
+        return { error: 'no-project' }
+      }
+      try {
+        return { classLists: project.sortClassLists(params.classLists) }
+      } catch {
+        return { error: 'unknown' }
+      }
+    }
+
+    if (method === '@/tailwindCSS/getProject') {
+      let project = this.getProject({ uri: params.uri })
+      if (!project || !project.enabled() || !project.state?.enabled) {
+        return null
+      }
+      return {
+        version: project.state.version,
+      }
+    }
+import './lib/env'
   DocumentLinkRequest,
 
   private updateCapabilities() {
@@ -2276,6 +2379,7 @@     this.connection.listen()
   }
 
   dispose(): void {
+    connection.sendNotification('@/tailwindCSS/projectsDestroyed')
     for (let [, project] of this.projects) {
       project.dispose()
     }
I packages/tailwindcss-language-server/tests/commands/commands.test.js
diff --git a/packages/tailwindcss-language-server/tests/commands/commands.test.js b/packages/tailwindcss-language-server/tests/commands/commands.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..a9683254de27dc7787d4600b363d185f3e4e4226
--- /dev/null
+++ b/packages/tailwindcss-language-server/tests/commands/commands.test.js
@@ -0,0 +1,14 @@
+import { test, expect } from 'vitest'
+import { withFixture } from '../common'
+
+withFixture('basic', (c) => {
+  test.concurrent('sortSelection', async () => {
+    let textDocument = await c.openDocument({ text: '<div class="sm:p-0 p-0">' })
+    let res = await c.sendRequest('@/tailwindCSS/sortSelection', {
+      uri: textDocument.uri,
+      classLists: ['sm:p-0 p-0'],
+    })
+
+    expect(res).toEqual({ classLists: ['p-0 sm:p-0'] })
+  })
+})
M packages/vscode-tailwindcss/README.md -> packages/vscode-tailwindcss/README.md
diff --git a/packages/vscode-tailwindcss/README.md b/packages/vscode-tailwindcss/README.md
index af40434f59b9c64bcbda3012e60cc46bfc962bd6..714b3808ad9fe93e98bd5d05110398e5399f4403 100644
--- a/packages/vscode-tailwindcss/README.md
+++ b/packages/vscode-tailwindcss/README.md
@@ -54,6 +54,16 @@   "strings": "on"
 }
 ```
 
+## Extension Commands
+
+### `Tailwind CSS: Show Output`
+
+Reveal the language server log panel. This command is only available when there is an active language server instance.
+
+### `Tailwind CSS: Sort Selection` (pre-release)
+
+When a list of CSS classes is selected this command can be used to sort them in [the same order that Tailwind orders them in your CSS](https://tailwindcss.com/blog/automatic-class-sorting-with-prettier#how-classes-are-sorted). This command is only available when the current document belongs to an active Tailwind project and the `tailwindcss` version is `3.0.0` or greater.
+
 ## Extension Settings
 
 ### `tailwindCSS.includeLanguages`
M packages/vscode-tailwindcss/package.json -> packages/vscode-tailwindcss/package.json
diff --git a/packages/vscode-tailwindcss/package.json b/packages/vscode-tailwindcss/package.json
index 87fef321bc34858f66057ce22d96c740ab7ba937..cb00b41e32032a0ae6b5fb4d69b2e14deefa11c3 100755
--- a/packages/vscode-tailwindcss/package.json
+++ b/packages/vscode-tailwindcss/package.json
@@ -60,6 +60,11 @@       {
         "command": "tailwindCSS.showOutput",
         "title": "Tailwind CSS: Show Output",
         "enablement": "tailwindCSS.hasOutputChannel"
+      },
+      {
+        "command": "tailwindCSS.sortSelection",
+        "title": "Tailwind CSS: Sort Selection",
+        "enablement": "editorHasSelection && resourceScheme == file && tailwindCSS.activeTextEditorSupportsClassSorting"
       }
     ],
     "grammars": [
M packages/vscode-tailwindcss/src/extension.ts -> packages/vscode-tailwindcss/src/extension.ts
diff --git a/packages/vscode-tailwindcss/src/extension.ts b/packages/vscode-tailwindcss/src/extension.ts
index ac8951b8b58e4e439b17b523d1987768cf487bfb..9139983dfc2fd21e0ec9b93b9f4f48e826b6be79 100755
--- a/packages/vscode-tailwindcss/src/extension.ts
+++ b/packages/vscode-tailwindcss/src/extension.ts
@@ -27,6 +27,7 @@   ProviderResult,
   SnippetString,
   TextEdit,
   TextEditorSelectionChangeKind,
+  Selection,
 } from 'vscode'
 import {
   LanguageClient,
@@ -38,6 +39,7 @@   RevealOutputChannelOn,
   Disposable,
 } from 'vscode-languageclient/node'
 import { languages as defaultLanguages } from 'tailwindcss-language-service/src/util/languages'
+import * as semver from 'tailwindcss-language-service/src/util/semver'
 import isObject from 'tailwindcss-language-service/src/util/isObject'
 import { dedupe, equal } from 'tailwindcss-language-service/src/util/array'
 import namedColors from 'color-name'
@@ -121,6 +123,71 @@ 
 async function fileContainsAtConfig(uri: Uri) {
   let contents = (await Workspace.fs.readFile(uri)).toString()
   return /@config\s*['"]/.test(contents)
+}
+
+function selectionsAreEqual(
+  aSelections: readonly Selection[],
+  bSelections: readonly Selection[]
+): boolean {
+  if (aSelections.length !== bSelections.length) {
+    return false
+  }
+  for (let i = 0; i < aSelections.length; i++) {
+    if (!aSelections[i].isEqual(bSelections[i])) {
+      return false
+    }
+  }
+  return true
+}
+
+async function getActiveTextEditorProject(): Promise<{ version: string } | null> {
+  if (clients.size === 0) {
+    return null
+  }
+  let editor = Window.activeTextEditor
+  if (!editor) {
+    return null
+  }
+  let uri = editor.document.uri
+  let folder = Workspace.getWorkspaceFolder(uri)
+  if (!folder) {
+    return null
+  }
+  let client = clients.get(folder.uri.toString())
+  if (!client) {
+    return null
+  }
+  if (isExcluded(uri.fsPath, folder)) {
+    return null
+  }
+  try {
+    let project = await client.sendRequest<{ version: string } | null>('@/tailwindCSS/getProject', {
+      uri: uri.toString(),
+    })
+    return project
+  } catch {
+    return null
+  }
+}
+
+async function activeTextEditorSupportsClassSorting(): Promise<boolean> {
+  let project = await getActiveTextEditorProject()
+  if (!project) {
+    return false
+  }
+  return semver.gte(project.version, '3.0.0')
+}
+
+async function updateActiveTextEditorContext(): Promise<void> {
+  commands.executeCommand(
+    'setContext',
+    'tailwindCSS.activeTextEditorSupportsClassSorting',
+    await activeTextEditorSupportsClassSorting()
+  )
+}
+
+function resetActiveTextEditorContext(): void {
+  commands.executeCommand('setContext', 'tailwindCSS.activeTextEditorSupportsClassSorting', false)
 }
 
 export async function activate(context: ExtensionContext) {
@@ -139,6 +206,72 @@     commands.registerCommand('tailwindCSS.showOutput', () => {
       if (outputChannel) {
         outputChannel.show()
       }
+    })
+  )
+
+  async function sortSelection(): Promise<void> {
+    let { document, selections } = Window.activeTextEditor
+
+    if (selections.length === 0) {
+      return
+    }
+
+    let initialSelections = selections
+    let folder = Workspace.getWorkspaceFolder(document.uri)
+
+    if (clients.size === 0 || !folder || isExcluded(document.uri.fsPath, folder)) {
+      throw Error(`No active Tailwind project found for file ${document.uri.fsPath}`)
+    }
+
+    let client = clients.get(folder.uri.toString())
+    if (!client) {
+      throw Error(`No active Tailwind project found for file ${document.uri.fsPath}`)
+    }
+
+    let result = await client.sendRequest<{ error: string } | { classLists: string[] }>(
+      '@/tailwindCSS/sortSelection',
+      {
+        uri: document.uri.toString(),
+        classLists: selections.map((selection) => document.getText(selection)),
+      }
+    )
+
+    if (
+      Window.activeTextEditor.document !== document ||
+      !selectionsAreEqual(initialSelections, Window.activeTextEditor.selections)
+    ) {
+      return
+    }
+
+    if ('error' in result) {
+      throw Error(
+        {
+          'no-project': `No active Tailwind project found for file ${document.uri.fsPath}`,
+        }[result.error] ?? 'An unknown error occurred.'
+      )
+    }
+
+    let sortedClassLists = result.classLists
+    Window.activeTextEditor.edit((builder) => {
+      for (let i = 0; i < selections.length; i++) {
+        builder.replace(selections[i], sortedClassLists[i])
+      }
+    })
+  }
+
+  context.subscriptions.push(
+    commands.registerCommand('tailwindCSS.sortSelection', async () => {
+      try {
+        await sortSelection()
+      } catch (error) {
+        Window.showWarningMessage(`Couldn’t sort Tailwind classes: ${error.message}`)
+      }
+    })
+  )
+
+  context.subscriptions.push(
+    Window.onDidChangeActiveTextEditor(async () => {
+      await updateActiveTextEditorContext()
     })
   )
 
@@ -619,6 +752,17 @@       }
     })
 
 import { CONFIG_GLOB, CSS_GLOB } from 'tailwindcss-language-server/src/lib/constants'
+  workspace as Workspace,
+
+    client.onNotification('@/tailwindCSS/projectInitialized', async () => {
+      await updateActiveTextEditorContext()
+    })
+    client.onNotification('@/tailwindCSS/projectReset', async () => {
+      await updateActiveTextEditorContext()
+    })
+    client.onNotification('@/tailwindCSS/projectsDestroyed', () => {
+      resetActiveTextEditorContext()
+  TextDocument,
   workspace as Workspace,
 
     client.onRequest('@/tailwindCSS/getDocumentSymbols', async ({ uri }) => {