orgize/orgize-lsp/src/commands/headline_toc.rs
2023-12-20 21:56:10 +08:00

101 lines
2.8 KiB
Rust

use orgize::{
export::{Container, Event, TraversalContext, Traverser},
rowan::ast::AstNode,
SyntaxKind,
};
use std::collections::HashMap;
use std::fmt::Write;
use tower_lsp::lsp_types::{TextEdit, Url, WorkspaceEdit};
use crate::Backend;
impl Backend {
pub async fn headline_toc(&self, url: Url, headline_offset: u32) {
let uri = url.to_string();
let Some(doc) = self.documents.get(&uri) else {
return;
};
let mut toc = Toc {
indent: 0,
output: String::new(),
headline_offset,
edit_range: None,
};
doc.traverse(&mut toc);
if let Some((start, end)) = toc.edit_range {
let mut changes = HashMap::new();
let range = doc.range_of(start, end);
changes.insert(
url,
vec![TextEdit {
new_text: toc.output,
range,
}],
);
let _ = self
.client
.apply_edit(WorkspaceEdit {
changes: Some(changes),
..Default::default()
})
.await;
}
}
}
pub struct Toc {
output: String,
indent: usize,
headline_offset: u32,
edit_range: Option<(u32, u32)>,
}
impl Traverser for Toc {
fn event(&mut self, event: Event, ctx: &mut TraversalContext) {
match event {
Event::Enter(Container::Headline(headline)) => {
if headline.begin() == self.headline_offset {
let start = headline
.syntax()
.children_with_tokens()
.find(|n| n.kind() == SyntaxKind::NEW_LINE)
.map(|n| n.text_range().end().into())
.unwrap_or(headline.end());
let end = headline.end();
self.edit_range = Some((start, end));
} else {
let title = headline.title().map(|e| e.to_string()).collect::<String>();
let slug = orgize_common::headline_slug(&headline);
let _ = writeln!(
&mut self.output,
"{: >i$}- [[#{slug}][{title}]]",
"",
i = self.indent
);
}
self.indent += 2;
}
Event::Leave(Container::Headline(_)) => self.indent -= 2,
Event::Enter(Container::Section(_)) => ctx.skip(),
Event::Enter(Container::Document(_)) => self.output += "#+begin_quote\n",
Event::Leave(Container::Document(_)) => self.output += "#+end_quote\n\n",
_ => {}
}
}
}