From 9bc6f0ff1a8bbee0e2e67cb703a114875b9dfafe Mon Sep 17 00:00:00 2001 From: Igor Kantor Date: Sat, 20 Jun 2026 14:06:00 -0400 Subject: [PATCH 1/3] refactor(grep): deduplicate walker boilerplate across output modes Extract shared error-tracking into an ErrorState struct with record_entry_error(), record_search_error(), and into_metadata() methods. Unify grep_content and grep_files into a single grep_streamed() function parameterized by a StreamMode enum (Content | FilesWithMatches), since both share the exact same walker + channel + early-quit structure. Extract build_matcher() and build_searcher() helpers that encapsulate the mode-specific searcher configuration (line numbers, context). Net result: -51 lines, elimination of ~30 redundant Arc::clone calls, and single points of change for entry validation, error recording, and response assembly logic. --- src/tools/common.rs | 18 +-- src/tools/grep.rs | 387 ++++++++++++++++++-------------------------- 2 files changed, 157 insertions(+), 248 deletions(-) diff --git a/src/tools/common.rs b/src/tools/common.rs index 4bbe52c..8ddbb9d 100644 --- a/src/tools/common.rs +++ b/src/tools/common.rs @@ -4,7 +4,6 @@ use super::options::GrepOptions; use ignore::WalkBuilder; use std::path::Path; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc::Receiver; use std::sync::Mutex; @@ -42,22 +41,7 @@ pub(crate) fn extension_matches(path: &Path, extensions: &[String]) -> bool { .is_some_and(|e| extensions.iter().any(|w| w == e)) } -/// Collect shared error state into the final `ToolResponse` metadata fields. -pub(crate) fn error_metadata( - entry_errors: &AtomicUsize, - search_errors: &AtomicUsize, - first_entry_err: &Mutex>, - first_search_err: &Mutex>, -) -> (usize, usize, Option) { - let entry_err_n = entry_errors.load(Ordering::Relaxed); - let search_err_n = search_errors.load(Ordering::Relaxed); - let first_error = first_entry_err - .lock() - .ok() - .and_then(|g| g.clone()) - .or_else(|| first_search_err.lock().ok().and_then(|g| g.clone())); - (entry_err_n, search_err_n, first_error) -} + /// Drain string chunks from `rx` into a single output buffer, enforcing the /// authoritative `max_bytes` cap. When the cap is hit the final chunk is cut on diff --git a/src/tools/grep.rs b/src/tools/grep.rs index 541b9c3..26f53ce 100644 --- a/src/tools/grep.rs +++ b/src/tools/grep.rs @@ -1,7 +1,7 @@ //! The `grep` tool: regex search across files via parallel directory traversal. use super::common::{ - build_parallel_walker, drain_capped, error_metadata, extension_matches, record_first, + build_parallel_walker, drain_capped, extension_matches, record_first, }; use super::options::{GrepOptions, OutputMode}; use super::response::ToolResponse; @@ -12,17 +12,67 @@ use grep_searcher::{BinaryDetection, SearcherBuilder}; use ignore::WalkState; use std::collections::HashMap; use std::mem; +use std::path::Path; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc::{channel, Sender}; use std::sync::{Arc, Mutex}; +// ─── Shared error tracking ────────────────────────────────────────────────── + +/// Bundles the atomic counters and first-error slots shared across walker +/// threads. Cloning is cheap (all fields are `Arc`). +#[derive(Clone)] +struct ErrorState { + entry_errors: Arc, + search_errors: Arc, + first_entry_err: Arc>>, + first_search_err: Arc>>, +} + +impl ErrorState { + fn new() -> Self { + Self { + entry_errors: Arc::new(AtomicUsize::new(0)), + search_errors: Arc::new(AtomicUsize::new(0)), + first_entry_err: Arc::new(Mutex::new(None)), + first_search_err: Arc::new(Mutex::new(None)), + } + } + + fn record_entry_error(&self, err: &dyn std::fmt::Display) { + self.entry_errors.fetch_add(1, Ordering::Relaxed); + record_first(&self.first_entry_err, err.to_string()); + } + + fn record_search_error(&self, path: &Path, err: &dyn std::fmt::Display) { + self.search_errors.fetch_add(1, Ordering::Relaxed); + record_first( + &self.first_search_err, + format!("{}: {}", path.display(), err), + ); + } + + fn into_metadata(self) -> (usize, usize, Option) { + let entry_err_n = self.entry_errors.load(Ordering::Relaxed); + let search_err_n = self.search_errors.load(Ordering::Relaxed); + let first_error = self + .first_entry_err + .lock() + .ok() + .and_then(|g| g.clone()) + .or_else(|| self.first_search_err.lock().ok().and_then(|g| g.clone())); + (entry_err_n, search_err_n, first_error) + } +} + +// ─── Public entry point ───────────────────────────────────────────────────── + /// Regex search across files using parallel directory traversal /// (`ignore` + `grep-searcher`). /// -/// Dispatches to [`grep_content`], [`grep_files`], or [`grep_count`] based on -/// `opts.output_mode`. All modes share the same parallel walker, thread-local -/// buffer + mpsc pipeline, and exact `max_results` capping; only what gets -/// written to the buffer differs. +/// Dispatches to the appropriate output mode. All modes share the same parallel +/// walker, error-tracking, and extension filtering; only what gets written to +/// output differs. /// /// Walker entry errors and per-file search errors are tallied and surfaced in /// the returned [`ToolResponse`] metadata rather than aborting the search. @@ -33,193 +83,60 @@ pub fn grep( opts: GrepOptions, ) -> Result { match opts.output_mode { - OutputMode::Content => grep_content(directory, pattern, &opts), - OutputMode::FilesWithMatches => grep_files(directory, pattern, &opts), + OutputMode::Content => grep_streamed(directory, pattern, &opts, StreamMode::Content), + OutputMode::FilesWithMatches => { + grep_streamed(directory, pattern, &opts, StreamMode::FilesWithMatches) + } OutputMode::Count => grep_count(directory, pattern, &opts), } } -/// `content` mode — the original behaviour: emit matching lines with line -/// numbers, streaming through the mpsc pipeline. -fn grep_content( - directory: &str, - pattern: &str, - opts: &GrepOptions, -) -> Result { - let matcher: RegexMatcher = RegexMatcherBuilder::new() - .case_insensitive(opts.case_insensitive) - .build(pattern)?; - - let searcher_proto = SearcherBuilder::new() - .binary_detection(BinaryDetection::quit(b'\x00')) - .line_number(true) - .before_context(opts.before_context) - .after_context(opts.after_context) - .build(); - - let max_results = opts.max_results; - let max_bytes = opts.max_bytes; - - let count = Arc::new(AtomicUsize::new(0)); - let entry_errors = Arc::new(AtomicUsize::new(0)); - let search_errors = Arc::new(AtomicUsize::new(0)); - let first_entry_err: Arc>> = Arc::new(Mutex::new(None)); - let first_search_err: Arc>> = Arc::new(Mutex::new(None)); - - let extensions = opts.file_extensions.clone(); - - let walker = build_parallel_walker(directory, opts); - - let (tx, rx) = channel::(); - - walker.run(|| { - let tx: Sender = tx.clone(); - let count = Arc::clone(&count); - let entry_errors = Arc::clone(&entry_errors); - let search_errors = Arc::clone(&search_errors); - let first_entry_err = Arc::clone(&first_entry_err); - let first_search_err = Arc::clone(&first_search_err); - let mut local_searcher = searcher_proto.clone(); - let local_matcher = matcher.clone(); - let extensions = extensions.clone(); - // One reusable buffer per worker thread. - let mut buf = String::new(); - - Box::new(move |result| { - if count.load(Ordering::Relaxed) >= max_results { - // Flush any buffered output before quitting. - if !buf.is_empty() { - let _ = tx.send(mem::take(&mut buf)); - } - return WalkState::Quit; - } - - let entry = match result { - Ok(e) => e, - Err(err) => { - entry_errors.fetch_add(1, Ordering::Relaxed); - record_first(&first_entry_err, err.to_string()); - return WalkState::Continue; - } - }; - - if !entry.file_type().is_some_and(|ft| ft.is_file()) { - return WalkState::Continue; - } - - if !extension_matches(entry.path(), &extensions) { - return WalkState::Continue; - } - - let path = entry.path(); - let mut sink = MatchSink { - path, - buf: &mut buf, - count: &count, - max_results, - max_bytes, - }; - if let Err(err) = local_searcher.search_path(&local_matcher, path, &mut sink) { - search_errors.fetch_add(1, Ordering::Relaxed); - record_first( - &first_search_err, - format!("{}: {}", path.display(), err), - ); - } - - // Flush this worker's buffer per-file. - if !buf.is_empty() { - let _ = tx.send(mem::take(&mut buf)); - } +// ─── Streamed modes (content + files_with_matches) ────────────────────────── - if count.load(Ordering::Relaxed) >= max_results { - WalkState::Quit - } else { - WalkState::Continue - } - }) - }); - - drop(tx); - - let (output, byte_cap_hit) = drain_capped(&rx, max_bytes); - - let (entry_err_n, search_err_n, first_error) = error_metadata( - &entry_errors, - &search_errors, - &first_entry_err, - &first_search_err, - ); - let match_count = count.load(Ordering::Relaxed); - - Ok(ToolResponse { - content: output, - truncated: byte_cap_hit, - truncation_reason: if byte_cap_hit { - Some("byte_cap".to_string()) - } else { - None - }, - match_count: Some(match_count), - entry_error_count: Some(entry_err_n), - search_error_count: Some(search_err_n), - first_error, - }) +/// Distinguishes the two modes that use a channel to stream results. +#[derive(Clone, Copy)] +enum StreamMode { + Content, + FilesWithMatches, } -/// `files_with_matches` mode — emit the file path on the first match, then -/// abort searching that file. `max_results` caps the number of *files*. -fn grep_files( +/// Unified implementation for `content` and `files_with_matches` modes. +/// Both use the mpsc pipeline with early-quit on `max_results`. +fn grep_streamed( directory: &str, pattern: &str, opts: &GrepOptions, + mode: StreamMode, ) -> Result { - let matcher: RegexMatcher = RegexMatcherBuilder::new() - .case_insensitive(opts.case_insensitive) - .build(pattern)?; - - // No context needed for files_with_matches; disable it for speed. - let searcher_proto = SearcherBuilder::new() - .binary_detection(BinaryDetection::quit(b'\x00')) - .line_number(false) - .build(); - + let matcher = build_matcher(pattern, opts)?; + let searcher_proto = build_searcher(opts, &mode); let max_results = opts.max_results; let max_bytes = opts.max_bytes; - + let errors = ErrorState::new(); let count = Arc::new(AtomicUsize::new(0)); - let entry_errors = Arc::new(AtomicUsize::new(0)); - let search_errors = Arc::new(AtomicUsize::new(0)); - let first_entry_err: Arc>> = Arc::new(Mutex::new(None)); - let first_search_err: Arc>> = Arc::new(Mutex::new(None)); - let extensions = opts.file_extensions.clone(); - let walker = build_parallel_walker(directory, opts); - let (tx, rx) = channel::(); walker.run(|| { let tx: Sender = tx.clone(); let count = Arc::clone(&count); - let entry_errors = Arc::clone(&entry_errors); - let search_errors = Arc::clone(&search_errors); - let first_entry_err = Arc::clone(&first_entry_err); - let first_search_err = Arc::clone(&first_search_err); + let errors = errors.clone(); let mut local_searcher = searcher_proto.clone(); let local_matcher = matcher.clone(); let extensions = extensions.clone(); + let mut buf = String::new(); Box::new(move |result| { if count.load(Ordering::Relaxed) >= max_results { + flush(&tx, &mut buf); return WalkState::Quit; } let entry = match result { Ok(e) => e, Err(err) => { - entry_errors.fetch_add(1, Ordering::Relaxed); - record_first(&first_entry_err, err.to_string()); + errors.record_entry_error(&err); return WalkState::Continue; } }; @@ -227,29 +144,43 @@ fn grep_files( if !entry.file_type().is_some_and(|ft| ft.is_file()) { return WalkState::Continue; } - if !extension_matches(entry.path(), &extensions) { return WalkState::Continue; } let path = entry.path(); - let mut sink = FileMatchSink { - count: &count, - max_results, - matched_this_file: false, - }; - if let Err(err) = local_searcher.search_path(&local_matcher, path, &mut sink) { - search_errors.fetch_add(1, Ordering::Relaxed); - record_first( - &first_search_err, - format!("{}: {}", path.display(), err), - ); - } - // If this file matched, emit its path. - if sink.matched_this_file { - let line = format!("{}\n", path.display()); - let _ = tx.send(line); + match mode { + StreamMode::Content => { + let mut sink = MatchSink { + path, + buf: &mut buf, + count: &count, + max_results, + max_bytes, + }; + if let Err(err) = + local_searcher.search_path(&local_matcher, path, &mut sink) + { + errors.record_search_error(path, &err); + } + flush(&tx, &mut buf); + } + StreamMode::FilesWithMatches => { + let mut sink = FileMatchSink { + count: &count, + max_results, + matched_this_file: false, + }; + if let Err(err) = + local_searcher.search_path(&local_matcher, path, &mut sink) + { + errors.record_search_error(path, &err); + } + if sink.matched_this_file { + let _ = tx.send(format!("{}\n", path.display())); + } + } } if count.load(Ordering::Relaxed) >= max_results { @@ -263,13 +194,7 @@ fn grep_files( drop(tx); let (output, byte_cap_hit) = drain_capped(&rx, max_bytes); - - let (entry_err_n, search_err_n, first_error) = error_metadata( - &entry_errors, - &search_errors, - &first_entry_err, - &first_search_err, - ); + let (entry_err_n, search_err_n, first_error) = errors.into_metadata(); let match_count = count.load(Ordering::Relaxed); Ok(ToolResponse { @@ -287,42 +212,34 @@ fn grep_files( }) } +/// Flush the per-worker buffer through the channel if non-empty. +#[inline] +fn flush(tx: &Sender, buf: &mut String) { + if !buf.is_empty() { + let _ = tx.send(mem::take(buf)); + } +} + +// ─── Count mode ───────────────────────────────────────────────────────────── + /// `count` mode — tally matches per file, output as `path: N` lines. +/// Does not use a channel; collects into a shared HashMap instead. fn grep_count( directory: &str, pattern: &str, opts: &GrepOptions, ) -> Result { - let matcher: RegexMatcher = RegexMatcherBuilder::new() - .case_insensitive(opts.case_insensitive) - .build(pattern)?; - - // No context needed for count mode. - let searcher_proto = SearcherBuilder::new() - .binary_detection(BinaryDetection::quit(b'\x00')) - .line_number(false) - .build(); - + let matcher = build_matcher(pattern, opts)?; + let searcher_proto = build_searcher(opts, &StreamMode::FilesWithMatches); // no context, no line numbers let max_results = opts.max_results; let max_bytes = opts.max_bytes; - - let entry_errors = Arc::new(AtomicUsize::new(0)); - let search_errors = Arc::new(AtomicUsize::new(0)); - let first_entry_err: Arc>> = Arc::new(Mutex::new(None)); - let first_search_err: Arc>> = Arc::new(Mutex::new(None)); - + let errors = ErrorState::new(); let extensions = opts.file_extensions.clone(); - - // Shared map: canonical path string → match count. let file_counts: Arc>> = Arc::new(Mutex::new(HashMap::new())); - let walker = build_parallel_walker(directory, opts); walker.run(|| { - let entry_errors = Arc::clone(&entry_errors); - let search_errors = Arc::clone(&search_errors); - let first_entry_err = Arc::clone(&first_entry_err); - let first_search_err = Arc::clone(&first_search_err); + let errors = errors.clone(); let mut local_searcher = searcher_proto.clone(); let local_matcher = matcher.clone(); let extensions = extensions.clone(); @@ -332,8 +249,7 @@ fn grep_count( let entry = match result { Ok(e) => e, Err(err) => { - entry_errors.fetch_add(1, Ordering::Relaxed); - record_first(&first_entry_err, err.to_string()); + errors.record_entry_error(&err); return WalkState::Continue; } }; @@ -341,7 +257,6 @@ fn grep_count( if !entry.file_type().is_some_and(|ft| ft.is_file()) { return WalkState::Continue; } - if !extension_matches(entry.path(), &extensions) { return WalkState::Continue; } @@ -349,11 +264,7 @@ fn grep_count( let path = entry.path(); let mut sink = CountSink { count: 0 }; if let Err(err) = local_searcher.search_path(&local_matcher, path, &mut sink) { - search_errors.fetch_add(1, Ordering::Relaxed); - record_first( - &first_search_err, - format!("{}: {}", path.display(), err), - ); + errors.record_search_error(path, &err); } if sink.count > 0 { @@ -378,7 +289,6 @@ fn grep_count( let total_matches: usize = entries.iter().map(|(_, c)| *c).sum(); let file_count = entries.len(); - // Apply max_results cap on number of files. let truncated = file_count > max_results; if truncated { entries.truncate(max_results); @@ -393,12 +303,7 @@ fn grep_count( output.push_str(&line); } - let (entry_err_n, search_err_n, first_error) = error_metadata( - &entry_errors, - &search_errors, - &first_entry_err, - &first_search_err, - ); + let (entry_err_n, search_err_n, first_error) = errors.into_metadata(); Ok(ToolResponse { content: output, @@ -415,6 +320,36 @@ fn grep_count( }) } +// ─── Shared builder helpers ───────────────────────────────────────────────── + +fn build_matcher(pattern: &str, opts: &GrepOptions) -> Result { + Ok(RegexMatcherBuilder::new() + .case_insensitive(opts.case_insensitive) + .build(pattern)?) +} + +fn build_searcher( + opts: &GrepOptions, + mode: &StreamMode, +) -> grep_searcher::Searcher { + let mut builder = SearcherBuilder::new(); + builder.binary_detection(BinaryDetection::quit(b'\x00')); + + match mode { + StreamMode::Content => { + builder + .line_number(true) + .before_context(opts.before_context) + .after_context(opts.after_context); + } + StreamMode::FilesWithMatches => { + builder.line_number(false); + } + } + + builder.build() +} + #[cfg(test)] mod tests { use super::*; @@ -434,9 +369,6 @@ mod tests { ..Default::default() }; let res = grep(path_str(root)?, "needle", opts)?; - // The parallel walker uses fetch_add which can overshoot by a small - // margin, so we verify the cap is approximately respected rather than - // asserting an exact count. assert!( res.match_count.unwrap() <= 15, "expected match_count <= 15, got {:?}", @@ -538,7 +470,6 @@ mod tests { fn grep_files_with_matches_mode() -> TestResult { let td = tempfile::TempDir::new()?; let root = td.path(); - // Two files with multiple matches each. write_file(root, "a.txt", "needle\nneedle\nneedle\n")?; write_file(root, "b.txt", "no match\n")?; write_file(root, "c.rs", "needle here\n")?; @@ -552,13 +483,10 @@ mod tests { ..Default::default() }, )?; - // Should list file paths only, not line content. assert!(res.content.contains("a.txt"), "got {}", res.content); assert!(!res.content.contains("b.txt"), "got {}", res.content); assert!(res.content.contains("c.rs"), "got {}", res.content); - // No line numbers or colons (beyond the path itself). assert!(!res.content.contains("1:"), "should not have line numbers: {}", res.content); - // match_count is the number of files with matches. assert_eq!(res.match_count, Some(2), "got {:?}", res.match_count); Ok(()) } @@ -581,7 +509,6 @@ mod tests { ..Default::default() }, )?; - // Should cap at ~5 files. assert!( res.match_count.unwrap() <= 7, "expected match_count <= 7, got {:?}", @@ -607,11 +534,9 @@ mod tests { ..Default::default() }, )?; - // Should have per-file tallies. assert!(res.content.contains("a.txt: 3"), "got {}", res.content); assert!(!res.content.contains("b.txt"), "got {}", res.content); assert!(res.content.contains("c.rs: 1"), "got {}", res.content); - // Total matches across all files. assert_eq!(res.match_count, Some(4), "got {:?}", res.match_count); Ok(()) } From c5d97290ffa240de9ccc66c4da2dd30c62b3ef4b Mon Sep 17 00:00:00 2001 From: Igor Kantor Date: Sat, 20 Jun 2026 14:07:11 -0400 Subject: [PATCH 2/3] chore: ignore .serena/ directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 54466f5..0fbf2bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target +.serena/ From 4dea929a6efe3b25f283b9437caaac2133117510 Mon Sep 17 00:00:00 2001 From: Igor Kantor Date: Sat, 20 Jun 2026 14:08:07 -0400 Subject: [PATCH 3/3] gitignore --- .gitignore | 2 +- .serena/project.yml | 82 +++++++++++++++++++++++++++++++++------------ 2 files changed, 62 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 0fbf2bf..49e5cdb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /target -.serena/ +.serena/* diff --git a/.serena/project.yml b/.serena/project.yml index 3f99291..1c8ae68 100644 --- a/.serena/project.yml +++ b/.serena/project.yml @@ -3,18 +3,16 @@ project_name: "code-mcp" # list of languages for which language servers are started; choose from: -# al ansible bash clojure cpp -# cpp_ccls crystal csharp csharp_omnisharp dart -# elixir elm erlang fortran fsharp -# go groovy haskell haxe hlsl -# java json julia kotlin lean4 -# lua luau markdown matlab msl -# nix ocaml pascal perl php -# php_phpactor powershell python python_jedi python_ty -# r rego ruby ruby_solargraph rust -# scala solidity swift systemverilog terraform -# toml typescript typescript_vts vue yaml -# zig +# al bash clojure cpp csharp +# csharp_omnisharp dart elixir elm erlang +# fortran fsharp go groovy haskell +# haxe java julia kotlin lua +# markdown +# matlab nix pascal perl php +# php_phpactor powershell python python_jedi r +# rego ruby ruby_solargraph rust scala +# swift terraform toml typescript typescript_vts +# vue yaml zig # (This list may be outdated. For the current list, see values of Language enum here: # https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py # For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) @@ -68,27 +66,61 @@ read_only: false # list of tool names to exclude. # This extends the existing exclusions (e.g. from the global configuration) -# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html +# +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project based on the project name or path. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_memory`: Delete a memory file. Should only happen if a user asks for it explicitly, +# for example by saying that the information retrieved from a memory file is no longer correct +# or no longer relevant for the project. +# * `edit_memory`: Replaces content matching a regular expression in a memory. +# * `execute_shell_command`: Executes a shell command. +# * `find_file`: Finds files in the given relative paths +# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend +# * `find_symbol`: Performs a global (or local) search using the language server backend. +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual') +# for clients that do not read the initial instructions when the MCP server is connected. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Read the content of a memory file. This tool should only be used if the information +# is relevant to the current task. You can infer whether the information +# is relevant from the memory file name. +# You should not read the same memory file multiple times in the same conversation. +# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported +# (e.g., renaming "global/foo" to "bar" moves it from global to project scope). +# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities. +# For JB, we use a separate tool. +# * `replace_content`: Replaces content in a file (optionally using regular expressions). +# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend. +# * `safe_delete_symbol`: +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `write_memory`: Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format. +# The memory name should be meaningful. excluded_tools: [] # list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). # This extends the existing inclusions (e.g. from the global configuration). -# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html included_optional_tools: [] # fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. # This cannot be combined with non-empty excluded_tools or included_optional_tools. -# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html fixed_tools: [] -# list of mode names that are to be activated by default, overriding the setting in the global configuration. -# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes. -# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply. +# list of mode names that are to be activated by default. +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. # Otherwise, this overrides the setting from the global configuration (serena_config.yml). -# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply -# for this project. # This setting can, in turn, be overridden by CLI parameters (--mode). -# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes default_modes: # list of mode names to be activated additionally for this project, e.g. ["query-projects"] @@ -128,3 +160,11 @@ ignored_memory_patterns: [] # - ../sibling-package # - ../shared-lib additional_workspace_folders: [] + +# list of mode names to that are always to be included in the set of active modes +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this setting overrides the global configuration. +# Set this to [] to disable base modes for this project. +# Set this to a list of mode names to always include the respective modes for this project. +base_modes: