Skip to content

Commit

Permalink
feat: add code-action to generate a default file header
Browse files Browse the repository at this point in the history
  • Loading branch information
DrWursterich committed Apr 13, 2024
1 parent b5802cd commit ecf4634
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 23 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ A work-in-progress language server for the sitepark markup language (spml).
- uris
- to be comparable (for `sp:if` and `sp:elseif` `eq`/`gt`/...)
- code actions to:
- generate a default file header
- split `sp:if` `condition` into `name` and `eq`/`gt`/`isNull`/...
- join `sp:if` `name` and `eq`/`gt`/`isNull`/... into `condition`

Expand Down
53 changes: 48 additions & 5 deletions src/command/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,19 @@ use crate::{
ast::{Argument, Comparable, ComparissonOperator, Condition, ConditionAst, Function},
parser::Parser,
},
CODE_ACTIONS,
CodeActionImplementation,
};

use super::LsError;

const DEFAULT_HEADER: &str = concat!(
"<%@ page language=\"java\" pageEncoding=\"UTF-8\" contentType=\"text/html; charset=UTF-8\"\n",
"%><%@ taglib uri=\"http://www.sitepark.com/taglibs/core\" prefix=\"sp\"\n",
"%><%@ taglib tagdir=\"/WEB-INF/tags/spt\" prefix=\"spt\"\n",
"%><%@ taglib tagdir=\"/WEB-INF/tags/tag\" prefix=\"tag\"\n",
"%>\n"
);

