From b57955da2c845024409cde0d8c611873a77dc508 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Wed, 18 Feb 2026 10:55:44 +0100 Subject: [PATCH 1/2] feat(ls): add configuration options for code formatter. Now users can configure the automatic code formatter with their own settings. --- ls/editors/code/package.json | 53 ++++++++++++- ls/src/features/formatting.rs | 42 +++++++++- ls/src/server.rs | 77 +++++++++++++------ .../tests/testdata/formatting1.response.json | 2 +- 4 files changed, 147 insertions(+), 27 deletions(-) diff --git a/ls/editors/code/package.json b/ls/editors/code/package.json index a9111d373..54ae88d4b 100644 --- a/ls/editors/code/package.json +++ b/ls/editors/code/package.json @@ -19,7 +19,58 @@ "contributes": { "configuration": { "title": "YARA", - "properties": {} + "properties": { + "YARA.codeFormatting": { + "type": "object", + "default": { + "alignMetadata": true, + "alignPatterns": true, + "indentSectionHeaders": true, + "indentSectionContents": true, + "newlineBeforeCurlyBrace": false, + "emptyLineBeforeSectionHeader": false, + "emptyLineAfterSectionHeader": false + }, + "description": "Options for automatic code formatting.", + "properties": { + "alignMetadata": { + "type": "boolean", + "default": true, + "markdownDescription": "Aligns the values in the `meta` section of a rule.\n\n**Example:**\n\n*Original code:*\n```yara\nrule example {\n meta:\n author = \"John Doe\"\n creation_date = \"2024-01-01\"\n description = \"A simple example rule.\"\n}\n```\n\n*Formatted code:*\n```yara\nrule example {\n meta:\n author = \"John Doe\"\n creation_date = \"2024-01-01\"\n description = \"A simple example rule.\"\n}\n```" + }, + "alignPatterns": { + "type": "boolean", + "default": true, + "markdownDescription": "Aligns the patterns in the `strings` section of a rule.\n\n**Example:**\n\n*Original code:*\n```yara\nrule example {\n strings:\n $a = \"some string\"\n $b_is_longer = { 48 65 6c 6c 6f }\n $c = \"another string\"\n}\n```\n\n*Formatted code:*\n```yara\nrule example {\n strings:\n $a = \"some string\"\n $b_is_longer = { 48 65 6c 6c 6f }\n $c = \"another string\"\n}\n```" + }, + "indentSectionHeaders": { + "type": "boolean", + "default": true, + "markdownDescription": "Indent section headers (`meta`, `strings`, `condition`).\n\n**Example:**\n\n*Original code:*\n```yara\nrule example {\nmeta:\n author = \"John Doe\"\nstrings:\n $a = \"some string\"\ncondition:\n $a\n}\n```\n\n*Formatted code:*\n```yara\nrule example {\n meta:\n author = \"John Doe\"\n strings:\n $a = \"some string\"\n condition:\n $a\n}\n```" + }, + "indentSectionContents": { + "type": "boolean", + "default": true, + "markdownDescription": "Indent the content of rule sections.\n\n**Example:**\n\n*Original code:*\n```yara\nrule example {\n meta:\n author = \"John Doe\"\n strings:\n $a = \"some string\"\n condition:\n $a\n}\n```\n\n*Formatted code:*\n```yara\nrule example {\n meta:\n author = \"John Doe\"\n strings:\n $a = \"some string\"\n condition:\n $a\n}\n```" + }, + "emptyLineBeforeSectionHeader": { + "type": "boolean", + "default": false, + "description": "Inserts an empty line before each section header." + }, + "emptyLineAfterSectionHeader": { + "type": "boolean", + "default": false, + "description": "Inserts an empty line after each section header." + }, + "newlineBeforeCurlyBrace": { + "type": "boolean", + "default": false, + "description": "Puts the opening curly brace on a new line." + } + } + } + } }, "configurationDefaults": { "[yara]": { diff --git a/ls/src/features/formatting.rs b/ls/src/features/formatting.rs index a97de6031..e96916ff6 100644 --- a/ls/src/features/formatting.rs +++ b/ls/src/features/formatting.rs @@ -1,3 +1,4 @@ +use serde::Deserialize; use std::{io::Cursor, sync::Arc}; use async_lsp::lsp_types::{ @@ -7,9 +8,36 @@ use yara_x_fmt::Indentation; use crate::documents::storage::DocumentStorage; +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct FormattingOptions { + pub align_metadata: bool, + pub align_patterns: bool, + pub indent_section_headers: bool, + pub indent_section_contents: bool, + pub newline_before_curly_brace: bool, + pub empty_line_before_section_header: bool, + pub empty_line_after_section_header: bool, +} + +impl Default for FormattingOptions { + fn default() -> Self { + Self { + align_metadata: true, + align_patterns: true, + indent_section_headers: true, + indent_section_contents: true, + newline_before_curly_brace: false, + empty_line_before_section_header: false, + empty_line_after_section_header: false, + } + } +} + pub fn formatting( documents: Arc, params: DocumentFormattingParams, + options: FormattingOptions, ) -> Option> { let document = documents.get(¶ms.text_document.uri)?; let src = document.text.as_str(); @@ -23,7 +51,19 @@ pub fn formatting( Indentation::Tabs }; - let formatter = yara_x_fmt::Formatter::new().indentation(indentation); + let formatter = yara_x_fmt::Formatter::new() + .indentation(indentation) + .align_metadata(options.align_metadata) + .align_patterns(options.align_patterns) + .indent_section_headers(options.indent_section_headers) + .indent_section_contents(options.indent_section_contents) + .newline_before_curly_brace(options.newline_before_curly_brace) + .empty_line_before_section_header( + options.empty_line_before_section_header, + ) + .empty_line_after_section_header( + options.empty_line_after_section_header, + ); match formatter.format(input, &mut output) { Ok(changed) if changed => Some(vec![TextEdit::new( diff --git a/ls/src/server.rs b/ls/src/server.rs index 544a2b0cc..6e5aa1677 100644 --- a/ls/src/server.rs +++ b/ls/src/server.rs @@ -15,16 +15,16 @@ use async_lsp::lsp_types::request::{ use async_lsp::lsp_types::{ CodeActionParams, CodeActionProviderCapability, CodeActionResponse, CompletionOptions, CompletionParams, CompletionResponse, - DiagnosticOptions, DiagnosticServerCapabilities, - DidChangeTextDocumentParams, DidCloseTextDocumentParams, - DidOpenTextDocumentParams, DidSaveTextDocumentParams, - DocumentDiagnosticParams, DocumentDiagnosticReportResult, - DocumentFormattingParams, DocumentHighlight, DocumentHighlightParams, - DocumentSymbolParams, DocumentSymbolResponse, - FullDocumentDiagnosticReport, GotoDefinitionParams, - GotoDefinitionResponse, Hover, HoverParams, HoverProviderCapability, - InitializeParams, InitializeResult, Location, OneOf, - PublishDiagnosticsParams, ReferenceParams, + ConfigurationItem, ConfigurationParams, DiagnosticOptions, + DiagnosticServerCapabilities, DidChangeTextDocumentParams, + DidCloseTextDocumentParams, DidOpenTextDocumentParams, + DidSaveTextDocumentParams, DocumentDiagnosticParams, + DocumentDiagnosticReportResult, DocumentFormattingParams, + DocumentHighlight, DocumentHighlightParams, DocumentSymbolParams, + DocumentSymbolResponse, FullDocumentDiagnosticReport, + GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverParams, + HoverProviderCapability, InitializeParams, InitializeResult, Location, + OneOf, PublishDiagnosticsParams, ReferenceParams, RelatedFullDocumentDiagnosticReport, RenameParams, SaveOptions, SelectionRange, SelectionRangeParams, SelectionRangeProviderCapability, SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, @@ -37,12 +37,14 @@ use async_lsp::router::Router; use async_lsp::{ClientSocket, LanguageClient, LanguageServer, ResponseError}; use futures::future::BoxFuture; +use crate::documents::storage::DocumentStorage; use crate::features::code_action::code_actions; use crate::features::completion::completion; use crate::features::diagnostics::diagnostics; use crate::features::document_highlight::document_highlight; use crate::features::document_symbol::document_symbol; use crate::features::formatting::formatting; +use crate::features::formatting::FormattingOptions; use crate::features::goto::go_to_definition; use crate::features::hover::hover; use crate::features::references::find_references; @@ -52,8 +54,6 @@ use crate::features::semantic_tokens::{ semantic_tokens, SEMANTIC_TOKEN_MODIFIERS, SEMANTIC_TOKEN_TYPES, }; -use crate::documents::storage::DocumentStorage; - /// Represents a YARA language server. pub struct YARALanguageServer { /// Client socket for communication with the Development Tool. @@ -123,7 +123,9 @@ impl LanguageServer for YARALanguageServer { range: Some(true), legend: SemanticTokensLegend { token_types: Vec::from(SEMANTIC_TOKEN_TYPES), - token_modifiers: Vec::from(SEMANTIC_TOKEN_MODIFIERS), + token_modifiers: Vec::from( + SEMANTIC_TOKEN_MODIFIERS, + ), }, ..Default::default() }, @@ -147,22 +149,30 @@ impl LanguageServer for YARALanguageServer { document_highlight_provider: Some(OneOf::Left(true)), document_symbol_provider: Some(OneOf::Left(true)), rename_provider: Some(OneOf::Left(true)), - code_action_provider: Some(CodeActionProviderCapability::Simple(true)), - selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)), + code_action_provider: Some( + CodeActionProviderCapability::Simple(true), + ), + selection_range_provider: Some( + SelectionRangeProviderCapability::Simple(true), + ), text_document_sync: Some(TextDocumentSyncCapability::Options( TextDocumentSyncOptions { - save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions { - include_text: Some(true), - })), + save: Some(TextDocumentSyncSaveOptions::SaveOptions( + SaveOptions { + include_text: Some(true), + }, + )), open_close: Some(true), change: Some(TextDocumentSyncKind::FULL), ..Default::default() }, )), // This is for pull model diagnostics - diagnostic_provider: Some(DiagnosticServerCapabilities::Options( - DiagnosticOptions::default(), - )), + diagnostic_provider: Some( + DiagnosticServerCapabilities::Options( + DiagnosticOptions::default(), + ), + ), ..ServerCapabilities::default() }, server_info: None, @@ -415,8 +425,6 @@ impl LanguageServer for YARALanguageServer { }) } - /// This method is called when the user requests to format a document. - /// /// It formats the source code according to the configured style and /// returns a set of edits to apply the changes. fn formatting( @@ -424,8 +432,28 @@ impl LanguageServer for YARALanguageServer { params: DocumentFormattingParams, ) -> BoxFuture<'static, Result>, Self::Error>> { let documents = Arc::clone(&self.documents); + let mut client = self.client.clone(); - Box::pin(async move { Ok(formatting(documents, params)) }) + Box::pin(async move { + let config = client + .configuration(ConfigurationParams { + items: vec![ConfigurationItem { + scope_uri: Some(params.text_document.uri.clone()), + section: Some("YARA.codeFormatting".to_string()), + }], + }) + .await; + + let options = config + .ok() + .and_then(|mut config| config.pop()) + .and_then(|v| { + serde_json::from_value::(v).ok() + }) + .unwrap_or_default(); + + Ok(formatting(documents, params, options)) + }) } /// This method is called when a document is opened. @@ -490,6 +518,7 @@ impl LanguageServer for YARALanguageServer { /// This method is called when the server is requested to shut down. /// /// It should not exit the process, but instead, it should prepare for + /// shutdown. fn shutdown( &mut self, diff --git a/ls/src/tests/testdata/formatting1.response.json b/ls/src/tests/testdata/formatting1.response.json index fc593a9ee..691676f8d 100644 --- a/ls/src/tests/testdata/formatting1.response.json +++ b/ls/src/tests/testdata/formatting1.response.json @@ -1,6 +1,6 @@ [ { - "newText": "rule test {\n strings:\n $a = \"some string\"\n\n condition:\n $a\n}\n", + "newText": "rule test {\n strings:\n $a = \"some string\"\n condition:\n $a\n}\n", "range": { "end": { "character": 0, From f9b77981a80a693488e3ccedbac9dcc0e92623d2 Mon Sep 17 00:00:00 2001 From: "Victor M. Alvarez" Date: Wed, 18 Feb 2026 10:56:47 +0100 Subject: [PATCH 2/2] style: remove empty line. --- ls/src/server.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ls/src/server.rs b/ls/src/server.rs index 6e5aa1677..9c9160348 100644 --- a/ls/src/server.rs +++ b/ls/src/server.rs @@ -518,7 +518,6 @@ impl LanguageServer for YARALanguageServer { /// This method is called when the server is requested to shut down. /// /// It should not exit the process, but instead, it should prepare for - /// shutdown. fn shutdown( &mut self,