1use anyhow::{Context, Result};
2use line_index::LineIndex;
3use log::info;
4use lsp_server::{Connection, Message, Notification, Response};
5use lsp_types::{
6 CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams,
7 CodeActionProviderCapability, CodeActionResponse, Command, Diagnostic,
8 DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
9 DocumentSymbol, DocumentSymbolParams, GotoDefinitionParams, GotoDefinitionResponse, Hover,
10 HoverContents, HoverParams, HoverProviderCapability, InitializeParams, InlayHint,
11 InlayHintKind, InlayHintLabel, InlayHintLabelPart, InlayHintParams, LanguageString, Location,
12 MarkedString, OneOf, PublishDiagnosticsParams, ReferenceParams, SelectionRangeParams,
13 SelectionRangeProviderCapability, ServerCapabilities, SymbolKind, TextDocumentSyncCapability,
14 TextDocumentSyncKind, Url, WorkDoneProgressOptions, WorkspaceEdit,
15 notification::{
16 DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, Notification as _,
17 PublishDiagnostics,
18 },
19 request::{
20 CodeActionRequest, DocumentSymbolRequest, GotoDefinition, HoverRequest, InlayHintRequest,
21 References, Request, SelectionRangeRequest,
22 },
23};
24use rowan::TextRange;
25use squawk_ide::code_actions::code_actions;
26use squawk_ide::document_symbols::{DocumentSymbolKind, document_symbols};
27use squawk_ide::find_references::find_references;
28use squawk_ide::goto_definition::goto_definition;
29use squawk_ide::hover::hover;
30use squawk_ide::inlay_hints::inlay_hints;
31use squawk_syntax::SourceFile;
32use std::collections::HashMap;
33
34use diagnostic::DIAGNOSTIC_NAME;
35
36use crate::diagnostic::AssociatedDiagnosticData;
37mod diagnostic;
38mod ignore;
39mod lint;
40mod lsp_utils;
41
42struct DocumentState {
43 content: String,
44 version: i32,
45}
46
47pub fn run() -> Result<()> {
48 info!("Starting Squawk LSP server");
49
50 let (connection, io_threads) = Connection::stdio();
51
52 let server_capabilities = serde_json::to_value(&ServerCapabilities {
53 text_document_sync: Some(TextDocumentSyncCapability::Kind(
54 TextDocumentSyncKind::INCREMENTAL,
55 )),
56 code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
57 code_action_kinds: Some(vec![
58 CodeActionKind::QUICKFIX,
59 CodeActionKind::REFACTOR_REWRITE,
60 ]),
61 work_done_progress_options: WorkDoneProgressOptions {
62 work_done_progress: None,
63 },
64 resolve_provider: None,
65 })),
66 selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)),
67 references_provider: Some(OneOf::Left(true)),
68 definition_provider: Some(OneOf::Left(true)),
69 hover_provider: Some(HoverProviderCapability::Simple(true)),
70 inlay_hint_provider: Some(OneOf::Left(true)),
71 document_symbol_provider: Some(OneOf::Left(true)),
72 ..Default::default()
73 })
74 .unwrap();
75
76 info!("LSP server initializing connection...");
77 let initialization_params = connection.initialize(server_capabilities)?;
78 info!("LSP server initialized, entering main loop");
79
80 main_loop(connection, initialization_params)?;
81
82 info!("LSP server shutting down");
83
84 io_threads.join()?;
85 Ok(())
86}
87
88fn main_loop(connection: Connection, params: serde_json::Value) -> Result<()> {
89 info!("Server main loop");
90
91 let init_params: InitializeParams = serde_json::from_value(params).unwrap_or_default();
92 info!("Client process ID: {:?}", init_params.process_id);
93 let client_name = init_params.client_info.map(|x| x.name);
94 info!("Client name: {client_name:?}");
95
96 let mut documents: HashMap<Url, DocumentState> = HashMap::new();
97
98 for msg in &connection.receiver {
99 match msg {
100 Message::Request(req) => {
101 info!("Received request: method={}, id={:?}", req.method, req.id);
102
103 if connection.handle_shutdown(&req)? {
104 info!("Received shutdown request, exiting");
105 return Ok(());
106 }
107
108 match req.method.as_ref() {
109 GotoDefinition::METHOD => {
110 handle_goto_definition(&connection, req, &documents)?;
111 }
112 HoverRequest::METHOD => {
113 handle_hover(&connection, req, &documents)?;
114 }
115 CodeActionRequest::METHOD => {
116 handle_code_action(&connection, req, &documents)?;
117 }
118 SelectionRangeRequest::METHOD => {
119 handle_selection_range(&connection, req, &documents)?;
120 }
121 InlayHintRequest::METHOD => {
122 handle_inlay_hints(&connection, req, &documents)?;
123 }
124 DocumentSymbolRequest::METHOD => {
125 handle_document_symbol(&connection, req, &documents)?;
126 }
127 "squawk/syntaxTree" => {
128 handle_syntax_tree(&connection, req, &documents)?;
129 }
130 "squawk/tokens" => {
131 handle_tokens(&connection, req, &documents)?;
132 }
133 References::METHOD => {
134 handle_references(&connection, req, &documents)?;
135 }
136 _ => {
137 info!("Ignoring unhandled request: {}", req.method);
138 }
139 }
140 }
141 Message::Response(resp) => {
142 info!("Received response: id={:?}", resp.id);
143 }
144 Message::Notification(notif) => {
145 info!("Received notification: method={}", notif.method);
146 match notif.method.as_ref() {
147 DidOpenTextDocument::METHOD => {
148 handle_did_open(&connection, notif, &mut documents)?;
149 }
150 DidChangeTextDocument::METHOD => {
151 handle_did_change(&connection, notif, &mut documents)?;
152 }
153 DidCloseTextDocument::METHOD => {
154 handle_did_close(&connection, notif, &mut documents)?;
155 }
156 _ => {
157 info!("Ignoring unhandled notification: {}", notif.method);
158 }
159 }
160 }
161 }
162 }
163 Ok(())
164}
165
166fn handle_goto_definition(
167 connection: &Connection,
168 req: lsp_server::Request,
169 documents: &HashMap<Url, DocumentState>,
170) -> Result<()> {
171 let params: GotoDefinitionParams = serde_json::from_value(req.params)?;
172 let uri = params.text_document_position_params.text_document.uri;
173 let position = params.text_document_position_params.position;
174
175 let content = documents.get(&uri).map_or("", |doc| &doc.content);
176 let parse = SourceFile::parse(content);
177 let file = parse.tree();
178 let line_index = LineIndex::new(content);
179 let offset = lsp_utils::offset(&line_index, position).unwrap();
180
181 let range = goto_definition(file, offset);
182
183 let result = match range {
184 Some(target_range) => {
185 debug_assert!(
186 !target_range.contains(offset),
187 "Our target destination range must not include the source range otherwise go to def won't work in vscode."
188 );
189 GotoDefinitionResponse::Scalar(Location {
190 uri: uri.clone(),
191 range: lsp_utils::range(&line_index, target_range),
192 })
193 }
194 None => GotoDefinitionResponse::Array(vec![]),
195 };
196
197 let resp = Response {
198 id: req.id,
199 result: Some(serde_json::to_value(&result).unwrap()),
200 error: None,
201 };
202
203 connection.sender.send(Message::Response(resp))?;
204 Ok(())
205}
206
207fn handle_hover(
208 connection: &Connection,
209 req: lsp_server::Request,
210 documents: &HashMap<Url, DocumentState>,
211) -> Result<()> {
212 let params: HoverParams = serde_json::from_value(req.params)?;
213 let uri = params.text_document_position_params.text_document.uri;
214 let position = params.text_document_position_params.position;
215
216 let content = documents.get(&uri).map_or("", |doc| &doc.content);
217 let parse = SourceFile::parse(content);
218 let file = parse.tree();
219 let line_index = LineIndex::new(content);
220 let offset = lsp_utils::offset(&line_index, position).unwrap();
221
222 let type_info = hover(&file, offset);
223
224 let result = type_info.map(|type_str| Hover {
225 contents: HoverContents::Scalar(MarkedString::LanguageString(LanguageString {
226 language: "sql".to_string(),
227 value: type_str,
228 })),
229 range: None,
230 });
231
232 let resp = Response {
233 id: req.id,
234 result: Some(serde_json::to_value(&result).unwrap()),
235 error: None,
236 };
237
238 connection.sender.send(Message::Response(resp))?;
239 Ok(())
240}
241
242fn handle_inlay_hints(
243 connection: &Connection,
244 req: lsp_server::Request,
245 documents: &HashMap<Url, DocumentState>,
246) -> Result<()> {
247 let params: InlayHintParams = serde_json::from_value(req.params)?;
248 let uri = params.text_document.uri;
249
250 let content = documents.get(&uri).map_or("", |doc| &doc.content);
251 let parse = SourceFile::parse(content);
252 let file = parse.tree();
253 let line_index = LineIndex::new(content);
254
255 let hints = inlay_hints(&file);
256
257 let lsp_hints: Vec<InlayHint> = hints
258 .into_iter()
259 .map(|hint| {
260 let line_col = line_index.line_col(hint.position);
261 let position = lsp_types::Position::new(line_col.line, line_col.col);
262 let kind = match hint.kind {
263 squawk_ide::inlay_hints::InlayHintKind::Type => InlayHintKind::TYPE,
264 squawk_ide::inlay_hints::InlayHintKind::Parameter => InlayHintKind::PARAMETER,
265 };
266
267 let label = if let Some(target_range) = hint.target {
268 InlayHintLabel::LabelParts(vec![InlayHintLabelPart {
269 value: hint.label,
270 location: Some(Location {
271 uri: uri.clone(),
272 range: lsp_utils::range(&line_index, target_range),
273 }),
274 tooltip: None,
275 command: None,
276 }])
277 } else {
278 InlayHintLabel::String(hint.label)
279 };
280
281 InlayHint {
282 position,
283 label,
284 kind: Some(kind),
285 text_edits: None,
286 tooltip: None,
287 padding_left: None,
288 padding_right: None,
289 data: None,
290 }
291 })
292 .collect();
293
294 let resp = Response {
295 id: req.id,
296 result: Some(serde_json::to_value(&lsp_hints).unwrap()),
297 error: None,
298 };
299
300 connection.sender.send(Message::Response(resp))?;
301 Ok(())
302}
303
304fn handle_document_symbol(
305 connection: &Connection,
306 req: lsp_server::Request,
307 documents: &HashMap<Url, DocumentState>,
308) -> Result<()> {
309 let params: DocumentSymbolParams = serde_json::from_value(req.params)?;
310 let uri = params.text_document.uri;
311
312 let content = documents.get(&uri).map_or("", |doc| &doc.content);
313 let parse = SourceFile::parse(content);
314 let file = parse.tree();
315 let line_index = LineIndex::new(content);
316
317 let symbols = document_symbols(&file);
318
319 fn convert_symbol(
320 sym: squawk_ide::document_symbols::DocumentSymbol,
321 line_index: &LineIndex,
322 ) -> DocumentSymbol {
323 let range = lsp_utils::range(line_index, sym.full_range);
324 let selection_range = lsp_utils::range(line_index, sym.focus_range);
325
326 let children = sym
327 .children
328 .into_iter()
329 .map(|child| convert_symbol(child, line_index))
330 .collect::<Vec<_>>();
331
332 let children = (!children.is_empty()).then_some(children);
333
334 DocumentSymbol {
335 name: sym.name,
336 detail: sym.detail,
337 kind: match sym.kind {
338 DocumentSymbolKind::Table => SymbolKind::STRUCT,
339 DocumentSymbolKind::Function => SymbolKind::FUNCTION,
340 DocumentSymbolKind::Type => SymbolKind::CLASS,
341 DocumentSymbolKind::Column => SymbolKind::FIELD,
342 DocumentSymbolKind::Variant => SymbolKind::ENUM_MEMBER,
343 },
344 tags: None,
345 range,
346 selection_range,
347 children,
348 #[allow(deprecated)]
349 deprecated: None,
350 }
351 }
352
353 let lsp_symbols: Vec<DocumentSymbol> = symbols
354 .into_iter()
355 .map(|sym| convert_symbol(sym, &line_index))
356 .collect();
357
358 let resp = Response {
359 id: req.id,
360 result: Some(serde_json::to_value(&lsp_symbols).unwrap()),
361 error: None,
362 };
363
364 connection.sender.send(Message::Response(resp))?;
365 Ok(())
366}
367
368fn handle_selection_range(
369 connection: &Connection,
370 req: lsp_server::Request,
371 documents: &HashMap<Url, DocumentState>,
372) -> Result<()> {
373 let params: SelectionRangeParams = serde_json::from_value(req.params)?;
374 let uri = params.text_document.uri;
375
376 let content = documents.get(&uri).map_or("", |doc| &doc.content);
377 let parse = SourceFile::parse(content);
378 let root = parse.syntax_node();
379 let line_index = LineIndex::new(content);
380
381 let mut selection_ranges = vec![];
382
383 for position in params.positions {
384 let Some(offset) = lsp_utils::offset(&line_index, position) else {
385 continue;
386 };
387
388 let mut ranges = Vec::new();
389 {
390 let mut range = TextRange::new(offset, offset);
391 loop {
392 ranges.push(range);
393 let next = squawk_ide::expand_selection::extend_selection(&root, range);
394 if next == range {
395 break;
396 } else {
397 range = next
398 }
399 }
400 }
401
402 let mut range = lsp_types::SelectionRange {
403 range: lsp_utils::range(&line_index, *ranges.last().unwrap()),
404 parent: None,
405 };
406 for &r in ranges.iter().rev().skip(1) {
407 range = lsp_types::SelectionRange {
408 range: lsp_utils::range(&line_index, r),
409 parent: Some(Box::new(range)),
410 }
411 }
412 selection_ranges.push(range);
413 }
414
415 let resp = Response {
416 id: req.id,
417 result: Some(serde_json::to_value(&selection_ranges).unwrap()),
418 error: None,
419 };
420
421 connection.sender.send(Message::Response(resp))?;
422 Ok(())
423}
424
425fn handle_references(
426 connection: &Connection,
427 req: lsp_server::Request,
428 documents: &HashMap<Url, DocumentState>,
429) -> Result<()> {
430 let params: ReferenceParams = serde_json::from_value(req.params)?;
431 let uri = params.text_document_position.text_document.uri;
432 let position = params.text_document_position.position;
433
434 let content = documents.get(&uri).map_or("", |doc| &doc.content);
435 let parse = SourceFile::parse(content);
436 let file = parse.tree();
437 let line_index = LineIndex::new(content);
438 let offset = lsp_utils::offset(&line_index, position).unwrap();
439
440 let ranges = find_references(&file, offset);
441 let include_declaration = params.context.include_declaration;
442
443 let locations: Vec<Location> = ranges
444 .into_iter()
445 .filter(|range| include_declaration || !range.contains(offset))
446 .map(|range| Location {
447 uri: uri.clone(),
448 range: lsp_utils::range(&line_index, range),
449 })
450 .collect();
451
452 let resp = Response {
453 id: req.id,
454 result: Some(serde_json::to_value(&locations).unwrap()),
455 error: None,
456 };
457
458 connection.sender.send(Message::Response(resp))?;
459 Ok(())
460}
461
462fn handle_code_action(
463 connection: &Connection,
464 req: lsp_server::Request,
465 documents: &HashMap<Url, DocumentState>,
466) -> Result<()> {
467 let params: CodeActionParams = serde_json::from_value(req.params)?;
468 let uri = params.text_document.uri;
469
470 let mut actions: CodeActionResponse = Vec::new();
471
472 let content = documents.get(&uri).map_or("", |doc| &doc.content);
473 let parse = SourceFile::parse(content);
474 let file = parse.tree();
475 let line_index = LineIndex::new(content);
476 let offset = lsp_utils::offset(&line_index, params.range.start).unwrap();
477
478 let ide_actions = code_actions(file, offset).unwrap_or_default();
479
480 for action in ide_actions {
481 let lsp_action = lsp_utils::code_action(&line_index, uri.clone(), action);
482 actions.push(CodeActionOrCommand::CodeAction(lsp_action));
483 }
484
485 for mut diagnostic in params
486 .context
487 .diagnostics
488 .into_iter()
489 .filter(|diagnostic| diagnostic.source.as_deref() == Some(DIAGNOSTIC_NAME))
490 {
491 let Some(rule_name) = diagnostic.code.as_ref().map(|x| match x {
492 lsp_types::NumberOrString::String(s) => s.clone(),
493 lsp_types::NumberOrString::Number(n) => n.to_string(),
494 }) else {
495 continue;
496 };
497 let Some(data) = diagnostic.data.take() else {
498 continue;
499 };
500
501 let associated_data: AssociatedDiagnosticData =
502 serde_json::from_value(data).context("deserializing diagnostic data")?;
503
504 if let Some(ignore_line_edit) = associated_data.ignore_line_edit {
505 let disable_line_action = CodeAction {
506 title: format!("Disable {rule_name} for this line"),
507 kind: Some(CodeActionKind::QUICKFIX),
508 diagnostics: Some(vec![diagnostic.clone()]),
509 edit: Some(WorkspaceEdit {
510 changes: Some({
511 let mut changes = HashMap::new();
512 changes.insert(uri.clone(), vec![ignore_line_edit]);
513 changes
514 }),
515 ..Default::default()
516 }),
517 command: None,
518 is_preferred: Some(false),
519 disabled: None,
520 data: None,
521 };
522 actions.push(CodeActionOrCommand::CodeAction(disable_line_action));
523 }
524 if let Some(ignore_file_edit) = associated_data.ignore_file_edit {
525 let disable_file_action = CodeAction {
526 title: format!("Disable {rule_name} for the entire file"),
527 kind: Some(CodeActionKind::QUICKFIX),
528 diagnostics: Some(vec![diagnostic.clone()]),
529 edit: Some(WorkspaceEdit {
530 changes: Some({
531 let mut changes = HashMap::new();
532 changes.insert(uri.clone(), vec![ignore_file_edit]);
533 changes
534 }),
535 ..Default::default()
536 }),
537 command: None,
538 is_preferred: Some(false),
539 disabled: None,
540 data: None,
541 };
542 actions.push(CodeActionOrCommand::CodeAction(disable_file_action));
543 }
544
545 let title = format!("Show documentation for {rule_name}");
546 let documentation_action = CodeAction {
547 title: title.clone(),
548 kind: Some(CodeActionKind::QUICKFIX),
549 diagnostics: Some(vec![diagnostic.clone()]),
550 edit: None,
551 command: Some(Command {
552 title,
553 command: "vscode.open".to_string(),
554 arguments: Some(vec![serde_json::to_value(format!(
555 "https://squawkhq.com/docs/{rule_name}"
556 ))?]),
557 }),
558 is_preferred: Some(false),
559 disabled: None,
560 data: None,
561 };
562 actions.push(CodeActionOrCommand::CodeAction(documentation_action));
563
564 if !associated_data.title.is_empty() && !associated_data.edits.is_empty() {
565 let fix_action = CodeAction {
566 title: associated_data.title,
567 kind: Some(CodeActionKind::QUICKFIX),
568 diagnostics: Some(vec![diagnostic.clone()]),
569 edit: Some(WorkspaceEdit {
570 changes: Some({
571 let mut changes = HashMap::new();
572 changes.insert(uri.clone(), associated_data.edits);
573 changes
574 }),
575 ..Default::default()
576 }),
577 command: None,
578 is_preferred: Some(true),
579 disabled: None,
580 data: None,
581 };
582 actions.push(CodeActionOrCommand::CodeAction(fix_action));
583 }
584 }
585
586 let result: CodeActionResponse = actions;
587 let resp = Response {
588 id: req.id,
589 result: Some(serde_json::to_value(&result).unwrap()),
590 error: None,
591 };
592
593 connection.sender.send(Message::Response(resp))?;
594 Ok(())
595}
596
597fn publish_diagnostics(
598 connection: &Connection,
599 uri: Url,
600 version: i32,
601 diagnostics: Vec<Diagnostic>,
602) -> Result<()> {
603 let publish_params = PublishDiagnosticsParams {
604 uri,
605 diagnostics,
606 version: Some(version),
607 };
608
609 let notification = Notification {
610 method: PublishDiagnostics::METHOD.to_owned(),
611 params: serde_json::to_value(publish_params)?,
612 };
613
614 connection
615 .sender
616 .send(Message::Notification(notification))?;
617 Ok(())
618}
619
620fn handle_did_open(
621 connection: &Connection,
622 notif: lsp_server::Notification,
623 documents: &mut HashMap<Url, DocumentState>,
624) -> Result<()> {
625 let params: DidOpenTextDocumentParams = serde_json::from_value(notif.params)?;
626 let uri = params.text_document.uri;
627 let content = params.text_document.text;
628 let version = params.text_document.version;
629
630 documents.insert(uri.clone(), DocumentState { content, version });
631
632 let content = documents.get(&uri).map_or("", |doc| &doc.content);
633
634 let diagnostics = lint::lint(content);
636 publish_diagnostics(connection, uri, version, diagnostics)?;
637
638 Ok(())
639}
640
641fn handle_did_change(
642 connection: &Connection,
643 notif: lsp_server::Notification,
644 documents: &mut HashMap<Url, DocumentState>,
645) -> Result<()> {
646 let params: DidChangeTextDocumentParams = serde_json::from_value(notif.params)?;
647 let uri = params.text_document.uri;
648 let version = params.text_document.version;
649
650 let Some(doc_state) = documents.get_mut(&uri) else {
651 return Ok(());
652 };
653
654 doc_state.content =
655 lsp_utils::apply_incremental_changes(&doc_state.content, params.content_changes);
656 doc_state.version = version;
657
658 let diagnostics = lint::lint(&doc_state.content);
659 publish_diagnostics(connection, uri, version, diagnostics)?;
660
661 Ok(())
662}
663
664fn handle_did_close(
665 connection: &Connection,
666 notif: lsp_server::Notification,
667 documents: &mut HashMap<Url, DocumentState>,
668) -> Result<()> {
669 let params: DidCloseTextDocumentParams = serde_json::from_value(notif.params)?;
670 let uri = params.text_document.uri;
671
672 documents.remove(&uri);
673
674 let publish_params = PublishDiagnosticsParams {
675 uri,
676 diagnostics: vec![],
677 version: None,
678 };
679
680 let notification = Notification {
681 method: PublishDiagnostics::METHOD.to_owned(),
682 params: serde_json::to_value(publish_params)?,
683 };
684
685 connection
686 .sender
687 .send(Message::Notification(notification))?;
688
689 Ok(())
690}
691
692#[derive(serde::Deserialize)]
693struct SyntaxTreeParams {
694 #[serde(rename = "textDocument")]
695 text_document: lsp_types::TextDocumentIdentifier,
696}
697
698fn handle_syntax_tree(
699 connection: &Connection,
700 req: lsp_server::Request,
701 documents: &HashMap<Url, DocumentState>,
702) -> Result<()> {
703 let params: SyntaxTreeParams = serde_json::from_value(req.params)?;
704 let uri = params.text_document.uri;
705
706 info!("Generating syntax tree for: {uri}");
707
708 let content = documents.get(&uri).map_or("", |doc| &doc.content);
709
710 let parse = SourceFile::parse(content);
711 let syntax_tree = format!("{:#?}", parse.syntax_node());
712
713 let resp = Response {
714 id: req.id,
715 result: Some(serde_json::to_value(&syntax_tree).unwrap()),
716 error: None,
717 };
718
719 connection.sender.send(Message::Response(resp))?;
720 Ok(())
721}
722
723#[derive(serde::Deserialize)]
724struct TokensParams {
725 #[serde(rename = "textDocument")]
726 text_document: lsp_types::TextDocumentIdentifier,
727}
728
729fn handle_tokens(
730 connection: &Connection,
731 req: lsp_server::Request,
732 documents: &HashMap<Url, DocumentState>,
733) -> Result<()> {
734 let params: TokensParams = serde_json::from_value(req.params)?;
735 let uri = params.text_document.uri;
736
737 info!("Generating tokens for: {uri}");
738
739 let content = documents.get(&uri).map_or("", |doc| &doc.content);
740
741 let tokens = squawk_lexer::tokenize(content);
742
743 let mut output = Vec::new();
744 let mut char_pos = 0;
745 for token in tokens {
746 let token_start = char_pos;
747 let token_end = token_start + token.len as usize;
748 let token_text = &content[token_start..token_end];
749 output.push(format!(
750 "{:?}@{}..{} {:?}",
751 token.kind, token_start, token_end, token_text
752 ));
753 char_pos = token_end;
754 }
755
756 let tokens_output = output.join("\n");
757
758 let resp = Response {
759 id: req.id,
760 result: Some(serde_json::to_value(&tokens_output).unwrap()),
761 error: None,
762 };
763
764 connection.sender.send(Message::Response(resp))?;
765 Ok(())
766}