add code preview

This commit is contained in:
2026-08-11 10:56:39 +07:00
parent 91a76a6f52
commit aabccdf099
5 changed files with 629 additions and 57 deletions
Generated
+404 -3
View File
@@ -1018,9 +1018,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.4.1"
version = "1.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9066c49992464636f92905fa096ec58baaa4d57ec19a5c096c68d3e25ef3d136"
checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -3522,6 +3522,41 @@ dependencies = [
"smallvec",
"smol",
"tracing",
"tree-sitter",
"tree-sitter-astro-next",
"tree-sitter-bash",
"tree-sitter-c",
"tree-sitter-c-sharp",
"tree-sitter-cmake",
"tree-sitter-cpp",
"tree-sitter-css",
"tree-sitter-diff",
"tree-sitter-elixir",
"tree-sitter-embedded-template",
"tree-sitter-go",
"tree-sitter-graphql",
"tree-sitter-html",
"tree-sitter-java",
"tree-sitter-javascript",
"tree-sitter-jsdoc",
"tree-sitter-json",
"tree-sitter-kotlin-sg",
"tree-sitter-lua",
"tree-sitter-make",
"tree-sitter-md",
"tree-sitter-php",
"tree-sitter-proto",
"tree-sitter-python",
"tree-sitter-ruby",
"tree-sitter-rust",
"tree-sitter-scala",
"tree-sitter-sequel",
"tree-sitter-svelte-next",
"tree-sitter-swift",
"tree-sitter-toml-ng",
"tree-sitter-typescript",
"tree-sitter-yaml",
"tree-sitter-zig",
"unicode-segmentation",
"uuid",
"windows 0.58.0",
@@ -8035,6 +8070,12 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6"
[[package]]
name = "streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
[[package]]
name = "strict-num"
version = "0.1.1"
@@ -8361,7 +8402,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.4.3",
"getrandom 0.3.4",
"once_cell",
"rustix 1.1.4",
"windows-sys 0.61.2",
@@ -8823,6 +8864,366 @@ dependencies = [
"tracing-log",
]
[[package]]
name = "tree-sitter"
version = "0.26.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83c567a8e18ae93f20982c90370b16fd24023aeaf52f6052b96957ab253a0fec"
dependencies = [
"cc",
"regex",
"regex-syntax",
"serde_json",
"streaming-iterator",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-astro-next"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "794a4a59fc2d88e49b4bc41fef9522d77184a36f4e68bbaf545cd1eb2364c46e"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-bash"
version = "0.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "329a4d48623ac337d42b1df84e81a1c9dbb2946907c102ca72db158c1964a52e"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-c"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9b2eb57a55fed6b00812912e730b7a275cf4fe98bfd6a5d76263d4438371728"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-c-sharp"
version = "0.23.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1aac67f1ad71de1d6d39708d34811081c26dfa495658de6c14c34200849357c"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-cmake"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "164e0c4f4236ec5ceff14824a5528615cf462e100467e49826442ff57d327061"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-cpp"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df2196ea9d47b4ab4a31b9297eaa5a5d19a0b121dceb9f118f6790ad0ab94743"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-css"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad6489794d41350d12a7fbe520e5199f688618f43aace5443980d1ddcf1b29e"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-diff"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfe1e5ca280a65dfe5ba4205c1bcc84edf486464fed315db53dee6da9a335889"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-elixir"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66dd064a762ed95bfc29857fa3cb7403bb1e5cb88112de0f6341b7e47284ba40"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-embedded-template"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790063ef14e5b67556abc0b3be0ed863fb41d65ee791cf8c0b20eb42a1fa46af"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-go"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b13d476345220dbe600147dd444165c5791bf85ef53e28acbedd46112ee18431"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-graphql"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efedc4cac157161cc23a0adc4553a2cedc908e1cd754b6cd033a919bb81ce5d6"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-html"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "261b708e5d92061ede329babaaa427b819329a9d427a1d710abb0f67bbef63ee"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-java"
version = "0.23.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0aa6cbcdc8c679b214e616fd3300da67da0e492e066df01bcf5a5921a71e90d6"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-javascript"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf40bf599e0416c16c125c3cec10ee5ddc7d1bb8b0c60fa5c4de249ad34dc1b1"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-jsdoc"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3862dfcb1038fc5e7812d7df14190afdeb7e1415288fd5f51f58395f8cb0faf"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-json"
version = "0.24.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d727acca406c0020cffc6cf35516764f36c8e3dc4408e5ebe2cb35a947ec471"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-kotlin-sg"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06ec43ae3c12165d4ac08afe4e1f5fc6757ffe274fa7bd5af9007ef11ba4319"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-language"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782"
[[package]]
name = "tree-sitter-lua"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea992f4164d83f371ef1239ae178c4d4596c296c09055e9a48bb02a2760403af"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-make"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5998dc7cbcbdab19fae8aefef982bf2d6544513d8d2e69cc44aec4c63810104"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-md"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2efd398be546456c814598ee56c0f51769a77241511b4a58077815d120afa882"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-php"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c17c3ab69052c5eeaa7ff5cd972dd1bc25d1b97ee779fec391ad3b5df5592"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-proto"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e4360b434b5980fc397137ef29e1988619fef4159ac86fa7ac5777d459d3924"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-python"
version = "0.23.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d065aaa27f3aaceaf60c1f0e0ac09e1cb9eb8ed28e7bcdaa52129cffc7f4b04"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-ruby"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be0484ea4ef6bb9c575b4fdabde7e31340a8d2dbc7d52b321ac83da703249f95"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-rust"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439e577dbe07423ec2582ac62c7531120dbfccfa6e5f92406f93dd271a120e45"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-scala"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efde5e68b4736e9eac17bfa296c6f104a26bffab363b365eb898c40a63c15d2f"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-sequel"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d198ad3c319c02e43c21efa1ec796b837afcb96ffaef1a40c1978fbdcec7d17"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-svelte-next"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f88190d0743e897c3e148a7e241aba0a8844b8afe816943851426e3f7b9753"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-swift"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe36052155b9dd69ca82b3b8f1b4ccfb2d867125ac1a4db1dd7331829242668c"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-toml-ng"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9adc2c898ae49730e857d75be403da3f92bb81d8e37a2f918a08dd10de5ebb1"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-typescript"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-yaml"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53c223db85f05e34794f065454843b0668ebc15d240ada63e2b5939f43ce7c97"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-zig"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab11fc124851b0db4dd5e55983bbd9631192e93238389dcd44521715e5d53e28"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "triomphe"
version = "0.1.16"
+3 -1
View File
@@ -18,7 +18,9 @@ gpui_macos = { git = "https://github.com/zed-industries/zed" }
gpui_tokio = { git = "https://github.com/zed-industries/zed" }
reqwest_client = { git = "https://github.com/zed-industries/zed" }
gpui-component = { git = "https://github.com/longbridge/gpui-component" }
# `tree-sitter-languages` enables syntax highlighting for the TextView
# code preview (fenced code blocks are highlighted with tree-sitter).
gpui-component = { git = "https://github.com/longbridge/gpui-component", features = ["tree-sitter-languages"] }
nostr = { git = "https://github.com/rust-nostr/nostr", features = ["nip59", "nip49", "nip44", "os-rng"] }
nostr-lmdb = { git = "https://github.com/rust-nostr/nostr" }
@@ -1,9 +1,10 @@
//! File explorer of the repository detail view: the file tree column and the
//! content column (README / file preview), backed by a persistent
//! [`TextViewState`] for markdown documents.
//! content column (README / file preview), backed by persistent
//! [`TextViewState`]s for markdown documents and code files.
use gpui::prelude::*;
use gpui::{AnyElement, Context, Entity, SharedString, WeakEntity, div, px};
use gpui_component::clipboard::Clipboard;
use gpui_component::list::ListItem;
use gpui_component::spinner::Spinner;
use gpui_component::text::{TextView, TextViewState};
@@ -11,7 +12,7 @@ use gpui_component::tree::{TreeEntry, TreeState, tree};
use gpui_component::{ActiveTheme, Icon, IconName, Sizable, StyledExt, h_flex, v_flex};
use super::RepoDetailView;
use super::helpers::{is_markdown_path, placeholder, plain_lines};
use super::helpers::{code_language, fenced_code, is_markdown_path, placeholder};
/// Width of the file explorer column.
const TREE_WIDTH: f32 = 240.;
@@ -44,6 +45,28 @@ pub(super) struct MarkdownView {
pub(super) state: Entity<TextViewState>,
}
/// A code file loaded into a persistent [`TextViewState`], rendered as a
/// fenced code block so the markdown parser syntax-highlights it.
///
/// Same persistence rationale as [`MarkdownView`]: the state lives as long
/// as this view, so re-viewing the same file does not re-parse it, and
/// parsing happens on a background task.
pub(super) struct CodeView {
/// Source path, relative to the worktree root.
pub(super) path: SharedString,
pub(super) state: Entity<TextViewState>,
}
/// Spinner shown while a document is being loaded/parsed.
fn preview_spinner() -> AnyElement {
v_flex()
.size_full()
.items_center()
.justify_center()
.child(Spinner::new().small())
.into_any_element()
}
impl RepoDetailView {
/// One row of the file tree: icon + name, indented by depth.
fn render_tree_item(
@@ -150,14 +173,11 @@ impl RepoDetailView {
.into_any_element()
} else if let Some(path) = selected_file {
match self.files.get(path.as_ref()) {
Some(FileContent::Text(text)) => {
Some(FileContent::Text(_)) => {
if is_markdown_path(path.as_ref()) {
self.markdown_element(Some(path.as_ref()), cx)
} else {
v_flex()
.size_full()
.children(plain_lines(text, cx))
.into_any_element()
self.code_element(path.as_ref(), cx)
}
}
Some(FileContent::Binary) => placeholder("Binary file — preview not supported", cx),
@@ -197,15 +217,7 @@ impl RepoDetailView {
.child(pane_title),
),
)
.child(
div()
.id("repo-content-scroll")
.flex_1()
.min_h_0()
.p_4()
.overflow_y_scroll()
.child(body),
)
.child(div().id("repo-content").flex_1().min_h_0().child(body))
}
/// Load `text` into the persistent markdown TextView state.
@@ -228,26 +240,71 @@ impl RepoDetailView {
/// The persistent markdown TextView for `path` (`None` = README), or a
/// spinner while the document is being loaded/parsed.
fn markdown_element(&mut self, path: Option<&str>, _cx: &mut Context<Self>) -> AnyElement {
let spinner = || {
v_flex()
.size_full()
.items_center()
.justify_center()
.child(Spinner::new().small())
.into_any_element()
};
let Some(md) = &self.md else {
return spinner();
return preview_spinner();
};
let ready = match path {
Some(path) => md.path.as_deref() == Some(path),
None => md.path.is_none(),
};
if !ready {
return spinner();
return preview_spinner();
}
TextView::new(&md.state).selectable(true).into_any_element()
TextView::new(&md.state)
.selectable(true)
.scrollable(true)
.p_4()
.into_any_element()
}
/// Load `text` into the persistent code TextView state for `path`.
///
/// The code is wrapped in a markdown fence (see [`fenced_code`]) so the
/// TextView renders it as a syntax-highlighted code block. Like
/// [`set_markdown`], the state is created empty and fed via `push_str`
/// so parsing happens on a background task instead of blocking the main
/// thread.
pub(super) fn set_code(&mut self, path: SharedString, text: &str, cx: &mut Context<Self>) {
let source = fenced_code(text, code_language(path.as_ref()));
let state = cx.new(|cx| TextViewState::markdown("", cx));
state.update(cx, |state, cx| state.push_str(&source, cx));
self.code = Some(CodeView { path, state });
}
/// The persistent code TextView for `path`, or a spinner while the file
/// is being loaded/parsed.
fn code_element(&mut self, path: &str, _cx: &mut Context<Self>) -> AnyElement {
let Some(code) = &self.code else {
return preview_spinner();
};
if code.path.as_ref() != path {
return preview_spinner();
}
TextView::new(&code.state)
.selectable(true)
.scrollable(true)
.p_4()
.code_block_actions(|code_block, _window, cx| {
let lang = code_block.lang().unwrap_or_default();
h_flex()
.gap_2()
.items_center()
.when(!lang.is_empty(), |this| {
this.child(
div()
.text_xs()
.text_color(cx.theme().muted_foreground)
.child(lang.clone()),
)
})
.child(
Clipboard::new(format!("copy-code-{lang}"))
.value(code_block.code())
.tooltip("Copy code"),
)
})
.into_any_element()
}
}
+115 -15
View File
@@ -1,5 +1,5 @@
//! Pure helpers for the repository detail view: file-tree building, plain
//! text rendering and small element builders.
//! Pure helpers for the repository detail view: file-tree building, code
//! preview helpers and small element builders.
use std::path::{Path, PathBuf};
@@ -45,20 +45,85 @@ fn insert_path(items: &mut Vec<TreeItem>, parts: &[String], prefix: &str) {
}
}
/// Render text with one element per line, preserving blank lines.
pub(super) fn plain_lines(text: &str, cx: &App) -> Vec<AnyElement> {
text.lines()
.map(|line| {
// A space keeps empty lines from collapsing to zero height.
let text = if line.is_empty() { " " } else { line };
div()
.font_family(cx.theme().mono_font_family.clone())
.text_xs()
.text_color(cx.theme().foreground)
.child(text.to_string())
.into_any_element()
/// The markdown fence language for a file path, or `None` for plain text.
///
/// Names are chosen so `gpui_component`'s highlighter can resolve them
/// (`highlighter::Language::from_name` accepts short aliases such as `rs`
/// and `js`).
pub(super) fn code_language(path: &str) -> Option<&'static str> {
let name = Path::new(path)
.file_name()
.and_then(|name| name.to_str())
.unwrap_or_default();
// Some common files are recognized by name rather than extension.
match name {
"Makefile" | "makefile" => return Some("make"),
"CMakeLists.txt" => return Some("cmake"),
_ => {}
}
let ext = Path::new(path).extension()?.to_str()?.to_ascii_lowercase();
Some(match ext.as_str() {
"rs" => "rust",
"toml" => "toml",
"json" | "jsonc" => "json",
"py" => "python",
"js" | "mjs" | "cjs" => "javascript",
"ts" | "mts" | "cts" => "typescript",
"tsx" | "jsx" => "tsx",
"go" => "go",
"c" | "h" => "c",
"cc" | "cpp" | "cxx" | "hh" | "hpp" | "hxx" => "cpp",
"cs" => "csharp",
"java" => "java",
"kt" | "kts" | "ktm" => "kotlin",
"swift" => "swift",
"php" | "phtml" => "php",
"rb" => "ruby",
"sh" | "bash" | "zsh" => "bash",
"yml" | "yaml" => "yaml",
"css" | "scss" | "sass" => "css",
"html" | "htm" => "html",
"lua" => "lua",
"sql" => "sql",
"proto" | "protobuf" => "proto",
"cmake" => "cmake",
"zig" => "zig",
"ex" | "exs" => "elixir",
"graphql" | "gql" => "graphql",
"diff" | "patch" => "diff",
"svelte" => "svelte",
"astro" => "astro",
"scala" => "scala",
_ => return None,
})
.collect()
}
/// Wrap `code` in a fenced markdown code block tagged with `lang`, so the
/// markdown [`TextViewState`] renders it as a syntax-highlighted code block.
///
/// The fence is one backtick longer than the longest run of backticks in
/// `code`, so the content can never close the block early.
pub(super) fn fenced_code(code: &str, lang: Option<&str>) -> String {
// Split on non-backtick characters so the segments are runs of backticks.
let longest_run = code.split(|c| c != '`').map(str::len).max().unwrap_or(0);
let fence = "`".repeat((longest_run + 1).max(3));
let mut out =
String::with_capacity(code.len() + fence.len() * 2 + lang.map_or(1, |lang| lang.len() + 2));
out.push_str(&fence);
if let Some(lang) = lang {
out.push(' ');
out.push_str(lang);
}
out.push('\n');
out.push_str(code);
if !code.ends_with('\n') {
out.push('\n');
}
out.push_str(&fence);
out
}
/// Whether a file path has a markdown extension.
@@ -134,4 +199,39 @@ mod tests {
assert_eq!(items[0].children[0].id, "a/b");
assert_eq!(items[0].children[0].children[0].id, "a/b/c.txt");
}
#[test]
fn fenced_code_wraps_in_triple_backticks() {
let out = fenced_code("fn main() {}\n", Some("rust"));
assert_eq!(out, "``` rust\nfn main() {}\n```");
}
#[test]
fn fenced_code_uses_longer_fence_than_content() {
let code = "let x = \"```\";\n`code`";
let out = fenced_code(code, None);
// The longest run of backticks in `code` is 3, so the fence is 4.
assert!(out.starts_with("````\n"));
assert!(out.ends_with("````"));
assert!(out.contains(code));
}
#[test]
fn fenced_code_keeps_trailing_newline() {
assert_eq!(fenced_code("a\n", None), "```\na\n```");
assert_eq!(fenced_code("a", None), "```\na\n```");
}
#[test]
fn code_language_maps_extensions_and_names() {
assert_eq!(code_language("src/main.rs"), Some("rust"));
assert_eq!(code_language("Cargo.toml"), Some("toml"));
assert_eq!(code_language("app.js"), Some("javascript"));
assert_eq!(code_language("index.tsx"), Some("tsx"));
assert_eq!(code_language("Makefile"), Some("make"));
assert_eq!(code_language("CMakeLists.txt"), Some("cmake"));
assert_eq!(code_language("data.csv"), None);
assert_eq!(code_language("LICENSE"), None);
assert_eq!(code_language("README.md"), None);
}
}
+16 -4
View File
@@ -18,7 +18,7 @@ use signed_state::{GitStore, RepoStore};
mod browser;
mod helpers;
use browser::{FileContent, MAX_PREVIEW_BYTES, MarkdownView};
use browser::{CodeView, FileContent, MAX_PREVIEW_BYTES, MarkdownView};
use helpers::{build_tree_items, is_markdown_path};
/// Detail view of a repository: header, stats, a file explorer with README
@@ -34,6 +34,8 @@ pub struct RepoDetailView {
worktree: Option<PathBuf>,
/// Markdown document currently in the preview pane (README or a file).
md: Option<MarkdownView>,
/// Code file currently in the preview pane.
code: Option<CodeView>,
readme_name: Option<SharedString>,
/// Currently previewed file (relative path) and its contents.
selected_file: Option<SharedString>,
@@ -63,6 +65,7 @@ impl RepoDetailView {
tree_state,
worktree: None,
md: None,
code: None,
readme_name: None,
selected_file: None,
files: HashMap::new(),
@@ -150,9 +153,11 @@ impl RepoDetailView {
Component::ParentDir | Component::RootDir | Component::Prefix(_)
)
});
let Some(worktree) = self.worktree.clone() else {
return;
};
if unsafe_path {
return;
}
@@ -186,14 +191,20 @@ impl RepoDetailView {
this.loading_files.remove(&path);
match content {
Ok(kind) => {
if let FileContent::Text(text) = &kind
&& is_markdown_path(&path)
{
if let FileContent::Text(text) = &kind {
if is_markdown_path(&path) {
let same = this.md.as_ref().map(|md| md.path.as_deref())
== Some(Some(path.as_str()));
if !same {
this.set_markdown(Some(path.clone().into()), text, cx);
}
} else {
let same = this.code.as_ref().map(|code| code.path.as_str())
== Some(path.as_str());
if !same {
this.set_code(path.clone().into(), text, cx);
}
}
}
this.files.insert(path, kind);
}
@@ -280,6 +291,7 @@ impl Render for RepoDetailView {
.pt_2()
.pb_4()
.w_full()
.items_start()
.justify_between()
.border_b_1()
.border_color(cx.theme().border)