Skip to content

Commit b4536eb

Browse files
committed
refactor(mcp): split monolithic tools.rs into focused submodules
Break 2233-line tools.rs God Object into per-concern files: - tools/response.rs: response helpers, ANSI stripping, truncation, formatting - tools/search.rs: search and query implementations - tools/lines.rs: get_lines and get_tail implementations - tools/context.rs: get_context implementation - tools/stats.rs: get_stats implementation - tools/mod.rs: struct, tool handlers, ServerHandler, tests Extract strip_line_info() and truncate_line_info() helpers to DRY up three near-identical strip/truncate response functions.
1 parent 3599320 commit b4536eb

File tree

7 files changed

+1371
-819
lines changed

7 files changed

+1371
-819
lines changed

CODE_REVIEW_ISSUES.json

Lines changed: 506 additions & 0 deletions
Large diffs are not rendered by default.

src/mcp/tools/context.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//! get_context tool implementation.
2+
3+
use super::response::*;
4+
use super::LazyTailMcp;
5+
use crate::index::reader::IndexReader;
6+
use crate::mcp::types::*;
7+
use crate::reader::{file_reader::FileReader, LogReader};
8+
use std::path::Path;
9+
10+
impl LazyTailMcp {
11+
#[allow(clippy::too_many_arguments)]
12+
pub(crate) fn get_context_impl(
13+
&self,
14+
path: &Path,
15+
line_number: usize,
16+
before: usize,
17+
after: usize,
18+
raw: bool,
19+
output: OutputFormat,
20+
full_content: bool,
21+
) -> String {
22+
let before_count = before.min(50);
23+
let after_count = after.min(50);
24+
25+
let mut reader = match FileReader::new(path) {
26+
Ok(r) => r,
27+
Err(e) => {
28+
return error_response(format!("Failed to open file '{}': {}", path.display(), e))
29+
}
30+
};
31+
32+
let index_reader = IndexReader::open(path);
33+
let renderer_names = self.renderer_names_for_path(path);
34+
let ctx = RenderContext {
35+
registry: &self.preset_registry,
36+
renderer_names: &renderer_names,
37+
};
38+
39+
let total = reader.total_lines();
40+
41+
if line_number >= total {
42+
return error_response(format!(
43+
"Line {} does not exist (file has {} lines)",
44+
line_number, total
45+
));
46+
}
47+
48+
// Get before lines
49+
let start_before = line_number.saturating_sub(before_count);
50+
let mut before_lines = Vec::new();
51+
for i in start_before..line_number {
52+
if let Ok(Some(content)) = reader.get_line(i) {
53+
let flags = index_reader.as_ref().and_then(|ir| ir.flags(i));
54+
let mut info = LineInfo {
55+
line_number: i,
56+
content,
57+
severity: index_reader
58+
.as_ref()
59+
.map(|ir| ir.severity(i))
60+
.and_then(|s| s.label().map(String::from)),
61+
rendered: None,
62+
};
63+
let raw_content = info.content.clone();
64+
render_line_info(&mut info, &raw_content, flags, &ctx);
65+
before_lines.push(info);
66+
}
67+
}
68+
69+
// Get target line
70+
let target_content = match reader.get_line(line_number) {
71+
Ok(Some(c)) => c,
72+
_ => return error_response("Failed to read target line"),
73+
};
74+
let target_flags = index_reader.as_ref().and_then(|ir| ir.flags(line_number));
75+
let mut target_line = LineInfo {
76+
line_number,
77+
content: target_content,
78+
severity: index_reader
79+
.as_ref()
80+
.map(|ir| ir.severity(line_number))
81+
.and_then(|s| s.label().map(String::from)),
82+
rendered: None,
83+
};
84+
let raw_target = target_line.content.clone();
85+
render_line_info(&mut target_line, &raw_target, target_flags, &ctx);
86+
87+
// Get after lines
88+
let end_after = (line_number + 1 + after_count).min(total);
89+
let mut after_lines = Vec::new();
90+
for i in (line_number + 1)..end_after {
91+
if let Ok(Some(content)) = reader.get_line(i) {
92+
let flags = index_reader.as_ref().and_then(|ir| ir.flags(i));
93+
let mut info = LineInfo {
94+
line_number: i,
95+
content,
96+
severity: index_reader
97+
.as_ref()
98+
.map(|ir| ir.severity(i))
99+
.and_then(|s| s.label().map(String::from)),
100+
rendered: None,
101+
};
102+
let raw_content = info.content.clone();
103+
render_line_info(&mut info, &raw_content, flags, &ctx);
104+
after_lines.push(info);
105+
}
106+
}
107+
108+
let mut response = GetContextResponse {
109+
before_lines,
110+
target_line,
111+
after_lines,
112+
total_lines: total,
113+
};
114+
115+
if !raw {
116+
strip_context_response(&mut response);
117+
}
118+
if !full_content {
119+
truncate_context_response(&mut response);
120+
}
121+
122+
format_context(&response, output)
123+
}
124+
}

