Skip to content

Commit 6161358

Browse files
committed
virtual doc scopes
1 parent 38b37ae commit 6161358

File tree

1 file changed

+54
-12
lines changed
  • packages/tailwindcss-language-service/src/documents

1 file changed

+54
-12
lines changed

packages/tailwindcss-language-service/src/documents/document.ts

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import type { Position, TextDocument } from 'vscode-languageserver-textdocument'
2-
import type { State } from '../util/state'
2+
import type { Span, State } from '../util/state'
3+
import { ScopeTree } from '../scopes/tree'
4+
import { ScopePath, walkScope } from '../scopes/walk'
5+
import { ScopeContext } from '../scopes/scope'
36
import { LanguageBoundary } from '../util/getLanguageBoundaries'
47
import { isWithinRange } from '../util/isWithinRange'
5-
import { getTextWithoutComments } from '../util/doc'
68

79
export type DocumentContext = 'html' | 'css' | 'js' | 'other' | (string & {})
810

@@ -29,34 +31,57 @@ export class VirtualDocument {
2931
*/
3032
private boundaries: LanguageBoundary[]
3133

34+
/**
35+
* A description of this document's "interesting" parts
36+
*
37+
* This is used to determine how the document is structured and what parts
38+
* are relevant to the current operation.
39+
*
40+
* For example, we can use this to get all class lists in a document, to
41+
* determine if the cursor is in a class name, or what part of a document is
42+
* considered "CSS" inside HTML or Vue documents.
43+
*/
44+
private scopes: ScopeTree
45+
3246
constructor(
3347
state: () => State,
3448
storage: TextDocument,
3549
boundaries: LanguageBoundary[],
50+
scopes: ScopeTree,
3651
) {
3752
this.state = state
3853
this.storage = storage
3954
this.boundaries = boundaries
55+
this.scopes = scopes
4056
}
4157

4258
/**
4359
* The current content of the document
4460
*/
4561
public get contents() {
62+
let spans: Span[] = []
63+
64+
walkScope(this.scopes.all(), {
65+
enter(node) {
66+
if (node.kind !== 'comment') return
67+
spans.push(node.source.scope)
68+
},
69+
})
70+
71+
// TODO: Drop comment removal once all features only query scopes
72+
let text = this.storage.getText()
73+
4674
// Replace all comment nodes with whitespace
4775
let tmp = ''
48-
49-
let type = this.boundaries[0].type
50-
if (type === 'html') {
51-
tmp = getTextWithoutComments(this.storage, 'html')
52-
} else if (type === 'css') {
53-
tmp = getTextWithoutComments(this.storage, 'css')
54-
} else if (type === 'js') {
55-
tmp = getTextWithoutComments(this.storage, 'js')
56-
} else if (type === 'jsx') {
57-
tmp = getTextWithoutComments(this.storage, 'jsx')
76+
let last = 0
77+
for (let [start, end] of spans) {
78+
tmp += text.slice(last, start)
79+
tmp += text.slice(start, end).replace(/./gs, (char) => (char === '\n' ? '\n' : ' '))
80+
last = end
5881
}
5982

83+
tmp += text.slice(last)
84+
6085
return tmp
6186
}
6287

@@ -72,4 +97,21 @@ export class VirtualDocument {
7297

7398
return null
7499
}
100+
101+
/**
102+
* Find the inner-most scope containing a given cursor position.
103+
*/
104+
public scopeAt(cursor: Position): ScopePath {
105+
return this.scopes.at(this.storage.offsetAt(cursor))
106+
}
107+
108+
/**
109+
* Find the inner-most scope containing a given cursor position.
110+
*/
111+
public contextAt(cursor: Position): ScopeContext {
112+
let scope = this.scopes.closestAt('context', this.storage.offsetAt(cursor))
113+
if (!scope) throw new Error('Unreachable')
114+
115+
return scope
116+
}
75117
}

0 commit comments

Comments
 (0)