Merge branch 'color-decorators'
commit
9cc7bee2a4
|
@ -1019,6 +1019,12 @@
|
||||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
|
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/debounce": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/graceful-fs": {
|
"@types/graceful-fs": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz",
|
||||||
|
@ -1960,6 +1966,12 @@
|
||||||
"integrity": "sha512-8P1cDi8ebZyDxUyUprBXwidoEtiQAawYPGvpfb+Dg0G6JrQ+VozwOmm91xYC0vAv1+0VmLehEPb+isg4BGUFfA==",
|
"integrity": "sha512-8P1cDi8ebZyDxUyUprBXwidoEtiQAawYPGvpfb+Dg0G6JrQ+VozwOmm91xYC0vAv1+0VmLehEPb+isg4BGUFfA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"debounce": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||||
|
|
18
package.json
18
package.json
|
@ -71,6 +71,22 @@
|
||||||
"default": {},
|
"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\"}`"
|
"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": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"inherit",
|
||||||
|
"on",
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"markdownEnumDescriptions": [
|
||||||
|
"Color decorators are rendered if `editor.colorDecorators` is enabled.",
|
||||||
|
"Color decorators are rendered.",
|
||||||
|
"Color decorators are not rendered."
|
||||||
|
],
|
||||||
|
"default": "inherit",
|
||||||
|
"markdownDescription": "Controls whether the editor should render inline color decorators for Tailwind CSS classes and helper functions.",
|
||||||
|
"scope": "language-overridable"
|
||||||
|
},
|
||||||
"tailwindCSS.validate": {
|
"tailwindCSS.validate": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true,
|
"default": true,
|
||||||
|
@ -157,6 +173,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ctrl/tinycolor": "^3.1.0",
|
"@ctrl/tinycolor": "^3.1.0",
|
||||||
|
"@types/debounce": "^1.2.0",
|
||||||
"@types/mocha": "^5.2.0",
|
"@types/mocha": "^5.2.0",
|
||||||
"@types/moo": "^0.5.3",
|
"@types/moo": "^0.5.3",
|
||||||
"@types/node": "^13.9.3",
|
"@types/node": "^13.9.3",
|
||||||
|
@ -166,6 +183,7 @@
|
||||||
"chokidar": "^3.3.1",
|
"chokidar": "^3.3.1",
|
||||||
"concurrently": "^5.1.0",
|
"concurrently": "^5.1.0",
|
||||||
"css.escape": "^1.5.1",
|
"css.escape": "^1.5.1",
|
||||||
|
"debounce": "^1.2.0",
|
||||||
"detect-indent": "^6.0.0",
|
"detect-indent": "^6.0.0",
|
||||||
"dlv": "^1.1.3",
|
"dlv": "^1.1.3",
|
||||||
"dset": "^2.0.1",
|
"dset": "^2.0.1",
|
||||||
|
|
|
@ -24,6 +24,7 @@ import isObject from './util/isObject'
|
||||||
import { dedupe, equal } from './util/array'
|
import { dedupe, equal } from './util/array'
|
||||||
import { createEmitter } from './lib/emitter'
|
import { createEmitter } from './lib/emitter'
|
||||||
import { onMessage } from './lsp/notifications'
|
import { onMessage } from './lsp/notifications'
|
||||||
|
import { registerColorDecorator } from './lib/registerColorDecorator'
|
||||||
|
|
||||||
const CLIENT_ID = 'tailwindcss-intellisense'
|
const CLIENT_ID = 'tailwindcss-intellisense'
|
||||||
const CLIENT_NAME = 'Tailwind CSS IntelliSense'
|
const CLIENT_NAME = 'Tailwind CSS IntelliSense'
|
||||||
|
@ -152,6 +153,7 @@ export function activate(context: ExtensionContext) {
|
||||||
client.onReady().then(() => {
|
client.onReady().then(() => {
|
||||||
let emitter = createEmitter(client)
|
let emitter = createEmitter(client)
|
||||||
registerConfigErrorHandler(emitter)
|
registerConfigErrorHandler(emitter)
|
||||||
|
registerColorDecorator(client, context, emitter)
|
||||||
onMessage(client, 'getConfiguration', async (scope) => {
|
onMessage(client, 'getConfiguration', async (scope) => {
|
||||||
return Workspace.getConfiguration('tailwindCSS', scope)
|
return Workspace.getConfiguration('tailwindCSS', scope)
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
import { window, workspace, ExtensionContext, TextEditor } from 'vscode'
|
||||||
|
import { NotificationEmitter } from './emitter'
|
||||||
|
import { LanguageClient } from 'vscode-languageclient'
|
||||||
|
import debounce from 'debounce'
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 preference =
|
||||||
|
workspace.getConfiguration('tailwindCSS', editor.document)
|
||||||
|
.colorDecorators || 'inherit'
|
||||||
|
|
||||||
|
let enabled: boolean =
|
||||||
|
preference === 'inherit'
|
||||||
|
? Boolean(workspace.getConfiguration('editor').colorDecorators)
|
||||||
|
: preference === 'on'
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
editor.setDecorations(colorDecorationType, [])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let { colors } = await emitter.emit('getDocumentColors', {
|
||||||
|
document: editor.document.uri.toString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
editor.setDecorations(
|
||||||
|
colorDecorationType,
|
||||||
|
colors
|
||||||
|
.filter(({ color }) => color !== 'rgba(0, 0, 0, 0.01)')
|
||||||
|
.map(({ range, color }) => ({
|
||||||
|
range,
|
||||||
|
renderOptions: { before: { backgroundColor: color } },
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const triggerUpdateDecorations = debounce(updateDecorations, 200)
|
||||||
|
|
||||||
|
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('editor.colorDecorators') ||
|
||||||
|
e.affectsConfiguration('tailwindCSS.colorDecorators')
|
||||||
|
) {
|
||||||
|
window.visibleTextEditors.forEach(updateDecorationsInEditor)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
emitter.on('configUpdated', () => {
|
||||||
|
window.visibleTextEditors.forEach(updateDecorationsInEditor)
|
||||||
|
})
|
||||||
|
|
||||||
|
emitter.on('configError', () => {
|
||||||
|
window.visibleTextEditors.forEach(updateDecorationsInEditor)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
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 { stringToPath } from '../util/stringToPath'
|
||||||
|
const dlv = require('dlv')
|
||||||
|
|
||||||
|
export function registerDocumentColorProvider(state: State) {
|
||||||
|
onMessage(
|
||||||
|
state.editor.connection,
|
||||||
|
'getDocumentColors',
|
||||||
|
async ({ document }) => {
|
||||||
|
let colors = []
|
||||||
|
if (!state.enabled) return { colors }
|
||||||
|
let doc = state.editor.documents.get(document)
|
||||||
|
if (!doc) return { colors }
|
||||||
|
|
||||||
|
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 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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: fn.valueRange, color })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return { colors }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
|
@ -35,9 +35,10 @@ import {
|
||||||
} from './providers/diagnostics/diagnosticsProvider'
|
} from './providers/diagnostics/diagnosticsProvider'
|
||||||
import { createEmitter } from '../lib/emitter'
|
import { createEmitter } from '../lib/emitter'
|
||||||
import { provideCodeActions } from './providers/codeActions/codeActionProvider'
|
import { provideCodeActions } from './providers/codeActions/codeActionProvider'
|
||||||
|
import { registerDocumentColorProvider } from './providers/documentColorProvider'
|
||||||
|
|
||||||
let connection = createConnection(ProposedFeatures.all)
|
let connection = createConnection(ProposedFeatures.all)
|
||||||
let state: State = { enabled: false, emitter: createEmitter(connection) }
|
const state: State = { enabled: false, emitter: createEmitter(connection) }
|
||||||
let documents = new TextDocuments()
|
let documents = new TextDocuments()
|
||||||
let workspaceFolder: string | null
|
let workspaceFolder: string | null
|
||||||
|
|
||||||
|
@ -73,7 +74,7 @@ connection.onInitialize(
|
||||||
async (params: InitializeParams): Promise<InitializeResult> => {
|
async (params: InitializeParams): Promise<InitializeResult> => {
|
||||||
const capabilities = params.capabilities
|
const capabilities = params.capabilities
|
||||||
|
|
||||||
const editorState: EditorState = {
|
state.editor = {
|
||||||
connection,
|
connection,
|
||||||
documents,
|
documents,
|
||||||
documentSettings,
|
documentSettings,
|
||||||
|
@ -99,12 +100,7 @@ connection.onInitialize(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
onChange: (newState: State): void => {
|
onChange: (newState: State): void => {
|
||||||
if (newState && !newState.error) {
|
if (newState && !newState.error) {
|
||||||
state = {
|
Object.assign(state, newState, { enabled: true })
|
||||||
...newState,
|
|
||||||
enabled: true,
|
|
||||||
emitter: state.emitter,
|
|
||||||
editor: editorState,
|
|
||||||
}
|
|
||||||
connection.sendNotification('tailwindcss/configUpdated', [
|
connection.sendNotification('tailwindcss/configUpdated', [
|
||||||
state.configPath,
|
state.configPath,
|
||||||
state.config,
|
state.config,
|
||||||
|
@ -112,11 +108,7 @@ connection.onInitialize(
|
||||||
])
|
])
|
||||||
updateAllDiagnostics(state)
|
updateAllDiagnostics(state)
|
||||||
} else {
|
} else {
|
||||||
state = {
|
state.enabled = false
|
||||||
enabled: false,
|
|
||||||
emitter: state.emitter,
|
|
||||||
editor: editorState,
|
|
||||||
}
|
|
||||||
if (newState && newState.error) {
|
if (newState && newState.error) {
|
||||||
const payload: {
|
const payload: {
|
||||||
message: string
|
message: string
|
||||||
|
@ -140,14 +132,9 @@ connection.onInitialize(
|
||||||
)
|
)
|
||||||
|
|
||||||
if (tailwindState) {
|
if (tailwindState) {
|
||||||
state = {
|
Object.assign(state, tailwindState, { enabled: true })
|
||||||
enabled: true,
|
|
||||||
emitter: state.emitter,
|
|
||||||
editor: editorState,
|
|
||||||
...tailwindState,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
state = { enabled: false, emitter: state.emitter, editor: editorState }
|
state.enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -195,6 +182,8 @@ connection.onInitialized &&
|
||||||
state.config,
|
state.config,
|
||||||
state.plugins,
|
state.plugins,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
registerDocumentColorProvider(state)
|
||||||
})
|
})
|
||||||
|
|
||||||
connection.onDidChangeConfiguration((change) => {
|
connection.onDidChangeConfiguration((change) => {
|
||||||
|
|
|
@ -48,17 +48,43 @@ export function getColor(
|
||||||
)
|
)
|
||||||
|
|
||||||
// check that all of the values are valid colors
|
// check that all of the values are valid colors
|
||||||
if (colors.some((color) => !color.isValid)) {
|
if (colors.some((color) => color !== 'transparent' && !color.isValid)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that all of the values are the same color
|
// check that all of the values are the same color, ignoring alpha
|
||||||
const colorStrings = colors.map((color) => color.toRgbString())
|
const colorStrings = dedupe(
|
||||||
if (dedupe(colorStrings).length !== 1) {
|
colors.map((color) =>
|
||||||
|
color === 'transparent'
|
||||||
|
? 'transparent'
|
||||||
|
: `${color.r}-${color.g}-${color.b}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (colorStrings.length !== 1) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return { documentation: colorStrings[0] }
|
if (colorStrings[0] === 'transparent') {
|
||||||
|
return {
|
||||||
|
documentation: 'rgba(0, 0, 0, 0.01)',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nonTransparentColors = colors.filter(
|
||||||
|
(color): color is TinyColor => color !== 'transparent'
|
||||||
|
)
|
||||||
|
|
||||||
|
const alphas = dedupe(nonTransparentColors.map((color) => color.a))
|
||||||
|
|
||||||
|
if (alphas.length === 1 || (alphas.length === 2 && alphas.includes(0))) {
|
||||||
|
return {
|
||||||
|
documentation: nonTransparentColors
|
||||||
|
.find((color) => color.a !== 0)
|
||||||
|
.toRgbString(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getColorFromValue(value: unknown): string {
|
export function getColorFromValue(value: unknown): string {
|
||||||
|
@ -73,9 +99,9 @@ export function getColorFromValue(value: unknown): string {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function createColor(str: string): TinyColor {
|
function createColor(str: string): TinyColor | 'transparent' {
|
||||||
if (str === 'transparent') {
|
if (str === 'transparent') {
|
||||||
return new TinyColor({ r: 0, g: 0, b: 0, a: 0.01 })
|
return 'transparent'
|
||||||
}
|
}
|
||||||
|
|
||||||
// matches: rgba(<r>, <g>, <b>, var(--bg-opacity))
|
// matches: rgba(<r>, <g>, <b>, var(--bg-opacity))
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { TextDocument, Range, Position } from 'vscode-languageserver'
|
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 lineColumn from 'line-column'
|
||||||
import { isCssContext, isCssDoc } from './css'
|
import { isCssContext, isCssDoc } from './css'
|
||||||
import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html'
|
import { isHtmlContext, isHtmlDoc, isSvelteDoc, isVueDoc } from './html'
|
||||||
|
@ -11,6 +16,7 @@ import {
|
||||||
getComputedClassAttributeLexer,
|
getComputedClassAttributeLexer,
|
||||||
} from './lexers'
|
} from './lexers'
|
||||||
import { getLanguageBoundaries } from './getLanguageBoundaries'
|
import { getLanguageBoundaries } from './getLanguageBoundaries'
|
||||||
|
import { resolveRange } from './resolveRange'
|
||||||
|
|
||||||
export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
|
export function findAll(re: RegExp, str: string): RegExpMatchArray[] {
|
||||||
let match: RegExpMatchArray
|
let match: RegExpMatchArray
|
||||||
|
@ -254,6 +260,64 @@ export function findClassListsInDocument(
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
export function indexToPosition(str: string, index: number): Position {
|
||||||
const { line, col } = lineColumn(str + '\n', index)
|
const { line, col } = lineColumn(str + '\n', index)
|
||||||
return { line: line - 1, character: col - 1 }
|
return { line: line - 1, character: col - 1 }
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -75,6 +75,15 @@ export type DocumentClassName = {
|
||||||
classList: DocumentClassList
|
classList: DocumentClassList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DocumentHelperFunction = {
|
||||||
|
full: string
|
||||||
|
helper: 'theme' | 'config'
|
||||||
|
value: string
|
||||||
|
quotes: '"' | "'"
|
||||||
|
range: Range
|
||||||
|
valueRange: Range
|
||||||
|
}
|
||||||
|
|
||||||
export type ClassNameMeta = {
|
export type ClassNameMeta = {
|
||||||
source: 'base' | 'components' | 'utilities'
|
source: 'base' | 'components' | 'utilities'
|
||||||
pseudo: string[]
|
pseudo: string[]
|
||||||
|
|
Loading…
Reference in New Issue