src/mcp/tools/lines.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
//! get_lines and get_tail tool implementations.
2+
3+
use super::response::*;
4+
use super::LazyTailMcp;
5+
use crate::index::reader::IndexReader;
6+
use crate::mcp::types::*;
7+
use crate::reader::{file_reader::FileReader, LogReader};
8+
use std::path::Path;
9+
10+
impl LazyTailMcp {
11+
pub(crate) fn get_lines_impl(
12+
&self,
13+
path: &Path,
14+
start: usize,
15+
count: usize,
16+
raw: bool,
17+
output: OutputFormat,
18+
full_content: bool,
19+
) -> String {
20+
let count = count.min(1000);
21+
22+
let mut reader = match FileReader::new(path) {
23+
Ok(r) => r,
24+
Err(e) => {
25+
return error_response(format!("Failed to open file '{}': {}", path.display(), e))
26+
}
27+
};
28+
29+
let index_reader = IndexReader::open(path);
30+
let renderer_names = self.renderer_names_for_path(path);
31+
let ctx = RenderContext {
32+
registry: &self.preset_registry,
33+
renderer_names: &renderer_names,
34+
};
35+
36+
let total = reader.total_lines();
37+
let mut lines = Vec::new();
38+
for i in start..(start + count).min(total) {
39+
if let Ok(Some(content)) = reader.get_line(i) {
40+
let flags = index_reader.as_ref().and_then(|ir| ir.flags(i));
41+
let mut info = LineInfo {
42+
line_number: i,
43+
content,
44+
severity: index_reader
45+
.as_ref()
46+
.map(|ir| ir.severity(i))
47+
.and_then(|s| s.label().map(String::from)),
48+
rendered: None,
49+
};
50+
let raw_content = info.content.clone();
51+
render_line_info(&mut info, &raw_content, flags, &ctx);
52+
lines.push(info);
53+
}
54+
}
55+
56+
let mut response = GetLinesResponse {
57+
lines,
58+
total_lines: total,
59+
has_more: start + count < total,
60+
};
61+
62+
if !raw {
63+
strip_lines_response(&mut response);
64+
}
65+
if !full_content {
66+
truncate_lines_response(&mut response);
67+
}
68+
69+
format_lines(&response, output)
70+
}
71+
72+
pub(crate) fn get_tail_impl(
73+
&self,
74+
path: &Path,
75+
count: usize,
76+
since_line: Option<usize>,
77+
raw: bool,
78+
output: OutputFormat,
79+
full_content: bool,
80+
) -> String {
81+
let count = count.min(1000);
82+
83+
let mut reader = match FileReader::new(path) {
84+
Ok(r) => r,
85+
Err(e) => {
86+
return error_response(format!("Failed to open file '{}': {}", path.display(), e))
87+
}
88+
};
89+
90+
let index_reader = IndexReader::open(path);
91+
let renderer_names = self.renderer_names_for_path(path);
92+
let ctx = RenderContext {
93+
registry: &self.preset_registry,
94+
renderer_names: &renderer_names,
95+
};
96+
97+
let total = reader.total_lines();
98+
99+
let (start, end, has_more) = if let Some(since) = since_line {
100+
let start = since.saturating_add(1);
101+
let end = start.saturating_add(count).min(total);
102+
let has_more = end < total;
103+
(start, end, has_more)
104+
} else {
105+
let start = total.saturating_sub(count);
106+
(start, total, start > 0)
107+
};
108+
109+
let mut lines = Vec::new();
110+
for i in start..end {
111+
if let Ok(Some(content)) = reader.get_line(i) {
112+
let flags = index_reader.as_ref().and_then(|ir| ir.flags(i));
113+
let mut info = LineInfo {
114+
line_number: i,
115+
content,
116+
severity: index_reader
117+
.as_ref()
118+
.map(|ir| ir.severity(i))
119+
.and_then(|s| s.label().map(String::from)),
120+
rendered: None,
121+
};
122+
let raw_content = info.content.clone();
123+
render_line_info(&mut info, &raw_content, flags, &ctx);
124+
lines.push(info);
125+
}
126+
}
127+
128+
let mut response = GetLinesResponse {
129+
lines,
130+
total_lines: total,
131+
has_more,
132+
};
133+
134+
if !raw {
135+
strip_lines_response(&mut response);
136+
}
137+
if !full_content {
138+
truncate_lines_response(&mut response);
139+
}
140+
141+
format_lines(&response, output)
142+
}
143+
}

0 commit comments

Comments
 (0)