feat(lsp): semantic tokens for headline keyword, priority and timestamp
This commit is contained in:
parent
e0021b4a91
commit
edd73e3c6d
4 changed files with 87 additions and 84 deletions
|
|
@ -75,6 +75,29 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"semanticTokenScopes": [
|
||||
{
|
||||
"language": "org",
|
||||
"scopes": {
|
||||
"headlineTodoKeyword": [
|
||||
"invalid.illegal.org"
|
||||
],
|
||||
"headlineDoneKeyword": [
|
||||
"keyword.control.org"
|
||||
],
|
||||
"headlineTags": [
|
||||
"markup.italic.org",
|
||||
"keyword.control.org"
|
||||
],
|
||||
"headlinePriority": [
|
||||
"keyword.control.org"
|
||||
],
|
||||
"timestamp": [
|
||||
"variable.org"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"language": "org",
|
||||
|
|
|
|||
|
|
@ -1863,9 +1863,6 @@
|
|||
},
|
||||
"common": {
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#timestamp"
|
||||
},
|
||||
{
|
||||
"include": "#link"
|
||||
},
|
||||
|
|
@ -1899,23 +1896,6 @@
|
|||
"patterns": [
|
||||
{
|
||||
"include": "#common"
|
||||
},
|
||||
{
|
||||
"include": "#todo"
|
||||
},
|
||||
{
|
||||
"include": "#done"
|
||||
},
|
||||
{
|
||||
"include": "#userKeywords"
|
||||
}
|
||||
]
|
||||
},
|
||||
"timestamp": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "variable.org",
|
||||
"match": "\\[\\d{4}-\\d{1,2}-\\d{1,2}(?: \\w{3})?\\]"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -1953,30 +1933,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"todo": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "invalid.illegal.org",
|
||||
"match": "\\bTODO\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
"done": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "keyword.control.org",
|
||||
"match": "\\bDONE\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
"userKeywords": {
|
||||
"patterns": [
|
||||
{
|
||||
"name": "string.quoted.double.org",
|
||||
"match": "\\b([A-Z]{3,})\\b"
|
||||
}
|
||||
]
|
||||
},
|
||||
"bold": {
|
||||
"patterns": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -64,8 +64,8 @@ impl LanguageServer for Backend {
|
|||
semantic_tokens_options: SemanticTokensOptions {
|
||||
work_done_progress_options: WorkDoneProgressOptions::default(),
|
||||
legend: SemanticTokensLegend {
|
||||
token_types: LEGEND_TYPE.into(),
|
||||
token_modifiers: vec![],
|
||||
token_types: semantic_token::TYPES.into(),
|
||||
token_modifiers: semantic_token::MODIFIERS.into(),
|
||||
},
|
||||
range: Some(true),
|
||||
full: Some(SemanticTokensFullOptions::Bool(true)),
|
||||
|
|
|
|||
|
|
@ -1,13 +1,27 @@
|
|||
use orgize::{
|
||||
export::{Container, Event, TraversalContext, Traverser},
|
||||
rowan::{ast::AstNode, TextRange},
|
||||
SyntaxKind,
|
||||
};
|
||||
use tower_lsp::lsp_types::{Range, SemanticToken, SemanticTokenType};
|
||||
use tower_lsp::lsp_types::{Range, SemanticToken, SemanticTokenModifier, SemanticTokenType};
|
||||
|
||||
use crate::org_document::OrgDocument;
|
||||
|
||||
/// Semantic token types that are used for highlighting
|
||||
pub const LEGEND_TYPE: &[SemanticTokenType] = &[SemanticTokenType::COMMENT];
|
||||
const TIMESTAMP: SemanticTokenType = SemanticTokenType::new("timestamp");
|
||||
const HEADLINE_TODO_KEYWORD: SemanticTokenType = SemanticTokenType::new("headlineTodoKeyword");
|
||||
const HEADLINE_DONE_KEYWORD: SemanticTokenType = SemanticTokenType::new("headlineDoneKeyword");
|
||||
const HEADLINE_PRIORITY: SemanticTokenType = SemanticTokenType::new("headlinePriority");
|
||||
const HEADLINE_TAGS: SemanticTokenType = SemanticTokenType::new("headlineTags");
|
||||
|
||||
pub const TYPES: &[SemanticTokenType] = &[
|
||||
TIMESTAMP,
|
||||
HEADLINE_TODO_KEYWORD,
|
||||
HEADLINE_DONE_KEYWORD,
|
||||
HEADLINE_PRIORITY,
|
||||
HEADLINE_TAGS,
|
||||
];
|
||||
|
||||
pub const MODIFIERS: &[SemanticTokenModifier] = &[];
|
||||
|
||||
pub struct SemanticTokenTraverser<'a> {
|
||||
pub doc: &'a OrgDocument,
|
||||
|
|
@ -21,37 +35,54 @@ pub struct SemanticTokenTraverser<'a> {
|
|||
|
||||
impl<'a> Traverser for SemanticTokenTraverser<'a> {
|
||||
fn event(&mut self, event: Event, ctx: &mut TraversalContext) {
|
||||
macro_rules! m {
|
||||
($range:expr, $ty:expr $(,$modifiers:expr)*) => {{
|
||||
if let Some(token) =
|
||||
self.create_token($range.start().into(), $range.end().into(), $ty)
|
||||
{
|
||||
self.tokens.push(token);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! s {
|
||||
($range:expr) => {
|
||||
if let Some(range) = self.range {
|
||||
if !range.contains_range($range) {
|
||||
return ctx.skip();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::Enter(Container::Comment(comment)) => {
|
||||
let range = comment.syntax().text_range();
|
||||
Event::Enter(Container::Section(section)) => s!(section.syntax().text_range()),
|
||||
Event::Enter(Container::Paragraph(paragraph)) => s!(paragraph.syntax().text_range()),
|
||||
Event::Enter(Container::OrgTable(table)) => s!(table.syntax().text_range()),
|
||||
Event::Enter(Container::List(list)) => s!(list.syntax().text_range()),
|
||||
Event::Enter(Container::Drawer(drawer)) => s!(drawer.syntax().text_range()),
|
||||
Event::Enter(Container::DynBlock(block)) => s!(block.syntax().text_range()),
|
||||
|
||||
if self.contains_range(range) {
|
||||
if let Some(token) = self.create_token(
|
||||
comment.begin(),
|
||||
comment.end(),
|
||||
SemanticTokenType::COMMENT,
|
||||
) {
|
||||
self.tokens.push(token);
|
||||
Event::Enter(Container::Headline(headline)) => {
|
||||
s!(headline.syntax().text_range());
|
||||
|
||||
for ch in headline.syntax().children_with_tokens() {
|
||||
match ch.kind() {
|
||||
SyntaxKind::HEADLINE_KEYWORD_DONE => {
|
||||
m!(ch.text_range(), HEADLINE_DONE_KEYWORD)
|
||||
}
|
||||
SyntaxKind::HEADLINE_KEYWORD_TODO => {
|
||||
m!(ch.text_range(), HEADLINE_TODO_KEYWORD)
|
||||
}
|
||||
SyntaxKind::HEADLINE_TAGS => m!(ch.text_range(), HEADLINE_TAGS),
|
||||
SyntaxKind::HEADLINE_PRIORITY => m!(ch.text_range(), HEADLINE_PRIORITY),
|
||||
SyntaxKind::NEW_LINE => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.skip();
|
||||
}
|
||||
Event::Enter(Container::CommentBlock(comment)) => {
|
||||
let range = comment.syntax().text_range();
|
||||
|
||||
if self.contains_range(range) {
|
||||
if let Some(token) = self.create_token(
|
||||
comment.begin(),
|
||||
comment.end(),
|
||||
SemanticTokenType::COMMENT,
|
||||
) {
|
||||
self.tokens.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.skip();
|
||||
}
|
||||
Event::Timestamp(timestamp) => m!(timestamp.syntax().text_range(), TIMESTAMP),
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -82,13 +113,6 @@ impl<'a> SemanticTokenTraverser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn contains_range(&self, range: TextRange) -> bool {
|
||||
match self.range {
|
||||
Some(r) => r.contains_range(range),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_token(
|
||||
&mut self,
|
||||
start: u32,
|
||||
|
|
@ -96,11 +120,11 @@ impl<'a> SemanticTokenTraverser<'a> {
|
|||
kind: SemanticTokenType,
|
||||
) -> Option<SemanticToken> {
|
||||
let length = end - start;
|
||||
let token_type = LEGEND_TYPE.iter().position(|item| item == &kind)? as u32;
|
||||
let token_type = TYPES.iter().position(|item| item == &kind)? as u32;
|
||||
|
||||
let line = self.doc.line_of(start);
|
||||
let first = self.doc.line_of(line);
|
||||
let start = self.doc.line_of(start) - first;
|
||||
|
||||
let start = start - self.doc.line_starts[line as usize];
|
||||
|
||||
let delta_line = line - self.previous_line;
|
||||
let delta_start = if delta_line == 0 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue