Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ The missing language server for Drupal.
- DataType
- FormElement
- RenderElement
### Code actions
- Add translation placeholders to `t()` functions.

## Installation

Expand Down Expand Up @@ -73,4 +75,3 @@ The missing language server for Drupal.

### Code actions
- [ ] Generate __construct doc block.
- [ ] Generate t function placeholder array.
19 changes: 15 additions & 4 deletions src/parser/php.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ use std::collections::HashMap;
use tree_sitter::{Node, Point};

use super::tokens::{
ClassAttribute, DrupalHook, DrupalPlugin, DrupalPluginReference, DrupalPluginType, PhpClass,
PhpClassName, PhpMethod, Token, TokenData,
ClassAttribute, DrupalHook, DrupalPlugin, DrupalPluginReference, DrupalPluginType, DrupalTranslationString, PhpClass, PhpClassName, PhpMethod, Token, TokenData
};
use super::{get_closest_parent_by_kind, get_node_at_position, get_tree, position_to_point};

Expand Down Expand Up @@ -73,7 +72,7 @@ impl PhpParser {
match node.kind() {
"class_declaration" => self.parse_class_declaration(node),
"method_declaration" => self.parse_method_declaration(node),
"scoped_call_expression" | "member_call_expression" => {
"scoped_call_expression" | "member_call_expression" | "function_call_expression" => {
self.parse_call_expression(node, point)
}
"function_definition" => self.parse_function_definition(node),
Expand Down Expand Up @@ -122,7 +121,11 @@ impl PhpParser {

fn parse_call_expression(&self, node: Node, point: Option<Point>) -> Option<Token> {
let string_content = node.descendant_for_point_range(point?, point?)?;
let name_node = node.child_by_field_name("name")?;
let name_node = match node.kind() {
"function_call_expression" => node.child_by_field_name("function"),
_ => node.child_by_field_name("name"),
}?;

let name = self.get_node_text(&name_node);

if node.kind() == "member_call_expression" {
Expand Down Expand Up @@ -228,6 +231,14 @@ impl PhpParser {
}),
node.range(),
));
} else if name == "t" {
return Some(Token::new(
TokenData::DrupalTranslationString(DrupalTranslationString {
string: self.get_node_text(&string_content).to_string(),
placeholders: None,
}),
node.range(),
));
}

None
Expand Down
7 changes: 7 additions & 0 deletions src/parser/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub enum TokenData {
DrupalPermissionDefinition(DrupalPermission),
DrupalPermissionReference(String),
DrupalPluginReference(DrupalPluginReference),
DrupalTranslationString(DrupalTranslationString),
}

#[derive(Debug, PartialEq, Clone)]
Expand Down Expand Up @@ -195,6 +196,12 @@ pub struct DrupalPluginReference {
pub plugin_id: String,
}

#[derive(Debug)]
pub struct DrupalTranslationString {
pub string: String,
pub placeholders: Option<String>,
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 2 additions & 0 deletions src/server/handle_request.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use lsp_server::{ErrorCode, Request, RequestId, Response, ResponseError};

use super::handlers::completion::handle_text_document_completion;
use super::handlers::code_action::handle_text_document_code_action;
use super::handlers::definition::handle_text_document_definition;
use super::handlers::hover::handle_text_document_hover;

Expand All @@ -10,6 +11,7 @@ pub fn handle_request(request: Request) -> Response {
let request_id = request.id.clone();
let response = match request.method.as_str() {
"textDocument/hover" => handle_text_document_hover(request),
"textDocument/codeAction" => handle_text_document_code_action(request),
"textDocument/definition" => handle_text_document_definition(request),
"textDocument/completion" => handle_text_document_completion(request),
"shutdown" => None,
Expand Down
96 changes: 96 additions & 0 deletions src/server/handlers/code_action/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use std::collections::HashMap;

use lsp_server::{ErrorCode, Request, Response};
use lsp_types::{
CodeAction, CodeActionKind, CodeActionParams, Position, Range, TextEdit, Uri, WorkspaceEdit,
};
use regex::Regex;

use crate::{
document_store::DOCUMENT_STORE,
parser::tokens::{Token, TokenData},
server::handle_request::get_response_error,
};

pub fn handle_text_document_code_action(request: Request) -> Option<Response> {
let params = match serde_json::from_value::<CodeActionParams>(request.params) {
Err(err) => {
return Some(get_response_error(
request.id,
ErrorCode::InvalidParams,
format!("Could not parse code action params: {:?}", err),
));
}
Ok(value) => value,
};

let mut token: Option<Token> = None;
if let Some(document) = DOCUMENT_STORE
.lock()
.unwrap()
.get_document(&params.text_document.uri.to_string())
{
token = document.get_token_under_cursor(params.range.start);
}

let mut code_actions_result: Vec<CodeAction> = vec![];
if let Some(token) = token {
if let TokenData::DrupalTranslationString(token_data) = &token.data {
let re = Regex::new(r#"(?<placeholder>[@%:]\w*)"#).unwrap();
let arguments_string: String = format!(
", [{}]",
re.captures_iter(&token_data.string)
.map(|capture| capture.name("placeholder"))
.filter_map(|str| Some(format!("'{}' => ''", str?.as_str())))
.collect::<Vec<String>>()
.join(", ")
);

let mut text_edits: HashMap<Uri, Vec<TextEdit>> = HashMap::new();
text_edits.insert(
params.text_document.uri,
vec![TextEdit {
range: Range {
start: Position {
line: token.range.end_point.row as u32,
character: token.range.end_point.column as u32 - 1,
},
end: Position {
line: token.range.end_point.row as u32,
character: token.range.end_point.column as u32 - 1,
},
},
new_text: arguments_string,
}],
);

code_actions_result.push(CodeAction {
title: String::from("Add translations placeholders"),
kind: Some(CodeActionKind::REFACTOR_INLINE),
diagnostics: None,
edit: Some(WorkspaceEdit {
changes: Some(text_edits),
document_changes: None,
change_annotations: None,
}),
command: None,
is_preferred: Some(true),
disabled: None,
data: None,
});
}
}

match serde_json::to_value(code_actions_result) {
Ok(result) => Some(Response {
id: request.id,
result: Some(result),
error: None,
}),
Err(error) => Some(get_response_error(
request.id,
ErrorCode::InternalError,
format!("No code actions found: {:?}", error),
)),
}
}
1 change: 1 addition & 0 deletions src/server/handlers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod completion;
pub mod code_action;
pub mod definition;
pub mod hover;
1 change: 1 addition & 0 deletions src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub async fn start_lsp(config: DrupalLspConfig) -> 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 {
code_action_provider: Some(lsp_types::CodeActionProviderCapability::Simple(true)),
text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)),
hover_provider: Some(HoverProviderCapability::Simple(true)),
definition_provider: Some(lsp_types::OneOf::Left(true)),
Expand Down