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
39 changes: 27 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ walkdir = "2.5.0"
[target.'cfg(any(target_arch = "wasm32", target_arch = "wasm64"))'.dependencies]
async-lsp = {version = "0.2.2", default-features=false, features=["omni-trait"]}
tower = { version = "0.5.2" }
wasm-bindgen-futures = {version = "0.4.61"}

[dev-dependencies]
goldenfile = { workspace = true }
5 changes: 5 additions & 0 deletions ls/editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@
"type": "string",
"default": "",
"description": "A regular expression that all rule names must match (example: APT_.*)."
},
"YARA.cacheWorkspace": {
"type": "boolean",
"default": true,
"description": "Specifies if the language server should cache all files from the workspace."
}
}
},
Expand Down
4 changes: 3 additions & 1 deletion ls/editors/code/src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ExtensionContext, window } from "vscode";
import { ExtensionContext, window, workspace } from "vscode";
import {
Executable,
LanguageClient,
Expand All @@ -11,6 +11,7 @@ import * as path from "path";
let client: LanguageClient | null = null;

export async function activate(context: ExtensionContext) {
const config = workspace.getConfiguration("YARA");
const platform = os.platform();
const arch = os.arch();

Expand Down Expand Up @@ -39,6 +40,7 @@ export async function activate(context: ExtensionContext) {
documentSelector: [{ scheme: "file", language: "yara" }],
outputChannel: outputChannel,
traceOutputChannel: outputChannel,
initializationOptions: config
};

client = new LanguageClient(
Expand Down
53 changes: 53 additions & 0 deletions ls/src/configuration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use serde::Deserialize;

/// This structure contains all client-side configuration settings,
/// which user can specify in the code editor.
#[derive(Deserialize, Default, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Config {
pub code_formatting: FormattingConfiguration,
pub metadata_validation: Vec<MetadataValidationRule>,
pub rule_name_validation: Option<String>,
pub cache_workspace: bool,
}

/// This structure represents settings for the YARA-X formatter.
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct FormattingConfiguration {
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 FormattingConfiguration {
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,
}
}
}

/// Rule that describes a how to validate a metadata entry in a rule.
#[derive(Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct MetadataValidationRule {
/// Metadata identifier
pub identifier: String,
/// Whether the metadata entry is required or not.
#[serde(default)]
pub required: bool,
/// Type of the metadata entry.
#[serde(rename = "type")]
pub ty: Option<String>,
}
6 changes: 6 additions & 0 deletions ls/src/documents/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ impl Document {
Self { uri, text, cst, line_index }
}

/// Creates a new document with precached CST.
pub fn new_with_cst(uri: Url, text: String, cst: CST) -> Self {
let line_index = LineIndex::new(text.as_str());
Self { uri, text, cst, line_index }
}

/// Updates all stored structures.
pub fn update(&mut self, text: String) {
self.cst = CST::from(text.as_str());
Expand Down
89 changes: 85 additions & 4 deletions ls/src/documents/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
use std::fs;

use async_lsp::lsp_types::Url;
use async_lsp::lsp_types::{FileChangeType, FileEvent, Url};
use dashmap::{mapref::one::Ref, DashMap};
use yara_x_parser::cst::{Immutable, Node, SyntaxKind, Token, CST};

Expand All @@ -23,6 +23,7 @@ pub struct OccurrencesResult {
#[derive(Default)]
pub struct DocumentStorage {
opened: DashMap<Url, Document>,
cached: DashMap<Url, CST>,
workspace: Option<Url>,
}

Expand All @@ -38,7 +39,12 @@ impl DocumentStorage {

/// Inserts a new document with the specified content.
pub fn insert(&self, uri: Url, text: String) {
self.opened.insert(uri.clone(), Document::new(uri, text));
// Checks if the opened document is already in cache and remove it.
let document = match self.cached.remove(&uri) {
Some((_, cst)) => Document::new_with_cst(uri.clone(), text, cst),
None => Document::new(uri.clone(), text),
};
self.opened.insert(uri.clone(), document);
}

/// Updates document content and its internal structures.
Expand All @@ -49,8 +55,15 @@ impl DocumentStorage {
}

/// Removes the document and returns (key,value) before removing.
pub fn remove(&self, uri: &Url) -> Option<(Url, Document)> {
self.opened.remove(uri)
pub fn remove(&self, uri: &Url, cache_enabled: bool) {
if let Some((uri, document)) = self.opened.remove(uri) {
// If the workspace caching is enabled we want to
// move CST of the closed document to the cache.
if cache_enabled {
let cst = document.cst;
self.cached.insert(uri, cst);
}
}
}

/// Sets workspace folder to the specified URI.
Expand All @@ -64,6 +77,8 @@ impl DocumentStorage {
fn get_document_cst_root(&self, uri: &Url) -> Option<Node<Immutable>> {
if let Some(doc) = self.get(uri) {
Some(doc.cst.root())
} else if let Some(cst) = self.cached.get(uri) {
Some(cst.root())
} else {
uri.to_file_path().ok().and_then(|path| {
fs::read_to_string(path)
Expand Down Expand Up @@ -321,4 +336,70 @@ impl DocumentStorage {

rules
}

/// Reads all files from the workspace and initializes cache iwth CST
/// for each YARA file.
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
pub fn cache_workspace(&self) {
if let Some(workspace_files) = self
.walk_workspace()
.map(|workspace| workspace.collect::<Vec<Url>>())
{
for entry in workspace_files {
if self.opened.contains_key(&entry) {
continue;
}

if let Some(cst) = entry
.to_file_path()
.ok()
.and_then(|path| fs::read_to_string(path).ok())
.map(|content| CST::from(content.as_str()))
{
self.cached.insert(entry, cst);
}
}
}
}

#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
pub fn cache_workspace(&self) {}

/// Clears the cache.
pub fn clear_cache(&self) {
self.cached.clear();
}

/// Reacts to the file system changes within the workspace by making
/// changes in the cache.
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
pub fn react_watched_files_changes(&self, changes: Vec<FileEvent>) {
for change in changes {
// Opened files are synchronized in `textDocument/did*` methods.
if self.opened.contains_key(&change.uri) {
continue;
}

match change.typ {
FileChangeType::CHANGED | FileChangeType::CREATED => {
if let Some(cst) =
change.uri.to_file_path().ok().and_then(|path| {
fs::read_to_string(path)
.ok()
.map(|content| CST::from(content.as_str()))
})
{
self.cached.insert(change.uri, cst);
}
}
FileChangeType::DELETED => {
self.cached.remove(&change.uri);
}
_ => {}
}
}
}

#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
pub fn react_watched_files_changes(&self, changes: Vec<FileEvent>) {}
}
Loading
Loading