1
1
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'
3
6
import { LanguageBoundary } from '../util/getLanguageBoundaries'
4
7
import { isWithinRange } from '../util/isWithinRange'
5
- import { getTextWithoutComments } from '../util/doc'
6
8
7
9
export type DocumentContext = 'html' | 'css' | 'js' | 'other' | ( string & { } )
8
10
@@ -29,34 +31,57 @@ export class VirtualDocument {
29
31
*/
30
32
private boundaries : LanguageBoundary [ ]
31
33
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
+
32
46
constructor (
33
47
state : ( ) => State ,
34
48
storage : TextDocument ,
35
49
boundaries : LanguageBoundary [ ] ,
50
+ scopes : ScopeTree ,
36
51
) {
37
52
this . state = state
38
53
this . storage = storage
39
54
this . boundaries = boundaries
55
+ this . scopes = scopes
40
56
}
41
57
42
58
/**
43
59
* The current content of the document
44
60
*/
45
61
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
+
46
74
// Replace all comment nodes with whitespace
47
75
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
58
81
}
59
82
83
+ tmp += text . slice ( last )
84
+
60
85
return tmp
61
86
}
62
87
@@ -72,4 +97,21 @@ export class VirtualDocument {
72
97
73
98
return null
74
99
}
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
+ }
75
117
}
0 commit comments