Skip to content

Commit 45d43bd

Browse files
authored
feat(search): add line numbers to semantic search results (#2085)
1 parent da2fd72 commit 45d43bd

5 files changed

Lines changed: 89 additions & 27 deletions

crates/forge_app/src/snapshots/forge_app__operation__tests__sem_search_with_results.snap

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,25 @@ expression: to_value(actual)
1111
<file_chunk
1212
file_path="src/retry.rs"
1313
lines="10-19"
14-
><![CDATA[fn retry_with_backoff(max_attempts: u32) {
15-
let mut delay = 100;
16-
for attempt in 0..max_attempts {
17-
if try_operation().is_ok() {
18-
return;
19-
}
20-
thread::sleep(Duration::from_millis(delay));
21-
delay *= 2;
22-
}
23-
}]]>
14+
><![CDATA[10:fn retry_with_backoff(max_attempts: u32) {
15+
11: let mut delay = 100;
16+
12: for attempt in 0..max_attempts {
17+
13: if try_operation().is_ok() {
18+
14: return;
19+
15: }
20+
16: thread::sleep(Duration::from_millis(delay));
21+
17: delay *= 2;
22+
18: }
23+
19:}]]>
2424
</file_chunk>
2525
<file_chunk
2626
file_path="src/http/client.rs"
2727
lines="45-50"
28-
><![CDATA[async fn request_with_retry(&self, url: &str) -> Result<Response> {
29-
const MAX_RETRIES: usize = 3;
30-
let mut backoff = ExponentialBackoff::default();
31-
// Implementation...
32-
}]]>
28+
><![CDATA[45:async fn request_with_retry(&self, url: &str) -> Result<Response> {
29+
46: const MAX_RETRIES: usize = 3;
30+
47: let mut backoff = ExponentialBackoff::default();
31+
48: // Implementation...
32+
49:}]]>
3333
</file_chunk>
3434
</query_result>
3535
</sem_search_results>

crates/forge_app/src/snapshots/forge_app__operation__tests__sem_search_with_usecase.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ expression: to_value(actual)
1111
<file_chunk
1212
file_path="src/auth.rs"
1313
lines="10-12"
14-
><![CDATA[fn authenticate_user(token: &str) -> Result<User> {
15-
verify_jwt(token)
16-
}]]>
14+
><![CDATA[10:fn authenticate_user(token: &str) -> Result<User> {
15+
11: verify_jwt(token)
16+
12:}]]>
1717
</file_chunk>
1818
</query_result>
1919
</sem_search_results>

crates/forge_domain/src/line_numbers.rs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,21 @@ pub trait LineNumbers {
1010

1111
impl<T: AsRef<str>> LineNumbers for T {
1212
fn numbered_from(&self, start: usize) -> String {
13-
self.as_ref()
14-
.lines()
13+
let text = self.as_ref();
14+
let lines: Vec<&str> = text.lines().collect();
15+
16+
if lines.is_empty() {
17+
return String::new();
18+
}
19+
20+
// Calculate the width needed for the largest line number
21+
let max_line_number = start + lines.len() - 1;
22+
let width = max_line_number.to_string().len();
23+
24+
lines
25+
.into_iter()
1526
.enumerate()
16-
.map(|(i, line)| format!("{}:{}", start + i, line))
27+
.map(|(i, line)| format!("{:>width$}:{}", start + i, line, width = width))
1728
.collect::<Vec<_>>()
1829
.join("\n")
1930
}
@@ -57,4 +68,51 @@ mod tests {
5768
let expected = "1:line1\n2:\n3:line3";
5869
assert_eq!(text.numbered(), expected);
5970
}
71+
72+
#[test]
73+
fn test_numbered_right_aligned_single_digit() {
74+
let text = "line1\nline2\nline3";
75+
let expected = "1:line1\n2:line2\n3:line3";
76+
assert_eq!(text.numbered(), expected);
77+
}
78+
79+
#[test]
80+
fn test_numbered_right_aligned_two_digits() {
81+
let text = "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk";
82+
let expected = " 1:a\n 2:b\n 3:c\n 4:d\n 5:e\n 6:f\n 7:g\n 8:h\n 9:i\n10:j\n11:k";
83+
assert_eq!(text.numbered(), expected);
84+
}
85+
86+
#[test]
87+
fn test_numbered_right_aligned_three_digits() {
88+
let mut lines = Vec::new();
89+
for i in 1..=100 {
90+
lines.push(format!("line{}", i));
91+
}
92+
let text = lines.join("\n");
93+
let actual = text.numbered();
94+
95+
// Check first line has 2 leading spaces (001 -> " 1")
96+
assert!(actual.starts_with(" 1:line1"));
97+
// Check line 10 has 1 leading space (010 -> " 10")
98+
assert!(actual.contains("\n 10:line10\n"));
99+
// Check line 100 has no leading spaces (100 -> "100")
100+
assert!(actual.contains("\n100:line100"));
101+
}
102+
103+
#[test]
104+
fn test_numbered_from_right_aligned() {
105+
let text = "alpha\nbeta\ngamma\ndelta";
106+
// Starting from 98, so max is 101 (3 digits)
107+
let expected = " 98:alpha\n 99:beta\n100:gamma\n101:delta";
108+
assert_eq!(text.numbered_from(98), expected);
109+
}
110+
111+
#[test]
112+
fn test_numbered_from_crosses_digit_boundary() {
113+
let text = "line8\nline9\nline10\nline11";
114+
// Starting from 8, max is 11 (2 digits)
115+
let expected = " 8:line8\n 9:line9\n10:line10\n11:line11";
116+
assert_eq!(text.numbered_from(8), expected);
117+
}
60118
}

crates/forge_domain/src/node.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use derive_setters::Setters;
33
use serde::{Deserialize, Serialize};
44
use uuid::Uuid;
55

6-
use crate::WorkspaceId;
6+
use crate::{LineNumbers, WorkspaceId};
77

88
/// Progress events emitted during codebase indexing
99
#[derive(Debug, Clone, PartialEq)]
@@ -497,14 +497,18 @@ impl NodeData {
497497

498498
match self {
499499
Self::FileChunk { file_path, content, start_line, end_line } => {
500+
let numbered_content = content.numbered_from(*start_line as usize);
500501
Element::new("file_chunk")
501502
.attr("file_path", file_path)
502503
.attr("lines", format!("{}-{}", start_line, end_line))
503-
.cdata(content)
504+
.cdata(numbered_content)
505+
}
506+
Self::File { file_path, content, .. } => {
507+
let numbered_content = content.numbered();
508+
Element::new("file")
509+
.attr("file_path", file_path)
510+
.cdata(numbered_content)
504511
}
505-
Self::File { file_path, content, .. } => Element::new("file")
506-
.attr("file_path", file_path)
507-
.cdata(content),
508512
Self::FileRef { file_path, .. } => {
509513
Element::new("file_ref").attr("file_path", file_path)
510514
}

crates/forge_domain/src/snapshots/forge_domain__node__tests__codebase_query_result_with_results.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ expression: actual
1010
<file_chunk
1111
file_path="src/auth.rs"
1212
lines="10-15"
13-
><![CDATA[fn authenticate() {}]]>
13+
><![CDATA[10:fn authenticate() {}]]>
1414
</file_chunk>
1515
</query_result>

0 commit comments

Comments
 (0)