T17 Editor: CodeEditor with gutter, folds, minimap, rulers, guides, brackets, whitespace, cursors, multi-cursor, sticky scroll, inlay hints, lenses, decorations, ghost text, inline chat and diff (T17a); completions, signatures, hover, code actions, rename, references, outline, go to symbol, call and type hierarchy (T17b); find and replace, search panel and filters (T17c); problems, output, debug console, extensions, settings with JSON, keybindings, theme previews, welcome, recent projects, switcher, task runner (T17d); status items (T17e)

Shared: generic undo History, OverflowMenu icon and tooltip, StatusBarItem leading, six Lucide icons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-09-26 00:43:41 -04:00
1 parent c229a096ca
commit d05754ac19
59 files changed
+10820 -71

No files matched your search

+183
View File
@@ -0,0 +1,183 @@
use std::ops::Range;
use gpui::{App, HighlightStyle};
use super::buffer::Buffer;
use crate::forms::{Kind, code_highlights, lex};
/// Each line that opens a block, and the last line the block holds, read from indentation.
pub(crate) fn folds(buffer: &Buffer) -> Vec<(usize, usize)> {
let blank = |line: usize| buffer.line(line).trim().is_empty();
let mut out = Vec::new();
for line in 0..buffer.lines() {
if blank(line) {
continue;
}
let indent = buffer.indent(line);
let mut end = None;
for next in line + 1..buffer.lines() {
if blank(next) {
continue;
}
if buffer.indent(next) <= indent {
break;
}
end = Some(next);
}
if let Some(end) = end {
out.push((line, end));
}
}
out
}
/// A bracket in the code, how deep it sits, and where its partner is when it has one.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Bracket {
pub offset: usize,
pub depth: usize,
pub partner: Option<usize>,
}
fn opener(close: char) -> char {
match close {
')' => '(',
']' => '[',
_ => '{',
}
}
/// Every bracket outside strings and comments; a closer takes its opener's depth.
pub(crate) fn brackets(buffer: &Buffer) -> Vec<Bracket> {
let mut out: Vec<Bracket> = Vec::new();
let mut open: Vec<(usize, char)> = Vec::new();
for line in 0..buffer.lines() {
let start = buffer.line_range(line).start;
let text = buffer.line(line);
for (range, kind) in lex(text) {
if kind != Kind::Mark {
continue;
}
let Some(ch) = text[range.clone()].chars().next() else {
continue;
};
let offset = start + range.start;
match ch {
'(' | '[' | '{' => {
open.push((out.len(), ch));
out.push(Bracket {
offset,
depth: open.len() - 1,
partner: None,
});
}
')' | ']' | '}' => match open.last() {
Some((ix, wanted)) if *wanted == opener(ch) => {
let ix = *ix;
open.pop();
let partner = out[ix].offset;
out[ix].partner = Some(offset);
out.push(Bracket {
offset,
depth: open.len(),
partner: Some(partner),
});
}
_ => out.push(Bracket {
offset,
depth: open.len(),
partner: None,
}),
},
_ => {}
}
}
}
out
}
/// The pair beside a caret: the bracket just after it, or else the one just before.
pub(crate) fn matched(brackets: &[Bracket], caret: usize) -> Option<(usize, usize)> {
let at = |offset: usize| {
brackets
.binary_search_by_key(&offset, |bracket| bracket.offset)
.ok()
.map(|ix| brackets[ix])
};
let bracket = at(caret).or_else(|| caret.checked_sub(1).and_then(at))?;
Some((bracket.offset, bracket.partner?))
}
/// A line's syntax colors as highlight styles.
pub(crate) fn colors(text: &str, cx: &App) -> Vec<(Range<usize>, HighlightStyle)> {
code_highlights(text, cx)
.into_iter()
.map(|(range, highlight)| {
(
range,
HighlightStyle {
color: Some(highlight.color),
background_color: highlight.background,
..HighlightStyle::default()
},
)
})
.collect()
}
/// Where `needle` appears in the lines of `range`, as offsets, whole words only when it is a word.
pub(crate) fn occurrences(buffer: &Buffer, needle: &str, lines: Range<usize>) -> Vec<Range<usize>> {
if needle.is_empty() || needle.contains('\n') {
return Vec::new();
}
let word = needle.chars().all(|ch| ch.is_alphanumeric() || ch == '_');
let boundary = |ch: Option<char>| ch.is_none_or(|ch| !(ch.is_alphanumeric() || ch == '_'));
let mut out = Vec::new();
for line in lines.start..lines.end.min(buffer.lines()) {
let start = buffer.line_range(line).start;
let text = buffer.line(line);
for (at, _) in text.match_indices(needle) {
let end = at + needle.len();
let whole =
boundary(text[..at].chars().next_back()) && boundary(text[end..].chars().next());
if !word || whole {
out.push(start + at..start + end);
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blocks_fold_by_indentation_and_skip_blank_lines() {
let buffer = Buffer::new("fn a() {\n one();\n\n two();\n}\nfn b() {}");
assert_eq!(folds(&buffer), [(0, 3)]);
}
#[test]
fn brackets_pair_by_depth_outside_strings_and_comments() {
let buffer = Buffer::new("f(a[0], \"(\") // )\n{ }");
let found = brackets(&buffer);
let offsets: Vec<usize> = found.iter().map(|bracket| bracket.offset).collect();
assert_eq!(
offsets,
[1, 3, 5, 11, 18, 20],
"the quoted and the commented ones do not count"
);
assert_eq!(found[1].depth, 1);
assert_eq!(found[3].partner, Some(1));
assert_eq!(matched(&found, 12), Some((11, 1)), "just before the caret");
assert_eq!(matched(&found, 18), Some((18, 20)), "just after the caret");
}
#[test]
fn occurrences_of_a_word_skip_longer_words() {
let buffer = Buffer::new("let total = total_cost + total;");
let found = occurrences(&buffer, "total", 0..1);
assert_eq!(found, [4..9, 25..30]);
}
}