1#![doc = include_str!("readme.md")]
2#[cfg(feature = "oak-highlight")]
3pub mod highlighter;
4
5use crate::{JsonLanguage, lexer::token_type::JsonTokenType};
6use core::range::Range;
7use dashmap::DashMap;
8use oak_core::{ParseCache, TokenType, parser::session::ParseSession, source::Source, tree::RedNode};
9#[cfg(feature = "lsp")]
10use {
11 futures::Future,
12 oak_hover::{Hover, HoverProvider},
13 oak_lsp::service::LanguageService,
14 oak_vfs::Vfs,
15};
16
17use crate::parser::element_type::JsonElementType;
18
19#[cfg(feature = "lsp")]
21pub struct JsonHoverProvider;
22#[cfg(feature = "lsp")]
23impl HoverProvider<JsonLanguage> for JsonHoverProvider {
24 fn hover(&self, node: &RedNode<JsonLanguage>, _range: Range<usize>) -> Option<Hover> {
25 let kind = node.green.kind;
26 let contents = match kind {
28 JsonElementType::Object => "### JSON Object\nA collection of key-value pairs.",
29 JsonElementType::Array => "### JSON Array\nAn ordered list of values.",
30 JsonElementType::ObjectEntry => "### JSON Property\nA key-value pair in an object.",
31 JsonElementType::StringLiteral => "### JSON String\nA sequence of Unicode characters.",
32 JsonElementType::NumberLiteral => "### JSON Number\nA numeric value.",
33 JsonElementType::BooleanLiteral => "### JSON Boolean\nA true or false value.",
34 JsonElementType::NullLiteral => "### JSON Null\nRepresents the intentional absence of any value.",
35 _ => return None,
36 };
37 Some(Hover { contents: contents.to_string(), range: Some(node.span()) })
38 }
39}
40#[cfg(feature = "lsp")]
42pub struct JsonLanguageService<V: Vfs> {
43 vfs: V,
44 workspace: oak_lsp::workspace::WorkspaceManager,
45 hover_provider: JsonHoverProvider,
46 sessions: DashMap<String, Box<ParseSession<JsonLanguage>>>,
47}
48impl<V: Vfs> JsonLanguageService<V> {
49 pub fn new(vfs: V) -> Self {
51 Self { vfs, workspace: oak_lsp::workspace::WorkspaceManager::default(), hover_provider: JsonHoverProvider, sessions: DashMap::new() }
52 }
53 fn collect_definitions(&self, node: &RedNode<JsonLanguage>, name: &str, source: &V::Source, uri: &str, definitions: &mut Vec<oak_lsp::LocationRange>) {
54 use oak_core::{
55 language::{ElementType, UniversalElementRole, UniversalTokenRole},
56 tree::RedTree,
57 };
58 if ElementType::is_universal(&node.green.kind, UniversalElementRole::Statement) && node.green.kind == JsonElementType::ObjectEntry {
60 for child in node.children() {
61 if let RedTree::Leaf(leaf) = child {
62 if TokenType::is_universal(&leaf.kind, UniversalTokenRole::Name) || leaf.kind == JsonTokenType::StringLiteral {
65 let text = source.get_text_in(leaf.span.clone());
66 let key_name = if leaf.kind == JsonTokenType::StringLiteral { text.trim_matches('"') } else { &text };
68 if key_name == name {
69 definitions.push(oak_lsp::LocationRange { uri: uri.to_string().into(), range: leaf.span });
70 return;
71 }
72 }
73 }
74 }
75 }
76 for child in node.children() {
77 if let RedTree::Node(child_node) = child {
78 self.collect_definitions(&child_node, name, source, uri, definitions);
79 }
80 }
81 }
82}
83impl<V: Vfs + Send + Sync + 'static + oak_vfs::WritableVfs> LanguageService for JsonLanguageService<V> {
84 type Lang = JsonLanguage;
85 type Vfs = V;
86 fn vfs(&self) -> &Self::Vfs {
87 &self.vfs
88 }
89 fn workspace(&self) -> &oak_lsp::workspace::WorkspaceManager {
90 &self.workspace
91 }
92 fn get_root(&self, uri: &str) -> impl Future<Output = Option<RedNode<'_, JsonLanguage>>> + Send + '_ {
93 let uri = uri.to_string();
94 async move {
95 let source = self.vfs().get_source(&uri)?;
96 let mut session_entry = self.sessions.entry(uri.clone()).or_insert_with(|| Box::new(ParseSession::<JsonLanguage>::default()));
97 let session = session_entry.as_mut();
98 let language = JsonLanguage::default();
99 let parser = crate::parser::JsonParser::new(&language);
100 let lexer = crate::lexer::JsonLexer::new(&language);
101 let tree = {
102 let output = oak_core::parser::parse(&parser, &lexer, &source, &[], session);
103 let tree_ref = output.result.as_ref().ok()?;
104 unsafe { &*(*tree_ref as *const oak_core::GreenNode<JsonLanguage> as *const oak_core::GreenNode<'static, JsonLanguage>) }
108 };
109 session.commit_generation(tree);
110 Some(RedNode::new(tree, 0))
111 }
112 }
113 fn definition<'a>(&'a self, uri: &'a str, range: Range<usize>) -> impl Future<Output = Vec<oak_lsp::LocationRange>> + Send + 'a {
114 let uri = uri.to_string();
115 async move {
116 let Some(root) = self.get_root(&uri).await
117 else {
118 return vec![];
119 };
120 let Some(source) = self.vfs().get_source(&uri)
121 else {
122 return vec![];
123 };
124 let Some(leaf) = root.leaf_at_offset(range.start)
125 else {
126 return vec![];
127 };
128 let text = source.get_text_in(leaf.span.clone());
129 let name = text.trim_matches('"');
130 let mut all_definitions = Vec::new();
132 let files = self.list_all_files(&uri).await;
133 for file_uri in files {
134 if let Some(file_root) = self.get_root(&file_uri).await {
135 if let Some(file_source) = self.vfs().get_source(&file_uri) {
136 self.collect_definitions(&file_root, name, &file_source, &file_uri, &mut all_definitions);
137 }
138 }
139 }
140 all_definitions
141 }
142 }
143 fn references<'a>(&'a self, uri: &'a str, range: Range<usize>) -> impl Future<Output = Vec<oak_lsp::LocationRange>> + Send + 'a {
144 let uri = uri.to_string();
145 async move {
146 let Some(root) = self.get_root(&uri).await
147 else {
148 return vec![];
149 };
150 let Some(source) = self.vfs().get_source(&uri)
151 else {
152 return vec![];
153 };
154 let Some(leaf) = root.leaf_at_offset(range.start)
155 else {
156 return vec![];
157 };
158 let text = source.get_text_in(leaf.span.clone());
159 let name = text.trim_matches('"');
160 let mut all_refs = Vec::new();
162 let files = self.list_all_files(&uri).await;
163 for file_uri in files {
164 if let Some(file_root) = self.get_root(&file_uri).await {
165 if let Some(file_source) = self.vfs().get_source(&file_uri) {
166 self.collect_definitions(&file_root, name, &file_source, &file_uri, &mut all_refs);
168 }
169 }
170 }
171 all_refs
172 }
173 }
174 fn rename<'a>(&'a self, uri: &'a str, range: Range<usize>, new_name: String) -> impl Future<Output = Option<oak_lsp::WorkspaceEdit>> + Send + 'a {
175 let uri = uri.to_string();
176 async move {
177 let refs = self.references(&uri, range).await;
178 if refs.is_empty() {
179 return None;
180 }
181 let mut changes = std::collections::HashMap::new();
182 for r in refs {
183 let Some(source) = self.vfs().get_source(&r.uri)
185 else {
186 continue;
187 };
188 let old_text = source.get_text_in(r.range.clone());
189 let formatted_new_name = if old_text.starts_with('"') { format!("\"{}\"", new_name) } else { new_name.clone() };
190 changes.entry(r.uri.to_string()).or_insert_with(Vec::new).push(oak_lsp::TextEdit { range: r.range, new_text: formatted_new_name });
191 }
192 Some(oak_lsp::WorkspaceEdit { changes })
193 }
194 }
195 fn hover(&self, uri: &str, range: Range<usize>) -> impl Future<Output = Option<oak_lsp::Hover>> + Send + '_ {
196 let uri = uri.to_string();
197 async move { self.with_root(&uri, |root| self.hover_provider.hover(&root, range).map(|h| oak_lsp::Hover { contents: h.contents, range: h.range })).await.flatten() }
198 }
199}