Skip to content

Commit b57955d

Browse files
committed
feat(ls): add configuration options for code formatter.
Now users can configure the automatic code formatter with their own settings.
1 parent b106c77 commit b57955d

4 files changed

Lines changed: 147 additions & 27 deletions

File tree

ls/editors/code/package.json

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,58 @@
1919
"contributes": {
2020
"configuration": {
2121
"title": "YARA",
22-
"properties": {}
22+
"properties": {
23+
"YARA.codeFormatting": {
24+
"type": "object",
25+
"default": {
26+
"alignMetadata": true,
27+
"alignPatterns": true,
28+
"indentSectionHeaders": true,
29+
"indentSectionContents": true,
30+
"newlineBeforeCurlyBrace": false,
31+
"emptyLineBeforeSectionHeader": false,
32+
"emptyLineAfterSectionHeader": false
33+
},
34+
"description": "Options for automatic code formatting.",
35+
"properties": {
36+
"alignMetadata": {
37+
"type": "boolean",
38+
"default": true,
39+
"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```"
40+
},
41+
"alignPatterns": {
42+
"type": "boolean",
43+
"default": true,
44+
"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```"
45+
},
46+
"indentSectionHeaders": {
47+
"type": "boolean",
48+
"default": true,
49+
"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```"
50+
},
51+
"indentSectionContents": {
52+
"type": "boolean",
53+
"default": true,
54+
"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```"
55+
},
56+
"emptyLineBeforeSectionHeader": {
57+
"type": "boolean",
58+
"default": false,
59+
"description": "Inserts an empty line before each section header."
60+
},
61+
"emptyLineAfterSectionHeader": {
62+
"type": "boolean",
63+
"default": false,
64+
"description": "Inserts an empty line after each section header."
65+
},
66+
"newlineBeforeCurlyBrace": {
67+
"type": "boolean",
68+
"default": false,
69+
"description": "Puts the opening curly brace on a new line."
70+
}
71+
}
72+
}
73+
}
2374
},
2475
"configurationDefaults": {
2576
"[yara]": {

ls/src/features/formatting.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use serde::Deserialize;
12
use std::{io::Cursor, sync::Arc};
23

34
use async_lsp::lsp_types::{
@@ -7,9 +8,36 @@ use yara_x_fmt::Indentation;
78

89
use crate::documents::storage::DocumentStorage;
910

11+
#[derive(Clone, Debug, Deserialize)]
12+
#[serde(rename_all = "camelCase")]
13+
pub(crate) struct FormattingOptions {
14+
pub align_metadata: bool,
15+
pub align_patterns: bool,
16+
pub indent_section_headers: bool,
17+
pub indent_section_contents: bool,
18+
pub newline_before_curly_brace: bool,
19+
pub empty_line_before_section_header: bool,
20+
pub empty_line_after_section_header: bool,
21+
}
22+
23+
impl Default for FormattingOptions {
24+
fn default() -> Self {
25+
Self {
26+
align_metadata: true,
27+
align_patterns: true,
28+
indent_section_headers: true,
29+
indent_section_contents: true,
30+
newline_before_curly_brace: false,
31+
empty_line_before_section_header: false,
32+
empty_line_after_section_header: false,
33+
}
34+
}
35+
}
36+
1037
pub fn formatting(
1138
documents: Arc<DocumentStorage>,
1239
params: DocumentFormattingParams,
40+
options: FormattingOptions,
1341
) -> Option<Vec<TextEdit>> {
1442
let document = documents.get(&params.text_document.uri)?;
1543
let src = document.text.as_str();
@@ -23,7 +51,19 @@ pub fn formatting(
2351
Indentation::Tabs
2452
};
2553

26-
let formatter = yara_x_fmt::Formatter::new().indentation(indentation);
54+
let formatter = yara_x_fmt::Formatter::new()
55+
.indentation(indentation)
56+
.align_metadata(options.align_metadata)
57+
.align_patterns(options.align_patterns)
58+
.indent_section_headers(options.indent_section_headers)
59+
.indent_section_contents(options.indent_section_contents)
60+
.newline_before_curly_brace(options.newline_before_curly_brace)
61+
.empty_line_before_section_header(
62+
options.empty_line_before_section_header,
63+
)
64+
.empty_line_after_section_header(
65+
options.empty_line_after_section_header,
66+
);
2767

2868
match formatter.format(input, &mut output) {
2969
Ok(changed) if changed => Some(vec![TextEdit::new(

ls/src/server.rs

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@ use async_lsp::lsp_types::request::{
1515
use async_lsp::lsp_types::{
1616
CodeActionParams, CodeActionProviderCapability, CodeActionResponse,
1717
CompletionOptions, CompletionParams, CompletionResponse,
18-
DiagnosticOptions, DiagnosticServerCapabilities,
19-
DidChangeTextDocumentParams, DidCloseTextDocumentParams,
20-
DidOpenTextDocumentParams, DidSaveTextDocumentParams,
21-
DocumentDiagnosticParams, DocumentDiagnosticReportResult,
22-
DocumentFormattingParams, DocumentHighlight, DocumentHighlightParams,
23-
DocumentSymbolParams, DocumentSymbolResponse,
24-
FullDocumentDiagnosticReport, GotoDefinitionParams,
25-
GotoDefinitionResponse, Hover, HoverParams, HoverProviderCapability,
26-
InitializeParams, InitializeResult, Location, OneOf,
27-
PublishDiagnosticsParams, ReferenceParams,
18+
ConfigurationItem, ConfigurationParams, DiagnosticOptions,
19+
DiagnosticServerCapabilities, DidChangeTextDocumentParams,
20+
DidCloseTextDocumentParams, DidOpenTextDocumentParams,
21+
DidSaveTextDocumentParams, DocumentDiagnosticParams,
22+
DocumentDiagnosticReportResult, DocumentFormattingParams,
23+
DocumentHighlight, DocumentHighlightParams, DocumentSymbolParams,
24+
DocumentSymbolResponse, FullDocumentDiagnosticReport,
25+
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverParams,
26+
HoverProviderCapability, InitializeParams, InitializeResult, Location,
27+
OneOf, PublishDiagnosticsParams, ReferenceParams,
2828
RelatedFullDocumentDiagnosticReport, RenameParams, SaveOptions,
2929
SelectionRange, SelectionRangeParams, SelectionRangeProviderCapability,
3030
SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
@@ -37,12 +37,14 @@ use async_lsp::router::Router;
3737
use async_lsp::{ClientSocket, LanguageClient, LanguageServer, ResponseError};
3838
use futures::future::BoxFuture;
3939

40+
use crate::documents::storage::DocumentStorage;
4041
use crate::features::code_action::code_actions;
4142
use crate::features::completion::completion;
4243
use crate::features::diagnostics::diagnostics;
4344
use crate::features::document_highlight::document_highlight;
4445
use crate::features::document_symbol::document_symbol;
4546
use crate::features::formatting::formatting;
47+
use crate::features::formatting::FormattingOptions;
4648
use crate::features::goto::go_to_definition;
4749
use crate::features::hover::hover;
4850
use crate::features::references::find_references;
@@ -52,8 +54,6 @@ use crate::features::semantic_tokens::{
5254
semantic_tokens, SEMANTIC_TOKEN_MODIFIERS, SEMANTIC_TOKEN_TYPES,
5355
};
5456

55-
use crate::documents::storage::DocumentStorage;
56-
5757
/// Represents a YARA language server.
5858
pub struct YARALanguageServer {
5959
/// Client socket for communication with the Development Tool.
@@ -123,7 +123,9 @@ impl LanguageServer for YARALanguageServer {
123123
range: Some(true),
124124
legend: SemanticTokensLegend {
125125
token_types: Vec::from(SEMANTIC_TOKEN_TYPES),
126-
token_modifiers: Vec::from(SEMANTIC_TOKEN_MODIFIERS),
126+
token_modifiers: Vec::from(
127+
SEMANTIC_TOKEN_MODIFIERS,
128+
),
127129
},
128130
..Default::default()
129131
},
@@ -147,22 +149,30 @@ impl LanguageServer for YARALanguageServer {
147149
document_highlight_provider: Some(OneOf::Left(true)),
148150
document_symbol_provider: Some(OneOf::Left(true)),
149151
rename_provider: Some(OneOf::Left(true)),
150-
code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
151-
selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)),
152+
code_action_provider: Some(
153+
CodeActionProviderCapability::Simple(true),
154+
),
155+
selection_range_provider: Some(
156+
SelectionRangeProviderCapability::Simple(true),
157+
),
152158
text_document_sync: Some(TextDocumentSyncCapability::Options(
153159
TextDocumentSyncOptions {
154-
save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions {
155-
include_text: Some(true),
156-
})),
160+
save: Some(TextDocumentSyncSaveOptions::SaveOptions(
161+
SaveOptions {
162+
include_text: Some(true),
163+
},
164+
)),
157165
open_close: Some(true),
158166
change: Some(TextDocumentSyncKind::FULL),
159167
..Default::default()
160168
},
161169
)),
162170
// This is for pull model diagnostics
163-
diagnostic_provider: Some(DiagnosticServerCapabilities::Options(
164-
DiagnosticOptions::default(),
165-
)),
171+
diagnostic_provider: Some(
172+
DiagnosticServerCapabilities::Options(
173+
DiagnosticOptions::default(),
174+
),
175+
),
166176
..ServerCapabilities::default()
167177
},
168178
server_info: None,
@@ -415,17 +425,35 @@ impl LanguageServer for YARALanguageServer {
415425
})
416426
}
417427

418-
/// This method is called when the user requests to format a document.
419-
///
420428
/// It formats the source code according to the configured style and
421429
/// returns a set of edits to apply the changes.
422430
fn formatting(
423431
&mut self,
424432
params: DocumentFormattingParams,
425433
) -> BoxFuture<'static, Result<Option<Vec<TextEdit>>, Self::Error>> {
426434
let documents = Arc::clone(&self.documents);
435+
let mut client = self.client.clone();
427436

428-
Box::pin(async move { Ok(formatting(documents, params)) })
437+
Box::pin(async move {
438+
let config = client
439+
.configuration(ConfigurationParams {
440+
items: vec![ConfigurationItem {
441+
scope_uri: Some(params.text_document.uri.clone()),
442+
section: Some("YARA.codeFormatting".to_string()),
443+
}],
444+
})
445+
.await;
446+
447+
let options = config
448+
.ok()
449+
.and_then(|mut config| config.pop())
450+
.and_then(|v| {
451+
serde_json::from_value::<FormattingOptions>(v).ok()
452+
})
453+
.unwrap_or_default();
454+
455+
Ok(formatting(documents, params, options))
456+
})
429457
}
430458

431459
/// This method is called when a document is opened.
@@ -490,6 +518,7 @@ impl LanguageServer for YARALanguageServer {
490518
/// This method is called when the server is requested to shut down.
491519
///
492520
/// It should not exit the process, but instead, it should prepare for
521+
493522
/// shutdown.
494523
fn shutdown(
495524
&mut self,

ls/src/tests/testdata/formatting1.response.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[
22
{
3-
"newText": "rule test {\n strings:\n $a = \"some string\"\n\n condition:\n $a\n}\n",
3+
"newText": "rule test {\n strings:\n $a = \"some string\"\n condition:\n $a\n}\n",
44
"range": {
55
"end": {
66
"character": 0,

0 commit comments

Comments
 (0)