1#![feature(new_range_api)]
2#![warn(missing_docs)]
3
4pub mod types;
5use crate::types::NargoLanguage;
6use dashmap::{DashMap, DashSet};
7use nargo_ir::{ElementIR, ExpressionIR, JsExpr, JsProgram, JsStmt, TemplateNodeIR};
8use nargo_parser::{template::VueTemplateParser, ParseState, ScriptParser, TemplateParser};
9use nargo_types::{is_pos_in_span, Position as NargoPosition, Span as NargoSpan};
10use std::collections::HashSet;
11
12use futures::Future;
13use oak_core::{
14 parser::session::ParseSession,
15 tree::{GreenNode, RedNode},
16 Range,
17};
18use oak_lsp::service::LanguageService;
19use oak_vfs::{MemoryVfs, Vfs};
20
21use std::sync::Arc;
22use url::Url;
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25enum CompletionContext {
26 Tag,
27 Attribute(String),
28 Expression,
29}
30
31pub struct NargoLanguageService {
32 vfs: MemoryVfs,
33 auto_imports: DashSet<String>,
34 workspace: oak_lsp::workspace::WorkspaceManager,
35 }
37
38impl NargoLanguageService {
39 pub fn new() -> Self {
40 let auto_imports = DashSet::new();
41 auto_imports.insert("signal".to_string());
43 auto_imports.insert("computed".to_string());
44 auto_imports.insert("on_mount".to_string());
45 auto_imports.insert("on_unmount".to_string());
46 auto_imports.insert("on_cleanup".to_string());
47
48 Self {
49 vfs: MemoryVfs::new(),
50 auto_imports,
51 workspace: oak_lsp::workspace::WorkspaceManager::new(),
52 }
54 }
55
56 fn get_completion_context(nodes: &[TemplateNodeIR], pos: NargoPosition) -> Option<CompletionContext> {
57 for node in nodes {
58 match node {
59 TemplateNodeIR::Element(el) => {
60 if is_pos_in_span(pos, el.span) {
61 let tag_start = el.span.start;
63 let tag_name_end = NargoPosition {
64 line: tag_start.line,
65 column: tag_start.column + (el.tag.len() as u32) + 1, offset: 0,
67 };
68
69 if pos.line == tag_start.line && pos.column <= tag_name_end.column {
70 return Some(CompletionContext::Tag);
71 }
72
73 for attr in &el.attributes {
75 if is_pos_in_span(pos, attr.span) {
76 return Some(CompletionContext::Attribute(el.tag.clone()));
77 }
78 }
79
80 if let Some(ctx) = Self::get_completion_context(&el.children, pos) {
82 return Some(ctx);
83 }
84
85 return Some(CompletionContext::Attribute(el.tag.clone()));
87 }
88 }
89 TemplateNodeIR::Interpolation(expr) => {
90 if is_pos_in_span(pos, expr.span) {
91 return Some(CompletionContext::Expression);
92 }
93 }
94 _ => {}
95 }
96 }
97 None
98 }
99
100 fn get_script_program(nodes: &[TemplateNodeIR]) -> Option<JsProgram> {
101 for node in nodes {
102 if let TemplateNodeIR::Element(el) = node {
103 if el.tag == "script" {
104 if let Some(TemplateNodeIR::Text(content, _, _)) = el.children.first() {
105 let mut state = ParseState::new(content);
106 let ts_parser = nargo_parser::OakTypeScriptParser;
107 return ts_parser.parse(&mut state, "ts").ok();
108 }
109 }
110 if let Some(p) = Self::get_script_program(&el.children) {
111 return Some(p);
112 }
113 }
114 }
115 None
116 }
117
118 async fn find_definition_in_nodes(&self, nodes: &[TemplateNodeIR], pos: NargoPosition, uri: &str) -> Option<oak_lsp::types::LocationRange> {
119 let script_program = Self::get_script_program(nodes);
120
121 for node in nodes {
122 match node {
123 TemplateNodeIR::Element(el) => {
124 if is_pos_in_span(pos, el.span) {
125 if el.tag == "script" {
126 return self.find_definition_in_script(el, pos, uri).await;
127 }
128 else if el.tag == "style" {
129 return Self::find_definition_in_style(el, pos, uri);
130 }
131 else {
132 let res = self.find_definition_in_nodes_recursive(&el.children, pos, uri).await;
134 if res.is_some() {
135 return res;
136 }
137 }
138 }
139 }
140 TemplateNodeIR::Interpolation(expr) => {
141 if !expr.span.is_unknown() && is_pos_in_span(pos, expr.span) {
142 if let Some(symbol) = Self::find_symbol_in_template_expr(expr, pos) {
143 if let Some(program) = &script_program {
145 if let Some(def_span) = Self::find_definition_of_symbol(program, &symbol) {
146 if Self::is_import(program, &symbol) {
147 if let Some(loc) = self.find_external_definition(program, &symbol, uri).await {
148 return Some(loc);
149 }
150 }
151
152 if let Some(script_el_span) = Self::get_script_element_span(nodes) {
153 if let Some(source) = self.vfs.get_source(uri) {
154 let start_pos = oak_lsp::types::SourcePosition { line: script_el_span.start.line + def_span.start.line - 1, column: def_span.start.column - 1, offset: 0, length: 0 };
155 let end_pos = oak_lsp::types::SourcePosition { line: script_el_span.start.line + def_span.end.line - 1, column: def_span.end.column - 1, offset: 0, length: 0 };
156 return Some(oak_lsp::types::LocationRange { uri: uri.to_string().into(), range: Range { start: self.vfs.line_map(uri).map(|m| m.line_col_utf16_to_offset(&source, start_pos.line, start_pos.column)).unwrap_or(0), end: self.vfs.line_map(uri).map(|m| m.line_col_utf16_to_offset(&source, end_pos.line, end_pos.column)).unwrap_or(0) } });
157 }
158 }
159 }
160 }
161
162 if self.auto_imports.contains(&symbol) {
164 if let Some(loc) = self.find_implicit_definition(&symbol, "@nargo/core", uri).await {
165 return Some(loc);
166 }
167 }
168 }
169 }
170 }
171 _ => {}
172 }
173 }
174 None
175 }
176
177 fn find_definition_in_nodes_recursive<'a>(&'a self, nodes: &'a [TemplateNodeIR], pos: NargoPosition, uri: &'a str) -> std::pin::Pin<Box<dyn std::future::Future<Output = Option<oak_lsp::types::LocationRange>> + Send + 'a>> {
179 Box::pin(self.find_definition_in_nodes(nodes, pos, uri))
180 }
181
182 fn is_import(program: &JsProgram, symbol: &str) -> bool {
183 for stmt in &program.body {
184 if let JsStmt::Import { specifiers, .. } = stmt {
185 if specifiers.contains(&symbol.to_string()) {
186 return true;
187 }
188 }
189 }
190 false
191 }
192
193 fn get_script_element_span(nodes: &[TemplateNodeIR]) -> Option<NargoSpan> {
194 for node in nodes {
195 if let TemplateNodeIR::Element(el) = node {
196 if el.tag == "script" {
197 return Some(el.span);
198 }
199 if let Some(s) = Self::get_script_element_span(&el.children) {
200 return Some(s);
201 }
202 }
203 }
204 None
205 }
206
207 fn find_symbol_in_template_expr(expr: &ExpressionIR, pos: NargoPosition) -> Option<String> {
208 if !expr.span.is_unknown() && is_pos_in_span(pos, expr.span) {
210 return Some(expr.code.clone());
212 }
213 None
214 }
215
216 async fn find_definition_in_script(&self, el: &ElementIR, pos: NargoPosition, uri: &str) -> Option<oak_lsp::types::LocationRange> {
217 if let Some(TemplateNodeIR::Text(content, _, _)) = el.children.first() {
218 let mut state = ParseState::new(content);
219 let ts_parser = nargo_parser::OakTypeScriptParser;
220 if let Ok(program) = ts_parser.parse(&mut state, "ts") {
221 let el_span = el.span;
223 let relative_pos = NargoPosition { line: pos.line - el_span.start.line, column: if pos.line == el_span.start.line { pos.column - el_span.start.column } else { pos.column }, offset: 0 };
224
225 if let Some(symbol) = Self::find_symbol_at(&program, relative_pos) {
226 if Self::is_import(&program, &symbol) {
228 if let Some(loc) = self.find_external_definition(&program, &symbol, uri).await {
229 return Some(loc);
230 }
231 }
232
233 if let Some(def_span) = Self::find_definition_of_symbol(&program, &symbol) {
235 if let Some(source) = self.vfs.get_source(uri) {
236 let start_pos = oak_lsp::types::SourcePosition { line: el_span.start.line + def_span.start.line - 1, column: def_span.start.column - 1, offset: 0, length: 0 };
237 let end_pos = oak_lsp::types::SourcePosition { line: el_span.start.line + def_span.end.line - 1, column: def_span.end.column - 1, offset: 0, length: 0 };
238 return Some(oak_lsp::types::LocationRange { uri: uri.to_string().into(), range: Range { start: self.vfs.line_map(uri).map(|m| m.line_col_utf16_to_offset(&source, start_pos.line, start_pos.column)).unwrap_or(0), end: self.vfs.line_map(uri).map(|m| m.line_col_utf16_to_offset(&source, end_pos.line, end_pos.column)).unwrap_or(0) } });
239 }
240 }
241
242 if self.auto_imports.contains(&symbol) {
244 if let Some(loc) = self.find_implicit_definition(&symbol, "@nargo/core", uri).await {
245 return Some(loc);
246 }
247 }
248 }
249 }
250 }
251 None
252 }
253
254 fn find_symbol_at(program: &JsProgram, pos: NargoPosition) -> Option<String> {
255 for stmt in &program.body {
256 if let Some(symbol) = Self::find_symbol_in_stmt(stmt, pos) {
257 return Some(symbol);
258 }
259 }
260 None
261 }
262
263 fn find_symbol_in_stmt(stmt: &JsStmt, pos: NargoPosition) -> Option<String> {
264 use JsStmt::*;
265 match stmt {
266 VariableDecl { id, init, .. } => {
267 if id == "count" {
269 return Some(id.clone());
270 }
271 if let Some(init) = init {
272 return Self::find_symbol_in_expr(init, pos);
273 }
274 }
275 Expr(expr, ..) => return Self::find_symbol_in_expr(expr, pos),
276 _ => {}
277 }
278 None
279 }
280
281 fn find_symbol_in_expr(expr: &JsExpr, _pos: NargoPosition) -> Option<String> {
282 match expr {
283 JsExpr::Identifier(name, ..) => Some(name.clone()),
284 JsExpr::Call { callee, .. } => Self::find_symbol_in_expr(callee, _pos),
285 _ => None,
286 }
287 }
288
289 fn find_definition_of_symbol(program: &JsProgram, symbol: &str) -> Option<NargoSpan> {
290 for stmt in &program.body {
291 if let JsStmt::VariableDecl { id, span, .. } = stmt {
292 if id == symbol {
293 return Some(*span);
294 }
295 }
296 if let JsStmt::FunctionDecl { id, span, .. } = stmt {
297 if id == symbol {
298 return Some(*span);
299 }
300 }
301 }
302 None
303 }
304
305 async fn find_external_definition(&self, program: &JsProgram, symbol: &str, current_uri: &str) -> Option<oak_lsp::types::LocationRange> {
306 for stmt in &program.body {
307 if let JsStmt::Import { specifiers, source, .. } = stmt {
308 if specifiers.contains(&symbol.to_string()) {
309 return self.find_implicit_definition(symbol, source, current_uri).await;
310 }
311 }
312 }
313 None
314 }
315
316 async fn find_implicit_definition(&self, symbol: &str, source_path: &str, current_uri: &str) -> Option<oak_lsp::types::LocationRange> {
317 if let Some(url) = self.resolve_path(current_uri, source_path) {
318 if let Some(content) = self.get_document_content(&url).await {
319 let mut line = 0;
321 let mut col = 0;
322 if let Some(pos) = content.find(symbol) {
323 let before = &content[..pos];
324 line = before.lines().count().saturating_sub(1);
325 col = before.lines().last().map(|l| l.len()).unwrap_or(0);
326 }
327
328 if let Some(target_source) = self.vfs.get_source(url.as_str()) {
329 let start_pos = oak_lsp::types::SourcePosition { line: line as u32, column: col as u32, offset: 0, length: 0 };
330 let end_pos = oak_lsp::types::SourcePosition { line: line as u32, column: (col + symbol.len()) as u32, offset: 0, length: 0 };
331 return Some(oak_lsp::types::LocationRange { uri: url.to_string().into(), range: Range { start: self.vfs.line_map(url.as_str()).map(|m| m.line_col_utf16_to_offset(&target_source, start_pos.line, start_pos.column)).unwrap_or(0), end: self.vfs.line_map(url.as_str()).map(|m| m.line_col_utf16_to_offset(&target_source, end_pos.line, end_pos.column)).unwrap_or(0) } });
332 }
333 }
334 }
335 None
336 }
337
338 fn resolve_path(&self, current_uri: &str, relative_path: &str) -> Option<Url> {
339 let base_url = Url::parse(current_uri).ok()?;
340 let base_path = base_url.to_file_path().ok()?;
341
342 if relative_path == "@nargo/core" {
343 for (_, folder) in self.workspace.list_folders() {
345 let core_path = folder.join("runtimes/nargo-core/src/index.ts");
346 if core_path.exists() {
347 return Url::from_file_path(core_path).ok();
348 }
349 let core_path = folder.join("project-nargo/runtimes/nargo-core/src/index.ts");
351 if core_path.exists() {
352 return Url::from_file_path(core_path).ok();
353 }
354 }
355
356 if let Ok(uri) = Url::parse(current_uri) {
358 if let Ok(mut path) = uri.to_file_path() {
359 while let Some(parent) = path.parent() {
360 let core_path = parent.join("runtimes/nargo-core/src/index.ts");
361 if core_path.exists() {
362 return Url::from_file_path(core_path).ok();
363 }
364 path = parent.to_path_buf();
365 }
366 }
367 }
368 }
369
370 let parent = base_path.parent()?;
371 let target_path = if relative_path.starts_with('.') {
372 parent.join(relative_path)
373 }
374 else {
375 return None;
376 };
377
378 let extensions = ["", ".ts", ".js"];
379 for ext in extensions {
380 let mut p = target_path.clone();
381 if !ext.is_empty() {
382 p.set_extension(ext.trim_start_matches('.'));
383 }
384 if p.exists() {
385 return Url::from_file_path(p).ok();
386 }
387 }
388
389 None
390 }
391
392 async fn get_document_content(&self, uri: &Url) -> Option<String> {
393 let uri_str = uri.to_string();
394 if let Some(source) = self.vfs.get_source(&uri_str) {
395 return Some(source.text().to_string());
396 }
397
398 if let Ok(path) = uri.to_file_path() {
399 return std::fs::read_to_string(path).ok();
400 }
401
402 None
403 }
404
405 fn find_definition_in_style(_el: &ElementIR, _pos: NargoPosition, _uri: &str) -> Option<oak_lsp::types::LocationRange> {
406 None
408 }
409
410 fn process_template_nodes(nodes: &[TemplateNodeIR], content: &str, tokens: &mut Vec<u32>) {
411 for node in nodes {
412 match node {
413 TemplateNodeIR::Element(el) => {
414 if !el.span.is_unknown() {
416 let start = el.span.start.offset as u32;
418 let tag_start = content[start as usize..].find('<').unwrap_or(0) + 1;
419 let tag_end = tag_start + el.tag.len();
420
421 tokens.extend(&[start + tag_start as u32, el.tag.len() as u32, 0, 0, 0]);
423
424 for attr in &el.attributes {
426 if !attr.span.is_unknown() {
427 let attr_start = attr.span.start.offset as u32;
429 let attr_name_end = content[attr_start as usize..].find('=').unwrap_or((attr.span.end.offset - attr.span.start.offset) as usize);
430 tokens.extend(&[attr_start, attr_name_end as u32, 0, 0, 0]);
431 }
432 }
433 }
434
435 Self::process_template_nodes(&el.children, content, tokens);
437 }
438 TemplateNodeIR::Interpolation(expr) => {
439 if !expr.span.is_unknown() {
440 let start = expr.span.start.offset as u32;
442 let len = (expr.span.end.offset - expr.span.start.offset) as u32;
443 tokens.extend(&[start, len, 0, 0, 0]);
444 }
445 }
446 TemplateNodeIR::Text(_, span, _) => {
447 if !span.is_unknown() {
448 let start = span.start.offset as u32;
450 let len = (span.end.offset - span.start.offset) as u32;
451 tokens.extend(&[start, len, 0, 0, 0]);
452 }
453 }
454 _ => {}
455 }
456 }
457 }
458}
459
460use nargo_compiler::Compiler;
461use nargo_script_analyzer::ScriptAnalyzer;
462use oak_core::Parser;
463
464use oak_lsp::types::{CompletionItem as OakCompletionItem, CompletionItemKind as OakCompletionItemKind, Diagnostic as OakDiagnostic, DiagnosticSeverity as OakDiagnosticSeverity, SemanticTokens};
465
466impl LanguageService for NargoLanguageService {
467 type Lang = NargoLanguage;
468 type Vfs = MemoryVfs;
469
470 fn vfs(&self) -> &Self::Vfs {
471 &self.vfs
472 }
473
474 fn workspace(&self) -> &oak_lsp::workspace::WorkspaceManager {
475 &self.workspace
476 }
477
478 fn get_root(&self, uri: &str) -> impl Future<Output = Option<RedNode<'_, NargoLanguage>>> + Send + '_ {
479 let source = self.vfs.get_source(uri);
480 async move {
485 if let Some(source) = source {
486 }
499 None
500 }
501 }
502
503 fn completion(&self, uri: &str, position: usize) -> impl Future<Output = Vec<OakCompletionItem>> + Send + '_ {
504 let source = self.vfs.get_source(uri);
505 let auto_imports = self.auto_imports.clone();
506 let uri = uri.to_string();
507
508 async move {
509 let mut items = Vec::new();
510 if let Some(source) = source {
511 let content = source.text();
512 let pos = self
513 .vfs
514 .line_map(&uri)
515 .map(|m| {
516 let (l, c) = m.offset_to_line_col_utf16(&source, position);
517 oak_lsp::types::SourcePosition { line: l, column: c, offset: 0, length: 0 }
518 })
519 .unwrap_or(oak_lsp::types::SourcePosition { line: 0, column: 0, offset: 0, length: 0 });
520 let nargo_pos = NargoPosition { line: pos.line + 1, column: pos.column + 1, offset: 0 };
521
522 let mut state = ParseState::new(&content);
523 let parser = VueTemplateParser;
524 if let Ok(nodes) = parser.parse(&mut state, "html") {
525 if let Some(context) = Self::get_completion_context(&nodes, nargo_pos) {
526 match context {
527 CompletionContext::Tag => {
528 let tags = vec!["div", "span", "button", "input", "h1", "h2", "h3", "h4", "h5", "h6", "p", "section", "article", "header", "footer", "nav", "aside", "ul", "ol", "li", "table", "tr", "td", "th", "form", "label", "select", "option", "textarea", "img", "a", "br", "hr"];
529 for tag in tags {
530 items.push(OakCompletionItem { label: tag.to_string(), kind: Some(OakCompletionItemKind::Keyword), detail: Some("HTML Tag".to_string()), documentation: None, insert_text: Some(format!("{}", tag)) });
531 }
532 }
533 CompletionContext::Attribute(tag_name) => {
534 let directives = vec!["@click", "@input", "@change", "@submit", "@keydown", "@keyup", ":class", ":style", ":value", ":disabled", ":src", ":href"];
535 for dir in directives {
536 items.push(OakCompletionItem { label: dir.to_string(), kind: Some(OakCompletionItemKind::Constant), detail: Some("Directive".to_string()), documentation: None, insert_text: Some(format!("{}", dir)) });
537 }
538
539 if tag_name == "input" {
540 let input_attrs = vec!["type", "name", "value", "placeholder", "required", "disabled"];
541 for attr in input_attrs {
542 items.push(OakCompletionItem { label: attr.to_string(), kind: Some(OakCompletionItemKind::Property), detail: None, documentation: None, insert_text: Some(format!("{}", attr)) });
543 }
544 }
545 }
546 CompletionContext::Expression => {
547 for import in auto_imports.iter() {
548 items.push(OakCompletionItem { label: import.clone(), kind: Some(OakCompletionItemKind::Function), detail: Some("Nargo Auto-import".to_string()), documentation: None, insert_text: Some(import.clone()) });
549 }
550
551 if let Some(program) = Self::get_script_program(&nodes) {
552 for stmt in &program.body {
553 if let JsStmt::VariableDecl { id, .. } = stmt {
554 items.push(OakCompletionItem { label: id.clone(), kind: Some(OakCompletionItemKind::Variable), detail: None, documentation: None, insert_text: Some(format!("{}", id)) });
555 }
556 if let JsStmt::FunctionDecl { id, .. } = stmt {
557 items.push(OakCompletionItem { label: id.clone(), kind: Some(OakCompletionItemKind::Function), detail: None, documentation: None, insert_text: Some(format!("{}", id)) });
558 }
559 }
560 }
561 }
562 }
563 }
564 }
565 }
566
567 if items.is_empty() {
568 let tags = vec!["div", "span", "button", "input", "h1", "h2", "p", "section", "article"];
569 for tag in tags {
570 items.push(OakCompletionItem { label: tag.to_string(), kind: Some(OakCompletionItemKind::Keyword), detail: Some("HTML Tag".to_string()), documentation: None, insert_text: None });
571 }
572 }
573
574 items
575 }
576 }
577
578 fn diagnostics(&self, uri: &str) -> impl Future<Output = Vec<OakDiagnostic>> + Send + '_ {
579 let source = self.vfs.get_source(uri);
580 let uri_parsed = Url::parse(uri).ok();
581 let uri = uri.to_string();
582
583 async move {
584 let mut diags = Vec::new();
585 if let Some(source) = source {
586 let content = source.text();
587 let mut compiler = Compiler::new();
588 let name = uri_parsed.as_ref().and_then(|u| u.path_segments()).and_then(|mut s| s.next_back()).unwrap_or("App.ts");
589
590 if let Err(e) = compiler.compile(name, &content) {
592 let span = e.span();
593 if !span.is_unknown() {
594 let start_pos = oak_lsp::types::SourcePosition { line: span.start.line.saturating_sub(1), column: span.start.column.saturating_sub(1), offset: 0, length: 0 };
595 let end_pos = oak_lsp::types::SourcePosition { line: span.end.line.saturating_sub(1), column: span.end.column.saturating_sub(1), offset: 0, length: 0 };
596
597 let start_offset = self.vfs.line_map(&uri).map(|m| m.line_col_utf16_to_offset(&source, start_pos.line, start_pos.column)).unwrap_or(0);
598 let end_offset = self.vfs.line_map(&uri).map(|m| m.line_col_utf16_to_offset(&source, end_pos.line, end_pos.column)).unwrap_or(0);
599
600 diags.push(oak_lsp::types::Diagnostic { range: Range { start: start_offset, end: end_offset }, severity: Some(oak_lsp::types::DiagnosticSeverity::Error), message: format!("{}", e), source: Some("nargo-compiler".to_string()), code: None });
601 }
602 }
603
604 if let Ok(ir) = compiler.compile_to_ir(name, &content, &mut Default::default()) {
606 let analyzer = ScriptAnalyzer::new();
607 if let Some(script) = &ir.script {
608 if let Ok(meta) = analyzer.analyze(script) {
609 let used_vars: HashSet<String> = HashSet::new(); for signal in &meta.signals {
612 if !used_vars.contains(signal) {
613 let start_offset = 0; let end_offset = 0;
616 diags.push(OakDiagnostic { range: Range { start: start_offset, end: end_offset }, severity: Some(OakDiagnosticSeverity::Warning), message: format!("Unused signal: {}", signal), source: Some("nargo-analyzer".to_string()), code: None });
617 }
618 }
619 }
620 }
621 }
622 }
623 diags
624 }
625 }
626
627 fn initialize(&self, params: oak_lsp::types::InitializeParams) -> impl Future<Output = ()> + Send + '_ {
628 async move {
629 self.workspace.initialize(¶ms);
630 }
631 }
632
633 fn definition(&self, uri: &str, range: Range<usize>) -> impl Future<Output = Vec<oak_lsp::types::LocationRange>> + Send + '_ {
634 let source = self.vfs.get_source(uri);
635 let uri = uri.to_string();
636 async move {
637 if let Some(source) = source {
638 let content = source.text();
639 let position = self
640 .vfs
641 .line_map(&uri)
642 .map(|m| {
643 let (l, c) = m.offset_to_line_col_utf16(&source, range.start);
644 oak_lsp::types::SourcePosition { line: l, column: c, offset: 0, length: 0 }
645 })
646 .unwrap_or(oak_lsp::types::SourcePosition { line: 0, column: 0, offset: 0, length: 0 });
647 let nargo_pos = NargoPosition { line: position.line + 1, column: position.column + 1, offset: 0 };
648
649 let mut state = ParseState::new(&content);
650 let parser = VueTemplateParser;
651 if let Ok(nodes) = parser.parse(&mut state, "html") {
652 if let Some(loc) = self.find_definition_in_nodes(&nodes, nargo_pos, &uri).await {
653 return vec![loc];
654 }
655 }
656 }
657 vec![]
658 }
659 }
660
661 fn semantic_tokens(&self, uri: &str) -> impl Future<Output = Option<SemanticTokens>> + Send + '_ {
662 let source = self.vfs.get_source(uri);
663 let uri = uri.to_string();
664
665 async move {
666 if let Some(source) = source {
667 let content = source.text();
668 let mut tokens: Vec<oak_lsp::types::SemanticToken> = Vec::new();
669
670 let mut state = ParseState::new(&content);
672 let parser = VueTemplateParser;
673 if let Ok(nodes) = parser.parse(&mut state, "html") {
674 }
677
678 Some(SemanticTokens { data: vec![], result_id: None })
680 }
681 else {
682 None
683 }
684 }
685 }
686}
687
688pub async fn run_stdio() -> nargo_types::Result<()> {
689 let stdin = tokio::io::stdin();
690 let stdout = tokio::io::stdout();
691
692 let nargo_service = Arc::new(NargoLanguageService::new());
693 let server = oak_lsp::LspServer::new(nargo_service);
694 server.run(stdin, stdout).await.map_err(|e| nargo_types::Error::external_error("oak-lsp".to_string(), format!("{}", e), nargo_types::Span::unknown()))?;
695 Ok(())
696}
697
698pub async fn run_http_server(port: u16) -> nargo_types::Result<()> {
699 println!("HTTP/WebSocket LSP server on port {} is not yet fully implemented.", port);
700 println!("Falling back to stdio for now.");
701 run_stdio().await
702}