Skip to content

Commit

Permalink
Tree-sitter perf
Browse files Browse the repository at this point in the history
  • Loading branch information
WillLillis committed Jan 22, 2024
1 parent 17ffbcd commit 7e32214
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 85 deletions.
15 changes: 13 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lsp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ tree-sitter.workspace = true
tree-sitter-html.workspace = true
maplit = "1.0.2"
phf = { version = "0.11.2", features = ["macros"] }
lsp-textdocument = "0.3.2"
146 changes: 92 additions & 54 deletions lsp/src/handle.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,15 @@
use crate::{
htmx::{hx_completion, hx_hover, HxCompletion},
text_store::TEXT_STORE,
text_store::{DocInfo, DOCUMENT_STORE},
tree_sitter::text_doc_change_to_ts_edit,
};
use log::{debug, error, warn};
use lsp_server::{Message, Notification, Request, RequestId};
use lsp_types::{CompletionContext, CompletionParams, CompletionTriggerKind};

#[derive(serde::Deserialize, Debug)]
struct Text {
text: String,
}

#[derive(serde::Deserialize, Debug)]
struct TextDocumentLocation {
uri: String,
}

#[derive(serde::Deserialize, Debug)]
struct TextDocumentChanges {
#[serde(rename = "textDocument")]
text_document: TextDocumentLocation,

#[serde(rename = "contentChanges")]
content_changes: Vec<Text>,
}

#[derive(serde::Deserialize, Debug)]
struct TextDocumentOpened {
uri: String,

text: String,
}

#[derive(serde::Deserialize, Debug)]
struct TextDocumentOpen {
#[serde(rename = "textDocument")]
text_document: TextDocumentOpened,
}
use lsp_textdocument::FullTextDocument;
use lsp_types::{
notification::{DidChangeTextDocument, DidOpenTextDocument},
CompletionContext, CompletionParams, CompletionTriggerKind,
};

