1
1
use bumpalo:: Bump ;
2
+ use crossbeam_channel:: { bounded, Receiver , Sender } ;
2
3
use dashmap:: DashMap ;
3
4
use hdx_ast:: css:: { StyleSheet , Visitable } ;
4
- use hdx_highlight:: { SemanticKind , SemanticModifier , TokenHighlighter } ;
5
- use hdx_parser:: { Features , Parser } ;
5
+ use hdx_highlight:: { Highlight , SemanticKind , SemanticModifier , TokenHighlighter } ;
6
+ use hdx_parser:: { Features , Parser , ParserReturn } ;
6
7
use itertools:: Itertools ;
7
8
use lsp_types:: Uri ;
8
- use std:: sync:: {
9
- atomic:: { AtomicBool , Ordering } ,
10
- Arc ,
9
+ use ropey:: Rope ;
10
+ use std:: {
11
+ sync:: {
12
+ atomic:: { AtomicBool , Ordering } ,
13
+ Arc ,
14
+ } ,
15
+ thread:: { Builder , JoinHandle } ,
11
16
} ;
12
17
use strum:: VariantNames ;
13
- use tracing:: trace;
18
+ use tracing:: { instrument , trace} ;
14
19
15
20
use crate :: { ErrorCode , Handler } ;
16
21
22
+ type Line = u32 ;
23
+ type Col = u32 ;
24
+
25
+ #[ derive( Debug ) ]
26
+ enum FileCall {
27
+ // Re-parse the document based on changes
28
+ RopeChange ( Rope ) ,
29
+ // Highlight a document, returning the semantic highlights
30
+ Highlight ,
31
+ }
32
+
33
+ #[ derive( Debug ) ]
34
+ enum FileReturn {
35
+ Highlights ( Vec < ( Highlight , Line , Col ) > ) ,
36
+ }
37
+
38
+ #[ derive( Debug ) ]
39
+ pub struct File {
40
+ pub content : Rope ,
41
+ thread : JoinHandle < ( ) > ,
42
+ sender : Sender < FileCall > ,
43
+ receiver : Receiver < FileReturn > ,
44
+ }
45
+
46
+ impl File {
47
+ fn new ( ) -> Self {
48
+ let ( sender, read_receiver) = bounded :: < FileCall > ( 0 ) ;
49
+ let ( write_sender, receiver) = bounded :: < FileReturn > ( 0 ) ;
50
+ Self {
51
+ content : Rope :: new ( ) ,
52
+ sender,
53
+ receiver,
54
+ thread : Builder :: new ( )
55
+ . name ( "LspDocumentHandler" . into ( ) )
56
+ . spawn ( move || {
57
+ let mut bump = Bump :: default ( ) ;
58
+ let mut string: String = "" . into ( ) ;
59
+ let mut result: ParserReturn < ' _ , StyleSheet < ' _ > > =
60
+ Parser :: new ( & bump, "" , Features :: default ( ) ) . parse_entirely :: < StyleSheet > ( ) ;
61
+ while let Ok ( call) = read_receiver. recv ( ) {
62
+ trace ! ( "String is currently {:?}" , string) ;
63
+ match call {
64
+ FileCall :: RopeChange ( rope) => {
65
+ trace ! ( "Parsing document" ) ;
66
+ // TODO! we should be able to optimize this by parsing a subset of the tree and mutating in
67
+ // place. For now though a partial parse request re-parses it all.
68
+ drop ( result) ;
69
+ bump. reset ( ) ;
70
+ string = rope. clone ( ) . into ( ) ;
71
+ result =
72
+ Parser :: new ( & bump, & string, Features :: default ( ) ) . parse_entirely :: < StyleSheet > ( ) ;
73
+ if let Some ( stylesheet) = & result. output {
74
+ trace ! ( "Sucessfully parsed stylesheet: {:#?}" , & stylesheet) ;
75
+ }
76
+ }
77
+ FileCall :: Highlight => {
78
+ trace ! ( "Highlighting document" ) ;
79
+ let mut highlighter = TokenHighlighter :: new ( ) ;
80
+ if let Some ( stylesheet) = & result. output {
81
+ stylesheet. accept ( & mut highlighter) ;
82
+ let mut current_line = 0 ;
83
+ let mut current_start = 0 ;
84
+ let data = highlighter
85
+ . highlights ( )
86
+ . sorted_by ( |a, b| Ord :: cmp ( & a. span ( ) , & b. span ( ) ) )
87
+ . map ( |h| {
88
+ // TODO: figure out a more efficient way to get line/col
89
+ let span_contents = h. span ( ) . span_contents ( & string) ;
90
+ let ( line, start) = span_contents. line_and_column ( ) ;
91
+ let delta_line: Line = line - current_line;
92
+ current_line = line;
93
+ let delta_start: Col =
94
+ if delta_line == 0 { start - current_start } else { start } ;
95
+ current_start = start;
96
+ ( * h, delta_line, delta_start)
97
+ } ) ;
98
+ write_sender. send ( FileReturn :: Highlights ( data. collect ( ) ) ) . ok ( ) ;
99
+ }
100
+ }
101
+ }
102
+ }
103
+ } )
104
+ . expect ( "Failed to document thread Reader" ) ,
105
+ }
106
+ }
107
+
108
+ fn to_string ( & self ) -> String {
109
+ self . content . clone ( ) . into ( )
110
+ }
111
+
112
+ fn commit ( & mut self , rope : Rope ) {
113
+ self . content = rope;
114
+ self . sender . send ( FileCall :: RopeChange ( self . content . clone ( ) ) ) . unwrap ( ) ;
115
+ }
116
+
117
+ #[ instrument]
118
+ fn get_highlights ( & self ) -> Vec < ( Highlight , Line , Col ) > {
119
+ self . sender . send ( FileCall :: Highlight ) . unwrap ( ) ;
120
+ while let Ok ( ret) = self . receiver . recv ( ) {
121
+ if let FileReturn :: Highlights ( highlights) = ret {
122
+ return highlights;
123
+ }
124
+ }
125
+ return vec ! [ ] ;
126
+ }
127
+ }
128
+
129
+ #[ derive( Debug ) ]
17
130
pub struct LSPService {
18
131
version : String ,
19
- files : Arc < DashMap < Uri , String > > ,
132
+ files : Arc < DashMap < Uri , File > > ,
20
133
initialized : AtomicBool ,
21
134
}
22
135
@@ -27,10 +140,12 @@ impl LSPService {
27
140
}
28
141
29
142
impl Handler for LSPService {
143
+ #[ instrument]
30
144
fn initialized ( & self ) -> bool {
31
145
self . initialized . load ( Ordering :: SeqCst )
32
146
}
33
147
148
+ #[ instrument]
34
149
fn initialize ( & self , req : lsp_types:: InitializeParams ) -> Result < lsp_types:: InitializeResult , ErrorCode > {
35
150
self . initialized . swap ( true , Ordering :: SeqCst ) ;
36
151
Ok ( lsp_types:: InitializeResult {
@@ -112,71 +227,90 @@ impl Handler for LSPService {
112
227
} )
113
228
}
114
229
230
+ #[ instrument]
115
231
fn semantic_tokens_full_request (
116
232
& self ,
117
233
req : lsp_types:: SemanticTokensParams ,
118
234
) -> Result < Option < lsp_types:: SemanticTokensResult > , ErrorCode > {
119
235
let uri = req. text_document . uri ;
120
- let allocator = Bump :: default ( ) ;
121
- if let Some ( source_text) = self . files . get ( & uri) {
122
- trace ! ( "Asked for SemanticTokens" ) ;
123
- let result =
124
- Parser :: new ( & allocator, source_text. as_str ( ) , Features :: default ( ) ) . parse_entirely :: < StyleSheet > ( ) ;
125
- if let Some ( stylesheet) = result. output {
126
- trace ! ( "Sucessfully parsed stylesheet: {:#?}" , & stylesheet) ;
127
- let mut highlighter = TokenHighlighter :: new ( ) ;
128
- stylesheet. accept ( & mut highlighter) ;
129
- let mut current_line = 0 ;
130
- let mut current_start = 0 ;
131
- let data = highlighter
132
- . highlights ( )
133
- . sorted_by ( |a, b| Ord :: cmp ( & a. span ( ) , & b. span ( ) ) )
134
- . map ( |highlight| {
135
- let span_contents = highlight. span ( ) . span_contents ( source_text. as_str ( ) ) ;
136
- let ( line, start) = span_contents. line_and_column ( ) ;
137
- let delta_line = line - current_line;
138
- current_line = line;
139
- let delta_start = if delta_line == 0 { start - current_start } else { start } ;
140
- current_start = start;
141
- lsp_types:: SemanticToken {
142
- token_type : highlight. kind ( ) . bits ( ) as u32 ,
143
- token_modifiers_bitset : highlight. modifier ( ) . bits ( ) as u32 ,
144
- delta_line,
145
- delta_start,
146
- length : span_contents. size ( ) ,
147
- }
148
- } )
149
- . collect ( ) ;
150
- return Ok ( Some ( lsp_types:: SemanticTokensResult :: Tokens ( lsp_types:: SemanticTokens {
151
- result_id : None ,
152
- data,
153
- } ) ) ) ;
154
- } else if !result. errors . is_empty ( ) {
155
- trace ! ( "\n \n Parse on {:?} failed. Saw error {:?}" , & uri, result. errors) ;
156
- }
236
+ trace ! ( "Asked for SemanticTokens for {:?}" , & uri) ;
237
+ if let Some ( document) = self . files . get ( & uri) {
238
+ let mut current_line = 0 ;
239
+ let mut current_start = 0 ;
240
+ // TODO: remove this, figure out a more efficient way to get line/col
241
+ let str = document. to_string ( ) ;
242
+ let data = document
243
+ . get_highlights ( )
244
+ . into_iter ( )
245
+ . map ( |( highlight, delta_line, delta_start) | lsp_types:: SemanticToken {
246
+ token_type : highlight. kind ( ) . bits ( ) as u32 ,
247
+ token_modifiers_bitset : highlight. modifier ( ) . bits ( ) as u32 ,
248
+ delta_line,
249
+ delta_start,
250
+ length : highlight. span ( ) . size ( ) ,
251
+ } )
252
+ . collect ( ) ;
253
+ Ok ( Some ( lsp_types:: SemanticTokensResult :: Tokens ( lsp_types:: SemanticTokens { result_id : None , data } ) ) )
254
+ } else {
255
+ Err ( ErrorCode :: InternalError )
157
256
}
158
- Err ( ErrorCode :: InternalError )
159
257
}
160
258
259
+ #[ instrument]
161
260
fn completion ( & self , req : lsp_types:: CompletionParams ) -> Result < Option < lsp_types:: CompletionResponse > , ErrorCode > {
162
- // let uri = req.text_document.uri;
163
- // let position = req.text_document_position;
164
- // let context = req.context;
165
- Err ( ErrorCode :: UnknownErrorCode )
261
+ let uri = req. text_document_position . text_document . uri ;
262
+ let position = req. text_document_position . position ;
263
+ let context = req. context ;
264
+ Ok ( None )
166
265
}
167
266
267
+ #[ instrument]
168
268
fn on_did_open_text_document ( & self , req : lsp_types:: DidOpenTextDocumentParams ) {
169
269
let uri = req. text_document . uri ;
170
270
let source_text = req. text_document . text ;
171
- self . files . clone ( ) . insert ( uri, source_text) ;
271
+ let mut doc = File :: new ( ) ;
272
+ let mut rope = doc. content . clone ( ) ;
273
+ rope. remove ( 0 ..) ;
274
+ rope. insert ( 0 , & source_text) ;
275
+ trace ! ( "comitting new document {:?} {:?}" , & uri, rope) ;
276
+ doc. commit ( rope) ;
277
+ self . files . clone ( ) . insert ( uri, doc) ;
172
278
}
173
279
280
+ #[ instrument]
174
281
fn on_did_change_text_document ( & self , req : lsp_types:: DidChangeTextDocumentParams ) {
175
282
let uri = req. text_document . uri ;
176
283
let changes = req. content_changes ;
177
- if changes. len ( ) == 1 && changes[ 0 ] . range . is_none ( ) {
178
- let source_text = & changes[ 0 ] . text ;
179
- self . files . clone ( ) . insert ( uri, source_text. into ( ) ) ;
284
+ if let Some ( mut file) = self . files . clone ( ) . get_mut ( & uri) {
285
+ let mut rope = file. content . clone ( ) ;
286
+ for change in changes {
287
+ let range = if let Some ( range) = change. range {
288
+ rope. try_line_to_char ( range. start . line as usize ) . map_or_else (
289
+ |_| ( 0 , None ) ,
290
+ |start| {
291
+ rope. try_line_to_char ( range. end . line as usize ) . map_or_else (
292
+ |_| ( start + range. start . character as usize , None ) ,
293
+ |end| {
294
+ ( start + range. start . character as usize , Some ( end + range. end . character as usize ) )
295
+ } ,
296
+ )
297
+ } ,
298
+ )
299
+ } else {
300
+ ( 0 , None )
301
+ } ;
302
+ match range {
303
+ ( start, None ) => {
304
+ rope. try_remove ( start..) . ok ( ) ;
305
+ rope. try_insert ( start, & change. text ) . ok ( ) ;
306
+ }
307
+ ( start, Some ( end) ) => {
308
+ rope. try_remove ( start..end) . ok ( ) ;
309
+ rope. try_insert ( start, & change. text ) . ok ( ) ;
310
+ }
311
+ }
312
+ }
313
+ file. commit ( rope)
180
314
}
181
315
}
182
316
}
0 commit comments