pub(crate) fn action(params: CodeActionParams) -> Result<Vec<CodeActionOrCommand>, LsError> {
let uri = params.text_document.uri;
let document = match document_store::get(&uri) {
Expand All @@ -32,6 +40,17 @@ pub(crate) fn action(params: CodeActionParams) -> Result<Vec<CodeActionOrCommand
};
}),
}?;
let mut actions = Vec::new();
let diagnostics = params.context.diagnostics;
if diagnostics.len() > 0 {
log::debug!("code action request carried diagnostics: {:?}", diagnostics);
match diagnostics[0].code {
Some(CodeActionImplementation::GENERATE_DEFAULT_HEADER_CODE) => {
actions.push(construct_generate_default_header(&uri))
}
_ => {}
}
}
let node = document.tree.root_node().descendant_for_point_range(
Point {
row: params.range.start.line as usize,
Expand All @@ -42,7 +61,6 @@ pub(crate) fn action(params: CodeActionParams) -> Result<Vec<CodeActionOrCommand
column: params.range.end.character as usize,
},
);
let mut actions = Vec::new();
match node {
Some(node) => match node.kind() {
"if_tag_open" => {
Expand Down Expand Up @@ -75,6 +93,31 @@ fn collect_attributes<'a>(mut node: Node<'a>) -> HashMap<&'a str, Node<'a>> {
return attributes;
}

fn construct_generate_default_header<'a>(uri: &Url) -> CodeActionOrCommand {
let document_start = Position {
line: 0,
character: 0,
};
return CodeActionOrCommand::CodeAction(CodeAction {
title: "generate default header".to_string(),
kind: Some(CodeActionImplementation::GenerateDefaultHeaders.to_kind()),
edit: Some(WorkspaceEdit {
changes: Some(HashMap::from([(
uri.clone(),
vec![TextEdit {
range: Range {
start: document_start,
end: document_start,
},
new_text: DEFAULT_HEADER.to_string(),
}],
)])),
..WorkspaceEdit::default()
}),
..CodeAction::default()
});
}

fn construct_name_to_condition<'a>(
document: &Document,
uri: &Url,
Expand Down Expand Up @@ -112,7 +155,7 @@ fn construct_name_to_condition<'a>(
};
return Some(CodeActionOrCommand::CodeAction(CodeAction {
title: format!("transform \"name\" and \"{}\" to \"condition\"", operator),
kind: Some(CODE_ACTIONS[0].clone()),
kind: Some(CodeActionImplementation::NameToCondition.to_kind()),
edit: Some(WorkspaceEdit {
changes: Some(HashMap::from([(
uri.clone(),
Expand Down Expand Up @@ -202,7 +245,7 @@ fn construct_condition_to_name<'a>(
"transform \"condition\" to \"name\" and \"{}\"",
operator_name
),
kind: Some(CODE_ACTIONS[1].clone()),
kind: Some(CodeActionImplementation::ConditionToName.to_kind()),
edit: Some(WorkspaceEdit {
changes: Some(HashMap::from([(
uri.clone(),
Expand All @@ -220,7 +263,7 @@ fn construct_condition_to_name<'a>(
return parse_is_null(&root).map(|(name, value)| {
CodeActionOrCommand::CodeAction(CodeAction {
title: "transform \"condition\" to \"name\" and \"isNull\"".to_string(),
kind: Some(CODE_ACTIONS[1].clone()),
kind: Some(CodeActionImplementation::ConditionToName.to_kind()),
edit: Some(WorkspaceEdit {
changes: Some(HashMap::from([(
uri.clone(),
Expand Down
45 changes: 36 additions & 9 deletions src/command/diagnostic.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
use super::{LsError, ResponseErrorCode};
use crate::document_store;
use crate::grammar;
use crate::modules;
use crate::parser;
use crate::spel::parser::Parser;
use anyhow::Result;
use lsp_types::DiagnosticTag;
use lsp_types::{Diagnostic, DiagnosticSeverity, DocumentDiagnosticParams, Position, Range, Url};
use std::{collections::HashMap, path::Path, str::FromStr};

use anyhow::Result;
use lsp_types::{
Diagnostic, DiagnosticSeverity, DiagnosticTag, DocumentDiagnosticParams, Position, Range, Url,
};
use tree_sitter::Node;

use crate::{
document_store, grammar, modules, parser, spel::parser::Parser, CodeActionImplementation,
};

use super::{LsError, ResponseErrorCode};

pub(crate) fn diagnostic(params: DocumentDiagnosticParams) -> Result<Vec<Diagnostic>, LsError> {
let uri = params.text_document.uri;
let document = match document_store::get(&uri) {
Expand Down Expand Up @@ -39,6 +41,7 @@ fn validate_document(
diagnositcs: &mut Vec<Diagnostic>,
file: &Url,
) -> Result<()> {
validate_header(root, text, diagnositcs)?;
for node in root.children(&mut root.walk()) {
match node.kind() {
"page_header" | "import_header" | "taglib_header" | "html_doctype" | "text"
Expand Down Expand Up @@ -68,6 +71,30 @@ fn validate_document(
return Ok(());
}

fn validate_header(root: Node, _text: &String, diagnositcs: &mut Vec<Diagnostic>) -> Result<()> {
if root.kind() != "document" {
let document_start = Position {
line: 0,
character: 0,
};
diagnositcs.push(Diagnostic {
source: Some("lspml".to_string()),
message: format!(
"missing atleast one header. Try generating one with the \"{}\" code-action",
CodeActionImplementation::GenerateDefaultHeaders
),
range: Range {
start: document_start,
end: document_start,
},
code: Some(CodeActionImplementation::GENERATE_DEFAULT_HEADER_CODE),
severity: Some(DiagnosticSeverity::ERROR),
..Default::default()
});
}
return Ok(());
}

fn validate_tag(
tag: grammar::TagProperties,
node: Node,
Expand Down
55 changes: 46 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ use lsp_types::{
CompletionOptions, CompletionOptionsCompletionItem, DiagnosticOptions,
DiagnosticServerCapabilities, DidChangeTextDocumentParams, DidCloseTextDocumentParams,
DidOpenTextDocumentParams, DidSaveTextDocumentParams, HoverOptions, HoverProviderCapability,
InitializeParams, OneOf, SemanticTokenModifier, SemanticTokenType, SemanticTokensFullOptions,
SemanticTokensLegend, SemanticTokensOptions, SemanticTokensServerCapabilities,
ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, WorkDoneProgressOptions,
InitializeParams, NumberOrString, OneOf, SemanticTokenModifier, SemanticTokenType,
SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
SemanticTokensServerCapabilities, ServerCapabilities, TextDocumentSyncCapability,
TextDocumentSyncKind, WorkDoneProgressOptions,
};
use std::{
error::Error,
fmt::{Display, Formatter},
fs::File,
str::FromStr,
};
use std::{error::Error, fs::File, str::FromStr};
use structured_logger::Builder;
mod command;
mod document_store;
Expand Down Expand Up @@ -53,10 +59,41 @@ pub(crate) const TOKEN_MODIFIERS: &'static [SemanticTokenModifier] = &[
SemanticTokenModifier::MODIFICATION,
];

pub(crate) const CODE_ACTIONS: &'static [CodeActionKind] = &[
CodeActionKind::new("refactor.name_to_condition"),
CodeActionKind::new("refactor.condition_to_name"),
];
pub(crate) enum CodeActionImplementation {
GenerateDefaultHeaders,
NameToCondition,
ConditionToName,
}

impl CodeActionImplementation {
pub(crate) const GENERATE_DEFAULT_HEADER_CODE: NumberOrString = NumberOrString::Number(7126);

fn kinds() -> Vec<CodeActionKind> {
return vec![
CodeActionImplementation::GenerateDefaultHeaders.to_kind(),
CodeActionImplementation::NameToCondition.to_kind(),
CodeActionImplementation::ConditionToName.to_kind(),
];
}

fn to_kind(self) -> CodeActionKind {
return CodeActionKind::new(match self {
CodeActionImplementation::GenerateDefaultHeaders => "refactor.generate_default_headers",
CodeActionImplementation::NameToCondition => "refactor.name_to_condition",
CodeActionImplementation::ConditionToName => "refactor.condition_to_name",
});
}
}

impl Display for CodeActionImplementation {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(match self {
CodeActionImplementation::GenerateDefaultHeaders => "refactor.generate_default_headers",
CodeActionImplementation::NameToCondition => "refactor.name_to_condition",
CodeActionImplementation::ConditionToName => "refactor.condition_to_name",
})
}
}

fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
let opts = CommandLineOpts::parse();
Expand Down Expand Up @@ -109,7 +146,7 @@ fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
},
})),
code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
code_action_kinds: Some(CODE_ACTIONS.to_vec()),
code_action_kinds: Some(CodeActionImplementation::kinds()),
..CodeActionOptions::default()
})),
..ServerCapabilities::default()
Expand Down
1 change: 1 addition & 0 deletions src/spel/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ impl Display for Function {
0 => formatter.write_str("()"),
len => {
formatter.write_str("(")?;
self.arguments[0].fmt(formatter)?;
for argument in &self.arguments[1..len] {
formatter.write_str(", ")?;
argument.fmt(formatter)?;
Expand Down

0 comments on commit ecf4634

Please sign in to comment.