#[derive(Debug)]
pub struct HtmxAttributeCompletion {
Expand All @@ -61,41 +34,84 @@ pub enum HtmxResult {
// ignore snakeCase
#[allow(non_snake_case)]
fn handle_didChange(noti: Notification) -> Option<HtmxResult> {
let text_document_changes: TextDocumentChanges = serde_json::from_value(noti.params).ok()?;
let uri = text_document_changes.text_document.uri;
let text = text_document_changes.content_changes[0].text.to_string();

if text_document_changes.content_changes.len() > 1 {
error!("more than one content change, please be wary");
match cast_notif::<DidChangeTextDocument>(noti) {
Ok(params) => {
match DOCUMENT_STORE
.get()
.expect("text store not initialized")
.lock()
.expect("text store mutex poisoned")
.get_mut(params.text_document.uri.as_str())
{
Some(entry) => {
entry
.doc
.update(&params.content_changes, params.text_document.version);

if let Some(ref mut curr_tree) = entry.tree {
for edit in params.content_changes.iter() {
match text_doc_change_to_ts_edit(edit, &entry.doc) {
Ok(edit) => {
curr_tree.edit(&edit);
}
Err(e) => {
error!("handle_didChange Bad edit info, failed to edit tree -- Error: {e}");
}
}
}
} else {
error!(
"handle_didChange tree for {} is None",
params.text_document.uri.as_str()
);
}
}
None => {
error!(
"handle_didChange No corresponding doc for supplied edits -- {}",
params.text_document.uri.as_str()
);
}
}
}
Err(e) => {
error!("Failed the deserialize DidChangeTextDocument params -- Error {e}");
}
}

TEXT_STORE
.get()
.expect("text store not initialized")
.lock()
.expect("text store mutex poisoned")
.insert(uri, text);

None
}

#[allow(non_snake_case)]
fn handle_didOpen(noti: Notification) -> Option<HtmxResult> {
debug!("handle_didOpen params {:?}", noti.params);
let text_document_changes = match serde_json::from_value::<TextDocumentOpen>(noti.params) {
Ok(p) => p.text_document,
let text_doc_open = match cast_notif::<DidOpenTextDocument>(noti) {
Ok(params) => params,
Err(err) => {
error!("handle_didOpen parsing params error : {:?}", err);
return None;
}
};

TEXT_STORE
let doc = FullTextDocument::new(
text_doc_open.text_document.language_id,
text_doc_open.text_document.version,
text_doc_open.text_document.text,
);
let mut parser = ::tree_sitter::Parser::new();
parser
.set_language(tree_sitter_html::language())
.expect("Failed to load HTML grammar");
let tree = parser.parse(doc.get_content(None), None);

let doc = DocInfo { doc, parser, tree };

DOCUMENT_STORE
.get()
.expect("text store not initialized")
.lock()
.expect("text store mutex poisoned")
.insert(text_document_changes.uri, text_document_changes.text);
.insert(text_doc_open.text_document.uri.to_string(), doc);

None
}
Expand Down Expand Up @@ -186,10 +202,23 @@ pub fn handle_other(msg: Message) -> Option<HtmxResult> {
None
}

fn cast_notif<R>(notif: Notification) -> anyhow::Result<R::Params>
where
R: lsp_types::notification::Notification,
R::Params: serde::de::DeserializeOwned,
{
match notif.extract(R::METHOD) {
Ok(value) => Ok(value),
Err(e) => Err(anyhow::anyhow!(
"cast_notif Failed to extract params -- Error: {e}"
)),
}
}

#[cfg(test)]
mod tests {
use super::{handle_request, HtmxResult, Request};
use crate::text_store::{init_text_store, TEXT_STORE};
use crate::text_store::{init_text_store, DocInfo, DOCUMENT_STORE};
use std::sync::Once;

static SETUP: Once = Once::new();
Expand All @@ -198,12 +227,21 @@ mod tests {
init_text_store();
});

TEXT_STORE
let doc =
lsp_textdocument::FullTextDocument::new("html".to_string(), 0, content.to_string());
let mut parser = ::tree_sitter::Parser::new();
parser
.set_language(tree_sitter_html::language())
.expect("Failed to load HTML grammar");
let tree = parser.parse(doc.get_content(None), None);
let doc_info = DocInfo { doc, parser, tree };

DOCUMENT_STORE
.get()
.expect("text store not initialized")
.lock()
.expect("text store mutex poisoned")
.insert(file.to_string(), content.to_string());
.insert(file.to_string(), doc_info);
}

#[test]
Expand Down
9 changes: 6 additions & 3 deletions lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use htmx::HxCompletion;
use log::{debug, error, info, warn};
use lsp_types::{
CompletionItem, CompletionItemKind, CompletionList, HoverContents, InitializeParams,
LanguageString, MarkedString, ServerCapabilities, TextDocumentSyncCapability,
TextDocumentSyncKind, WorkDoneProgressOptions,
LanguageString, MarkedString, PositionEncodingKind, ServerCapabilities,
TextDocumentSyncCapability, TextDocumentSyncKind, WorkDoneProgressOptions,
};

use lsp_server::{Connection, Message, Response};
Expand Down Expand Up @@ -123,7 +123,10 @@ pub fn start_lsp() -> Result<()> {

// Run the server and wait for the two threads to end (typically by trigger LSP Exit event).
let server_capabilities = serde_json::to_value(ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)),
position_encoding: Some(PositionEncodingKind::UTF16), // compatability with lsp_textdocument crate
text_document_sync: Some(TextDocumentSyncCapability::Kind(
TextDocumentSyncKind::INCREMENTAL,
)),
completion_provider: Some(lsp_types::CompletionOptions {
resolve_provider: Some(false),
trigger_characters: Some(vec!["-".to_string(), "\"".to_string(), " ".to_string()]),
Expand Down
31 changes: 20 additions & 11 deletions lsp/src/text_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,46 @@ use std::{
sync::{Arc, Mutex, OnceLock},
};

use lsp_types::Url;
use lsp_textdocument::FullTextDocument;
use lsp_types::{Range, Url};
use tree_sitter::{Parser, Tree};

pub struct DocInfo {
pub doc: FullTextDocument,
pub parser: Parser,
pub tree: Option<Tree>,
}

type TxtStore = HashMap<String, String>;
type DocStore = HashMap<String, DocInfo>;

pub struct TextStore(TxtStore);
#[derive(Default)]
pub struct DocumentStore(DocStore);

impl Deref for TextStore {
type Target = TxtStore;
impl Deref for DocumentStore {
type Target = DocStore;
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for TextStore {
impl DerefMut for DocumentStore {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

pub static TEXT_STORE: OnceLock<Arc<Mutex<TextStore>>> = OnceLock::new();
pub static DOCUMENT_STORE: OnceLock<Arc<Mutex<DocumentStore>>> = OnceLock::new();

pub fn init_text_store() {
_ = TEXT_STORE.set(Arc::new(Mutex::new(TextStore(HashMap::new()))));
_ = DOCUMENT_STORE.set(Arc::new(Mutex::new(DocumentStore::default())));
}

pub fn get_text_document(uri: Url) -> Option<String> {
return TEXT_STORE
pub fn get_text_document(uri: &Url, range: Option<Range>) -> Option<String> {
return DOCUMENT_STORE
.get()
.expect("text store not initialized")
.lock()
.expect("text store mutex poisoned")
.get(&uri.to_string())
.cloned();
.map(|doc| doc.doc.get_content(range).to_string());
}
Loading

0 comments on commit 7e32214

Please sign in to comment.