Skip to content

Commit cb247ad

Browse files
feat: print read file contents (#659)
* feat: print read file contents * fix: failing test
1 parent f88cc1c commit cb247ad

File tree

4 files changed

+158
-149
lines changed

4 files changed

+158
-149
lines changed

crates/q_cli/src/cli/chat/tools/fs_read.rs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use super::{
2222
InvokeOutput,
2323
OutputKind,
2424
format_path,
25+
stylize_output_if_able,
2526
};
2627

2728
#[derive(Debug, Deserialize)]
@@ -49,10 +50,8 @@ impl FsRead {
4950
// we need to pass it through the Context first.
5051
let path = ctx.fs().chroot_path_str(&self.path);
5152
let is_file = ctx.fs().symlink_metadata(&self.path).await?.is_file();
52-
let cwd = ctx.env().current_dir()?;
5353

5454
if is_file {
55-
let relative_path = format_path(&cwd, &path);
5655
if let Some((start, Some(end))) = self.read_range()? {
5756
// TODO: file size limit?
5857
// TODO: don't read entire file in memory
@@ -68,42 +67,48 @@ impl FsRead {
6867
}
6968
};
7069
let (start, end) = (convert_index(start), convert_index(end));
71-
queue!(
72-
updates,
73-
style::SetForegroundColor(Color::Green),
74-
style::Print(format!("Reading lines {}-{} in {}", start + 1, end + 1, relative_path)),
75-
style::ResetColor,
76-
style::Print("\n"),
77-
)?;
78-
70+
// quick hack check in case of invalid model input
7971
if start > end {
8072
return Ok(InvokeOutput {
8173
output: OutputKind::Text(String::new()),
8274
});
8375
}
84-
8576
// The range should be inclusive on both ends.
86-
let f = file
77+
let file_contents = file
8778
.lines()
8879
.skip(start)
8980
.take(end - start + 1)
9081
.collect::<Vec<_>>()
9182
.join("\n");
83+
queue!(
84+
updates,
85+
style::SetForegroundColor(Color::Green),
86+
style::Print("Reading:\n"),
87+
style::ResetColor,
88+
style::Print("\n"),
89+
)?;
90+
91+
let formatted = stylize_output_if_able(&path, file_contents.as_str(), Some(start + 1), None);
92+
queue!(updates, style::Print(formatted), style::ResetColor, style::Print("\n"))?;
9293
return Ok(InvokeOutput {
93-
output: OutputKind::Text(f),
94+
output: OutputKind::Text(file_contents),
9495
});
9596
}
9697

98+
let file = ctx.fs().read_to_string(&path).await?;
99+
let file_text = stylize_output_if_able(path, file.as_str(), None, None);
97100
queue!(
98101
updates,
99102
style::SetForegroundColor(Color::Green),
100-
style::Print(format!("Reading {}", relative_path)),
103+
style::Print("Reading:\n"),
104+
style::ResetColor,
105+
style::Print(file_text),
101106
style::ResetColor,
102107
style::Print("\n"),
103108
)?;
104-
return Ok(InvokeOutput {
105-
output: OutputKind::Text(ctx.fs().read_to_string(&path).await?),
106-
});
109+
Ok(InvokeOutput {
110+
output: OutputKind::Text(file),
111+
})
107112
} else {
108113
let cwd = ctx.env().current_dir()?;
109114
let max_depth = self.read_range()?.map_or(0, |(d, _)| d);

crates/q_cli/src/cli/chat/tools/fs_write.rs

Lines changed: 2 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,26 @@
11
use std::borrow::Cow;
22
use std::io::Write;
3-
use std::path::{
4-
Path,
5-
PathBuf,
6-
};
7-
use std::sync::LazyLock;
3+
use std::path::PathBuf;
84

95
use crossterm::queue;
106
use crossterm::style::{
117
self,
128
Color,
139
};
1410
use eyre::{
15-
ContextCompat,
1611
Result,
1712
bail,
1813
eyre,
1914
};
2015
use fig_os_shim::Context;
2116
use serde::Deserialize;
22-
use syntect::easy::HighlightLines;
23-
use syntect::highlighting::ThemeSet;
24-
use syntect::parsing::SyntaxSet;
25-
use syntect::util::{
26-
LinesWithEndings,
27-
as_24_bit_terminal_escaped,
28-
};
29-
use tracing::error;
3017

3118
use super::{
3219
InvokeOutput,
3320
format_path,
21+
stylize_output_if_able,
3422
};
3523

36-
static SYNTAX_SET: LazyLock<SyntaxSet> = LazyLock::new(SyntaxSet::load_defaults_newlines);
37-
static THEME_SET: LazyLock<ThemeSet> = LazyLock::new(ThemeSet::load_defaults);
38-
3924
#[derive(Debug, Deserialize)]
4025
#[serde(tag = "command")]
4126
pub enum FsWrite {
@@ -254,103 +239,6 @@ fn truncate_str(text: &str, max_len: usize) -> Cow<'_, str> {
254239
}
255240
}
256241

257-
/// Returns the number of characters required for displaying line numbers for `file_text`.
258-
fn terminal_width(line_count: usize) -> usize {
259-
((line_count as f32 + 0.1).log10().ceil()) as usize
260-
}
261-
262-
fn stylize_output_if_able<'a>(
263-
path: impl AsRef<Path>,
264-
file_text: &'a str,
265-
starting_line: Option<usize>,
266-
gutter_prefix: Option<&str>,
267-
) -> Cow<'a, str> {
268-
match stylized_file(path, file_text, starting_line, gutter_prefix) {
269-
Ok(s) => s.into(),
270-
Err(err) => {
271-
error!(?err, "unable to syntax highlight the output");
272-
file_text.into()
273-
},
274-
}
275-
}
276-
277-
/// Returns a 24bit terminal escaped syntax-highlighted [String] of the file pointed to by `path`,
278-
/// if able.
279-
///
280-
/// Params:
281-
/// - `starting_line` - 1-indexed line to start the line number at.
282-
/// - `gutter_prefix` - character to display in the first cell of the gutter, before the file
283-
/// number.
284-
fn stylized_file(
285-
path: impl AsRef<Path>,
286-
file_text: impl AsRef<str>,
287-
starting_line: Option<usize>,
288-
gutter_prefix: Option<&str>,
289-
) -> Result<String> {
290-
let starting_line = starting_line.unwrap_or(1);
291-
let gutter_prefix = gutter_prefix.unwrap_or(" ");
292-
293-
let ps = &*SYNTAX_SET;
294-
let ts = &*THEME_SET;
295-
296-
let extension = path
297-
.as_ref()
298-
.extension()
299-
.wrap_err("missing extension")?
300-
.to_str()
301-
.wrap_err("not utf8")?;
302-
303-
let syntax = ps
304-
.find_syntax_by_extension(extension)
305-
.wrap_err_with(|| format!("missing extension: {}", extension))?;
306-
307-
let theme = &ts.themes["base16-ocean.dark"];
308-
let mut h = HighlightLines::new(syntax, theme);
309-
let gutter_width = terminal_width(file_text.as_ref().lines().count()) + terminal_width(starting_line);
310-
let file_text = LinesWithEndings::from(file_text.as_ref());
311-
let (gutter_fg, gutter_bg) = match (
312-
theme.settings.gutter_foreground,
313-
theme.settings.gutter,
314-
theme.settings.foreground,
315-
theme.settings.background,
316-
) {
317-
(Some(gutter_fg), Some(gutter_bg), _, _) => (gutter_fg, gutter_bg),
318-
(_, _, Some(fg), Some(bg)) => (fg, bg),
319-
_ => bail!("missing theme"),
320-
};
321-
let gutter_prefix_style = syntect::highlighting::Style {
322-
foreground: gutter_fg,
323-
background: gutter_bg,
324-
font_style: syntect::highlighting::FontStyle::BOLD,
325-
};
326-
let gutter_linenum_style = syntect::highlighting::Style {
327-
foreground: gutter_fg,
328-
background: gutter_bg,
329-
font_style: syntect::highlighting::FontStyle::default(),
330-
};
331-
332-
let mut file = String::new();
333-
// We need to append newlines here for some reason, otherwise the highlighting ends at the end
334-
// of the content for the first line.
335-
file.push_str(&as_24_bit_terminal_escaped(&[(gutter_linenum_style, "\n\n")], true));
336-
for (i, line) in file_text.enumerate() {
337-
let i = (i + starting_line).to_string();
338-
let gutter_content = format!("{:>width$} ", i, width = gutter_width);
339-
let mut ranges = vec![
340-
(gutter_prefix_style, gutter_prefix),
341-
(gutter_linenum_style, gutter_content.as_str()),
342-
];
343-
ranges.append(&mut h.highlight_line(line, ps)?);
344-
let escaped_line = as_24_bit_terminal_escaped(&ranges[..], true);
345-
file.push_str(&escaped_line);
346-
}
347-
if !file.ends_with("\n") {
348-
file.push('\n');
349-
}
350-
351-
Ok(file)
352-
}
353-
354242
/// Returns a 1-indexed line number range of the start and end of `needle` inside `file`.
355243
fn line_number_at(file: impl AsRef<str>, needle: impl AsRef<str>) -> Option<(usize, usize)> {
356244
let file = file.as_ref();
@@ -643,14 +531,4 @@ mod tests {
643531
let s = "Hello, world!";
644532
assert_eq!(truncate_str(s, 0), "<...Truncated>");
645533
}
646-
647-
#[test]
648-
fn test_gutter_width() {
649-
assert_eq!(terminal_width(1), 1);
650-
assert_eq!(terminal_width(9), 1);
651-
assert_eq!(terminal_width(10), 2);
652-
assert_eq!(terminal_width(99), 2);
653-
assert_eq!(terminal_width(100), 3);
654-
assert_eq!(terminal_width(999), 3);
655-
}
656534
}

0 commit comments

Comments
 (0)