Skip to content

Commit e03cbba

Browse files
committed
Collect offset-based range information
1 parent c8efeca commit e03cbba

File tree

3 files changed

+76
-14
lines changed

3 files changed

+76
-14
lines changed

packages/tailwindcss-language-service/src/util/find.ts

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,24 @@ export function findLast(re: RegExp, str: string): RegExpMatchArray {
3333
}
3434

3535
export function getClassNamesInClassList(
36-
{ classList, range, important }: DocumentClassList,
36+
{ classList, span, range, important }: DocumentClassList,
3737
blocklist: State['blocklist'],
3838
): DocumentClassName[] {
3939
const parts = classList.split(/(\s+)/)
4040
const names: DocumentClassName[] = []
4141
let index = 0
4242
for (let i = 0; i < parts.length; i++) {
4343
if (i % 2 === 0 && !blocklist.includes(parts[i])) {
44+
const classNameSpan = [index, index + parts[i].length]
4445
const start = indexToPosition(classList, index)
4546
const end = indexToPosition(classList, index + parts[i].length)
4647
names.push({
4748
className: parts[i],
49+
span: [span[0] + classNameSpan[0], span[0] + classNameSpan[1]],
4850
classList: {
4951
classList,
5052
range,
53+
span,
5154
important,
5255
},
5356
relativeRange: {
@@ -107,11 +110,19 @@ export function findClassListsInCssRange(
107110
const matches = findAll(regex, text)
108111
const globalStart: Position = range ? range.start : { line: 0, character: 0 }
109112

113+
const rangeStartOffset = doc.offsetAt(globalStart)
114+
110115
return matches.map((match) => {
111-
const start = indexToPosition(text, match.index + match[1].length)
112-
const end = indexToPosition(text, match.index + match[1].length + match.groups.classList.length)
116+
let span = [
117+
match.index + match[1].length,
118+
match.index + match[1].length + match.groups.classList.length,
119+
] as [number, number]
120+
121+
const start = indexToPosition(text, span[0])
122+
const end = indexToPosition(text, span[1])
113123
return {
114124
classList: match.groups.classList,
125+
span: [rangeStartOffset + span[0], rangeStartOffset + span[1]],
115126
important: Boolean(match.groups.important),
116127
range: {
117128
start: {
@@ -143,6 +154,7 @@ async function findCustomClassLists(
143154
for (let match of customClassesIn({ text, filters: regexes })) {
144155
result.push({
145156
classList: match.classList,
157+
span: match.range,
146158
range: {
147159
start: doc.positionAt(match.range[0]),
148160
end: doc.positionAt(match.range[1]),
@@ -179,6 +191,8 @@ export async function findClassListsInHtmlRange(
179191

180192
const result: DocumentClassList[] = []
181193

194+
const rangeStartOffset = doc.offsetAt(range?.start || { line: 0, character: 0 })
195+
182196
matches.forEach((match) => {
183197
const subtext = text.substr(match.index + match[0].length - 1)
184198

@@ -234,17 +248,17 @@ export async function findClassListsInHtmlRange(
234248
const after = value.match(/\s*$/)
235249
const afterOffset = after === null ? 0 : -after[0].length
236250

237-
const start = indexToPosition(
238-
text,
251+
let span = [
239252
match.index + match[0].length - 1 + offset + beforeOffset,
240-
)
241-
const end = indexToPosition(
242-
text,
243253
match.index + match[0].length - 1 + offset + value.length + afterOffset,
244-
)
254+
]
255+
256+
const start = indexToPosition(text, span[0])
257+
const end = indexToPosition(text, span[1])
245258

246259
return {
247260
classList: value.substr(beforeOffset, value.length + afterOffset),
261+
span: [rangeStartOffset + span[0], rangeStartOffset + span[1]] as [number, number],
248262
range: {
249263
start: {
250264
line: (range?.start.line || 0) + start.line,
@@ -356,6 +370,8 @@ export function findHelperFunctionsInRange(
356370
text,
357371
)
358372

373+
let rangeStartOffset = range?.start ? doc.offsetAt(range.start) : 0
374+
359375
// Eliminate matches that are on an `@import`
360376
matches = matches.filter((match) => {
361377
// Scan backwards to see if we're in an `@import` statement
@@ -422,6 +438,16 @@ export function findHelperFunctionsInRange(
422438
range,
423439
),
424440
},
441+
spans: {
442+
full: [
443+
rangeStartOffset + startIndex,
444+
rangeStartOffset + startIndex + match.groups.path.length,
445+
],
446+
path: [
447+
rangeStartOffset + startIndex + quotesBefore.length,
448+
rangeStartOffset + startIndex + quotesBefore.length + path.length,
449+
],
450+
},
425451
}
426452
})
427453
}

packages/tailwindcss-language-service/src/util/getLanguageBoundaries.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import { isVueDoc, isHtmlDoc, isSvelteDoc } from './html'
44
import type { State } from './state'
55
import { indexToPosition } from './find'
66
import { isJsDoc } from './js'
7-
import moo from 'moo'
7+
import moo, { Rules } from 'moo'
88
import Cache from 'tmp-cache'
99
import { getTextWithoutComments } from './doc'
1010
import { isCssLanguage } from './css'
1111

1212
export type LanguageBoundary = {
1313
type: 'html' | 'js' | 'jsx' | 'css' | (string & {})
1414
range: Range
15+
span: [number, number]
1516
lang?: string
1617
}
1718

@@ -29,9 +30,11 @@ let jsxScriptTypes = [
2930
'text/babel',
3031
]
3132

33+
type States = { [x: string]: Rules }
34+
3235
let text = { text: { match: /[^]/, lineBreaks: true } }
3336

34-
let states = {
37+
let states: States = {
3538
main: {
3639
cssBlockStart: { match: /<style(?=[>\s])/, push: 'cssBlock' },
3740
jsBlockStart: { match: '<script', push: 'jsBlock' },
@@ -177,7 +180,11 @@ export function getLanguageBoundaries(
177180

178181
let type = defaultType
179182
let boundaries: LanguageBoundary[] = [
180-
{ type: defaultType, range: { start: { line: 0, character: 0 }, end: undefined } },
183+
{
184+
type: defaultType,
185+
range: { start: { line: 0, character: 0 }, end: undefined },
186+
span: [0, undefined],
187+
},
181188
]
182189
let offset = 0
183190

@@ -189,12 +196,24 @@ export function getLanguageBoundaries(
189196
if (!boundaries[boundaries.length - 1].range.end) {
190197
boundaries[boundaries.length - 1].range.end = position
191198
}
199+
if (!boundaries[boundaries.length - 1].span[1]) {
200+
boundaries[boundaries.length - 1].span[1] = offset
201+
}
192202
type = token.type.replace(/BlockStart$/, '')
193-
boundaries.push({ type, range: { start: position, end: undefined } })
203+
boundaries.push({
204+
type,
205+
range: { start: position, end: undefined },
206+
span: [offset, undefined],
207+
})
194208
} else if (token.type.endsWith('BlockEnd')) {
195209
let position = indexToPosition(text, offset)
210+
boundaries[boundaries.length - 1].span[1] = offset
196211
boundaries[boundaries.length - 1].range.end = position
197-
boundaries.push({ type: defaultType, range: { start: position, end: undefined } })
212+
boundaries.push({
213+
type: defaultType,
214+
range: { start: position, end: undefined },
215+
span: [offset, undefined],
216+
})
198217
} else if (token.type === 'lang') {
199218
boundaries[boundaries.length - 1].type = token.text
200219

@@ -222,6 +241,10 @@ export function getLanguageBoundaries(
222241
boundaries[boundaries.length - 1].range.end = indexToPosition(text, offset)
223242
}
224243

244+
if (!boundaries[boundaries.length - 1].span[1]) {
245+
boundaries[boundaries.length - 1].span[1] = offset
246+
}
247+
225248
cache.set(cacheKey, boundaries)
226249

227250
for (let boundary of boundaries) {

packages/tailwindcss-language-service/src/util/state.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ export type ClassNames = {
1818
classNames: ClassNamesTree
1919
}
2020

21+
/**
22+
* Indicates a contiguous range of text, inclusive
23+
*
24+
* Offsets are in UTF-16 code units (indexing into a JS string)
25+
*/
26+
export type Span = [start: number, end: number]
27+
2128
export type EditorState = {
2229
connection: Connection
2330
folder: string
@@ -146,13 +153,15 @@ export interface State {
146153
export type DocumentClassList = {
147154
classList: string
148155
range: Range
156+
span: Span
149157
important?: boolean
150158
}
151159

152160
export type DocumentClassName = {
153161
className: string
154162
range: Range
155163
relativeRange: Range
164+
span: Span
156165
classList: DocumentClassList
157166
}
158167

@@ -163,6 +172,10 @@ export type DocumentHelperFunction = {
163172
full: Range
164173
path: Range
165174
}
175+
spans: {
176+
full: Span
177+
path: Span
178+
}
166179
}
167180

168181
export type ClassNameMeta = {

0 commit comments

Comments
 (0)