clean up
This commit is contained in:
@@ -7,7 +7,6 @@ use signed_core::Announcement;
|
||||
use signed_state::ProfileStore;
|
||||
use signed_ui::{UserAvatar, middle_truncate};
|
||||
|
||||
/// Open the About dialog showing every field of the announcement event.
|
||||
pub(super) fn open_about_dialog(announcement: Announcement, window: &mut Window, cx: &mut App) {
|
||||
window.open_dialog(cx, move |dialog, _window, cx| {
|
||||
let announcement = announcement.clone();
|
||||
@@ -21,7 +20,6 @@ pub(super) fn open_about_dialog(announcement: Announcement, window: &mut Window,
|
||||
});
|
||||
}
|
||||
|
||||
/// The announcement's fields as labeled rows.
|
||||
fn announcement_rows(announcement: &Announcement, cx: &App) -> AnyElement {
|
||||
let mut rows: Vec<AnyElement> = Vec::new();
|
||||
|
||||
@@ -116,7 +114,6 @@ fn announcement_rows(announcement: &Announcement, cx: &App) -> AnyElement {
|
||||
v_flex().gap_3().w_full().children(rows).into_any_element()
|
||||
}
|
||||
|
||||
/// One info row with a small muted label above the value.
|
||||
fn row(label: &'static str, value: AnyElement, cx: &App) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_1()
|
||||
@@ -132,7 +129,6 @@ fn row(label: &'static str, value: AnyElement, cx: &App) -> AnyElement {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Plain text value, wrapping within the dialog.
|
||||
fn text<T>(value: T) -> AnyElement
|
||||
where
|
||||
T: Into<SharedString>,
|
||||
@@ -147,7 +143,6 @@ where
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// A mono-spaced value with a copy button, for hex identifiers.
|
||||
fn copy_value(id: &'static str, value: String, cx: &App) -> AnyElement {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
@@ -164,10 +159,6 @@ fn copy_value(id: &'static str, value: String, cx: &App) -> AnyElement {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// One row per maintainer with avatar and display name.
|
||||
/// The display name falls back to a shortened npub.
|
||||
///
|
||||
/// A copy button copies the full pubkey.
|
||||
fn maintainers(maintainers: &[PublicKey], cx: &App) -> AnyElement {
|
||||
let profile_store = ProfileStore::global(cx);
|
||||
v_flex()
|
||||
@@ -197,9 +188,6 @@ fn maintainers(maintainers: &[PublicKey], cx: &App) -> AnyElement {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// One row per item of a multi-value tag.
|
||||
///
|
||||
/// The value is truncated to a single line, with a copy button for the full value.
|
||||
fn list(id: &'static str, items: impl IntoIterator<Item = String>, cx: &App) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_2()
|
||||
|
||||
@@ -18,7 +18,6 @@ use crate::views::pull_requests::detail::PullRequestDetailView;
|
||||
use crate::views::repo::init_dialog;
|
||||
|
||||
impl RepoDetailView {
|
||||
/// Re-push the repository's refs to its announced grasp servers.
|
||||
pub(super) fn push_repository(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.error = None;
|
||||
cx.notify();
|
||||
@@ -28,7 +27,6 @@ impl RepoDetailView {
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Push the unpushed commits of the local checkout at `path`.
|
||||
pub(super) fn push_unpushed_checkout(
|
||||
&mut self,
|
||||
path: PathBuf,
|
||||
@@ -71,7 +69,6 @@ impl RepoDetailView {
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Open the issues list panel in the dock area.
|
||||
pub(super) fn open_issue_detail(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.store.read(cx).addr().is_none() {
|
||||
return;
|
||||
@@ -89,7 +86,6 @@ impl RepoDetailView {
|
||||
});
|
||||
}
|
||||
|
||||
/// Open the pull requests list panel in the dock area.
|
||||
pub(super) fn open_pull_request_detail(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.store.read(cx).addr().is_none() {
|
||||
return;
|
||||
@@ -124,7 +120,7 @@ impl RepoDetailView {
|
||||
open_repo_panel(&self.dock_area, &addr, None, window, &mut *cx);
|
||||
}
|
||||
|
||||
/// Open the dialog guiding the user through publishing the local repository to NIP-34.
|
||||
/// Open the dialog that publishes the local repository to NIP-34.
|
||||
pub(super) fn open_init_dialog(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(local_path) = self.store.read(cx).path.clone() else {
|
||||
return;
|
||||
@@ -176,17 +172,8 @@ pub(crate) enum RepoItem {
|
||||
Patch,
|
||||
}
|
||||
|
||||
/// Open the detail panel of `item` in `addr`'s repository, in the dock's center.
|
||||
///
|
||||
/// The repository store is built here, not taken from a `RepoDetailView`, so the
|
||||
/// item panel is the only panel docked.
|
||||
///
|
||||
/// `hint` is an announcement already in hand for `addr`, e.g. the inbox row the
|
||||
/// item was clicked from. The store resolves the repository from the local
|
||||
/// database on its own, so an entry point with only the address works too.
|
||||
///
|
||||
/// A patch opens nothing: patches are only consumed inside a pull request's
|
||||
/// detail panel, and have no panel of their own.
|
||||
pub(crate) fn open_repo_item(
|
||||
dock_area: &WeakEntity<DockArea>,
|
||||
addr: &RepoAddr,
|
||||
|
||||
@@ -67,9 +67,6 @@ impl RepoDetailView {
|
||||
})
|
||||
}
|
||||
|
||||
/// The ready-to-push banner of an owned repository.
|
||||
///
|
||||
/// A local checkout has unpushed commits, with a Push action and a dismiss control.
|
||||
pub(super) fn render_push_banner(&self, cx: &Context<Self>) -> Option<AnyElement> {
|
||||
let status = self.push_suggestion(cx)?;
|
||||
let key = (status.path.clone(), status.branch.clone());
|
||||
@@ -155,7 +152,6 @@ impl RepoDetailView {
|
||||
)
|
||||
}
|
||||
|
||||
/// Warning after a push that only some grasp servers accepted.
|
||||
pub(super) fn render_push_warning_banner(&self, cx: &Context<Self>) -> Option<AnyElement> {
|
||||
let store = self.store.read(cx);
|
||||
let warning = store.last_push_warning.clone()?;
|
||||
@@ -220,7 +216,6 @@ impl RepoDetailView {
|
||||
)
|
||||
}
|
||||
|
||||
/// The ready-to-contribute banner of the repository panel.
|
||||
pub(super) fn render_ready_banner(&self, cx: &Context<Self>) -> Option<AnyElement> {
|
||||
let status = self.ready_suggestion(cx)?;
|
||||
let key = (status.path.clone(), status.branch.clone());
|
||||
|
||||
@@ -15,38 +15,28 @@ use signed_ui::{placeholder, tree_row};
|
||||
use super::RepoDetailView;
|
||||
use crate::views::repo::helpers::{code_language, is_markdown_path};
|
||||
|
||||
/// Width of the file explorer column.
|
||||
const TREE_WIDTH: f32 = 240.;
|
||||
/// Files larger than this are not previewed.
|
||||
pub(super) const MAX_PREVIEW_BYTES: usize = 1024 * 1024;
|
||||
/// Preview cache caps, a file count and a text byte count.
|
||||
///
|
||||
/// The oldest previews are evicted beyond the caps.
|
||||
pub(super) const MAX_PREVIEWED_FILES: usize = 32;
|
||||
pub(super) const MAX_PREVIEW_CACHE_BYTES: usize = 8 * 1024 * 1024;
|
||||
|
||||
/// Preview state of a browsed file.
|
||||
pub(super) enum FileContent {
|
||||
/// Decodable text content.
|
||||
Text(String),
|
||||
/// Not valid UTF-8.
|
||||
Binary,
|
||||
/// Bigger than [`MAX_PREVIEW_BYTES`].
|
||||
TooLarge,
|
||||
/// Reading failed.
|
||||
Failed(String),
|
||||
}
|
||||
|
||||
/// A markdown document loaded into a persistent [`TextViewState`].
|
||||
pub(super) struct MarkdownView {
|
||||
/// Source path, `None` means the repository README.
|
||||
/// `None` means the repository README.
|
||||
pub(super) path: Option<SharedString>,
|
||||
pub(super) state: Entity<TextViewState>,
|
||||
/// Hash of the source, so the same document is not re-parsed on a refresh.
|
||||
source_hash: u64,
|
||||
}
|
||||
|
||||
/// A code file loaded into a persistent [`InputState`].
|
||||
pub(super) struct CodeView {
|
||||
/// Source path, relative to the worktree root.
|
||||
pub(super) path: SharedString,
|
||||
@@ -55,8 +45,6 @@ pub(super) struct CodeView {
|
||||
source_hash: u64,
|
||||
}
|
||||
|
||||
/// Hash of a preview's source text.
|
||||
///
|
||||
/// Two loads of the same document produce the same hash, so the persistent
|
||||
/// markdown/editor state can be kept instead of rebuilt, which would re-parse
|
||||
/// and flash the pane.
|
||||
@@ -68,7 +56,6 @@ fn source_hash(text: &str) -> u64 {
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
/// Spinner shown while a document is being loaded/parsed.
|
||||
fn preview_spinner() -> AnyElement {
|
||||
v_flex()
|
||||
.size_full()
|
||||
@@ -79,7 +66,6 @@ fn preview_spinner() -> AnyElement {
|
||||
}
|
||||
|
||||
impl RepoDetailView {
|
||||
/// One row of the file tree with icon and name, indented by depth.
|
||||
fn render_tree_item(
|
||||
ix: usize,
|
||||
entry: &TreeEntry,
|
||||
@@ -96,7 +82,6 @@ impl RepoDetailView {
|
||||
})
|
||||
}
|
||||
|
||||
/// Left column showing the file tree.
|
||||
pub(super) fn render_tree_column(
|
||||
tree_state: Entity<TreeState>,
|
||||
view: WeakEntity<Self>,
|
||||
@@ -117,7 +102,6 @@ impl RepoDetailView {
|
||||
)))
|
||||
}
|
||||
|
||||
/// Right column, README, selected file preview or status text.
|
||||
pub(super) fn render_content_column(
|
||||
&self,
|
||||
pane_title: SharedString,
|
||||
@@ -230,7 +214,6 @@ impl RepoDetailView {
|
||||
.child(div().id("repo-content").flex_1().min_h_0().child(body))
|
||||
}
|
||||
|
||||
/// Load `text` into the persistent markdown TextView state.
|
||||
pub(super) fn set_markdown(
|
||||
&mut self,
|
||||
path: Option<SharedString>,
|
||||
@@ -254,9 +237,6 @@ impl RepoDetailView {
|
||||
});
|
||||
}
|
||||
|
||||
/// The persistent markdown TextView for `path`, where `None` is the README.
|
||||
///
|
||||
/// Shows a spinner while the document is being loaded or parsed.
|
||||
fn markdown_element(&self, path: Option<&str>, _cx: &mut Context<Self>) -> AnyElement {
|
||||
let Some(md) = &self.md else {
|
||||
return preview_spinner();
|
||||
@@ -279,9 +259,6 @@ impl RepoDetailView {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Load `text` into the persistent code editor state for `path`.
|
||||
///
|
||||
/// Code editor mode makes the Input render it read-only and highlighted.
|
||||
pub(super) fn set_code(
|
||||
&mut self,
|
||||
path: SharedString,
|
||||
@@ -312,7 +289,6 @@ impl RepoDetailView {
|
||||
});
|
||||
}
|
||||
|
||||
/// The persistent code editor for `path`, or a spinner while the file loads or parses.
|
||||
fn code_element(&self, path: &str, _cx: &mut Context<Self>) -> AnyElement {
|
||||
let Some(code) = &self.code else {
|
||||
return preview_spinner();
|
||||
@@ -332,7 +308,6 @@ impl RepoDetailView {
|
||||
}
|
||||
|
||||
impl RepoDetailView {
|
||||
/// Preview the file at `path`, relative to the worktree root.
|
||||
fn open_file(&mut self, path: &str, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.selected_file = Some(path.into());
|
||||
|
||||
@@ -455,7 +430,6 @@ impl RepoDetailView {
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Drop the cached preview, editor and commit state of `path`.
|
||||
pub(super) fn drop_preview_of(&mut self, path: &str) {
|
||||
if let Some(FileContent::Text(text)) = self.files.remove(path) {
|
||||
self.preview_bytes -= text.len();
|
||||
|
||||
@@ -24,8 +24,6 @@ use crate::views::repo::helpers::{ShareTargets, ref_selector_trigger};
|
||||
use crate::views::send_patch::open_send_patch_panel;
|
||||
|
||||
impl RepoDetailView {
|
||||
/// The NIP-34 header, actions and issues/PR counts.
|
||||
/// Or the local header with an Init button for an unpublished repository.
|
||||
pub(super) fn render_header(&mut self, cx: &mut Context<Self>) -> AnyElement {
|
||||
if self.store.read(cx).addr().is_none() {
|
||||
return self.render_local_header(cx);
|
||||
@@ -411,8 +409,6 @@ impl RepoDetailView {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Header for a local, not yet published, repository.
|
||||
/// The directory name and path with an Init button instead of the NIP-34 actions.
|
||||
fn render_local_header(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
let name = self.display_name(cx);
|
||||
let path = self
|
||||
@@ -476,8 +472,6 @@ impl RepoDetailView {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// The tab row shared by both header variants.
|
||||
/// Files and Commits tabs, the HEAD commit button and the branch/tag selectors.
|
||||
fn render_header_tabs(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
let commits_count = self.all_commits.as_ref().map(|list| list.total);
|
||||
let worktree_empty = self.switching_ref || self.worktree.is_none();
|
||||
@@ -658,7 +652,6 @@ impl RepoDetailView {
|
||||
}
|
||||
}
|
||||
|
||||
/// The `nostr://...` clone URL of an announcement, NIP-34.
|
||||
fn nostr_clone_url(announcement: &Announcement, nip05: Option<&str>) -> SharedString {
|
||||
let owner = announcement.owner;
|
||||
let user = nip05
|
||||
@@ -676,10 +669,6 @@ fn nostr_clone_url(announcement: &Announcement, nip05: Option<&str>) -> SharedSt
|
||||
SharedString::from(url)
|
||||
}
|
||||
|
||||
/// The forked-from row of the detail header.
|
||||
///
|
||||
/// Clickable link to the upstream repository when the `u` tag references a NIP-34 repo.
|
||||
/// Plain text when it only carries a git URL.
|
||||
fn fork_row(announcement: &Announcement, cx: &mut Context<RepoDetailView>) -> Option<AnyElement> {
|
||||
let upstream = announcement.upstream.as_ref()?;
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ use utils::{relative_time, relative_time_secs};
|
||||
pub(crate) struct TreeItemSeed {
|
||||
/// Path of the node, relative to the worktree root.
|
||||
id: String,
|
||||
/// File or directory name.
|
||||
label: String,
|
||||
children: Vec<TreeItemSeed>,
|
||||
}
|
||||
@@ -200,9 +199,6 @@ impl ShareTargets {
|
||||
}
|
||||
}
|
||||
|
||||
/// The share dropdown menu, one row per target.
|
||||
///
|
||||
/// Each shows a compact label, the copy button and row click copy the full value.
|
||||
pub(crate) fn menu(&self, menu: PopupMenu) -> PopupMenu {
|
||||
menu.min_w(px(340.))
|
||||
.item(menu_copy_row(
|
||||
@@ -232,7 +228,6 @@ impl ShareTargets {
|
||||
}
|
||||
}
|
||||
|
||||
/// Shorten an naddr link to `<url>/naddr1...[last tail chars]`.
|
||||
fn truncate_naddr_link(url: &str, tail: usize) -> String {
|
||||
let Some(end) = url.find("naddr1").map(|i| i + "naddr1".len()) else {
|
||||
return url.to_string();
|
||||
@@ -243,14 +238,9 @@ fn truncate_naddr_link(url: &str, tail: usize) -> String {
|
||||
format!("{}...{}", &url[..end], &url[url.len() - tail..])
|
||||
}
|
||||
|
||||
/// Width of one line-number gutter in a diff row.
|
||||
pub(crate) const GUTTER_WIDTH: f32 = 44.;
|
||||
/// Height of one row in a virtual diff list.
|
||||
pub(crate) const DIFF_ROW_HEIGHT: f32 = 20.;
|
||||
|
||||
/// One row of a virtual diff list, a hunk header or a line of a hunk.
|
||||
///
|
||||
/// Shared by the commit diff and pull request diff viewers.
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum DiffRow {
|
||||
Hunk {
|
||||
@@ -259,11 +249,12 @@ pub(crate) enum DiffRow {
|
||||
new_start: u32,
|
||||
new_lines: u32,
|
||||
},
|
||||
/// Line `line` of hunk `hunk` of the selected file's diff.
|
||||
Line { hunk: usize, line: usize },
|
||||
Line {
|
||||
hunk: usize,
|
||||
line: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// The rows of `file`'s diff, one header row per hunk then its lines.
|
||||
pub(crate) fn diff_rows(file: &FileDiff) -> Vec<DiffRow> {
|
||||
let mut rows = Vec::new();
|
||||
for (hunk_ix, hunk) in file.hunks.iter().enumerate() {
|
||||
@@ -281,7 +272,6 @@ pub(crate) fn diff_rows(file: &FileDiff) -> Vec<DiffRow> {
|
||||
rows
|
||||
}
|
||||
|
||||
/// One row of the virtual diff list, a hunk header or a single line.
|
||||
pub(crate) fn render_diff_row(hunks: &[DiffHunk], row: DiffRow, cx: &App) -> AnyElement {
|
||||
match row {
|
||||
DiffRow::Hunk {
|
||||
@@ -308,9 +298,6 @@ pub(crate) fn render_diff_row(hunks: &[DiffHunk], row: DiffRow, cx: &App) -> Any
|
||||
}
|
||||
}
|
||||
|
||||
/// One diff line, old and new line numbers in the gutters.
|
||||
///
|
||||
/// The content is tinted by kind, addition, deletion or context.
|
||||
pub(crate) fn render_diff_line(line: &DiffLine, cx: &App) -> AnyElement {
|
||||
let bg = match line.kind {
|
||||
DiffLineKind::Addition => Some(cx.theme().success.opacity(0.2)),
|
||||
@@ -358,7 +345,6 @@ pub(crate) fn render_diff_line(line: &DiffLine, cx: &App) -> AnyElement {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Find a tree item by id, searching into nested children.
|
||||
pub(crate) fn find_item<'a>(items: &'a [TreeItem], id: Option<&str>) -> Option<&'a TreeItem> {
|
||||
let id = id?;
|
||||
items.iter().find_map(|item| {
|
||||
@@ -370,21 +356,16 @@ pub(crate) fn find_item<'a>(items: &'a [TreeItem], id: Option<&str>) -> Option<&
|
||||
})
|
||||
}
|
||||
|
||||
/// The root issue events of a repo store, for the shared detail sections.
|
||||
pub(crate) fn issue_roots(store: &RepoStore) -> &[Event] {
|
||||
&store.issues
|
||||
}
|
||||
|
||||
/// The root pull request events of a repo store, for the shared detail sections.
|
||||
pub(crate) fn pr_roots(store: &RepoStore) -> &[Event] {
|
||||
&store.pull_requests
|
||||
}
|
||||
|
||||
/// The trigger body of the branch/tag selectors.
|
||||
///
|
||||
/// The kind icon, the selection or placeholder, and the caret.
|
||||
/// `Combobox` replaces its default trigger entirely,
|
||||
/// the only way to show an icon inside it.
|
||||
/// The kind icon, the selection or placeholder, and the caret. `Combobox`
|
||||
/// replaces its default trigger entirely, the only way to show an icon inside it.
|
||||
pub(crate) fn ref_selector_trigger(
|
||||
ctx: &ComboboxTriggerContext<SearchableVec<SharedString>>,
|
||||
icon: CustomIconName,
|
||||
@@ -418,7 +399,6 @@ pub(crate) fn ref_selector_trigger(
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Section heading of a detail sidebar, shared by the issue and PR panels.
|
||||
pub(crate) fn sidebar_title(text: &str, cx: &App) -> AnyElement {
|
||||
div()
|
||||
.text_xs()
|
||||
@@ -428,7 +408,6 @@ pub(crate) fn sidebar_title(text: &str, cx: &App) -> AnyElement {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Right sidebar with participants and labels of a root event, issue or PR.
|
||||
pub(crate) fn sidebar_section(
|
||||
store: &Entity<RepoStore>,
|
||||
id: EventId,
|
||||
@@ -511,7 +490,6 @@ pub(crate) fn sidebar_section(
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// The comments on a root event, issue or PR, one card per comment.
|
||||
pub(crate) fn comments_section(store: &Entity<RepoStore>, root: EventId, cx: &App) -> AnyElement {
|
||||
let store = store.read(cx);
|
||||
let comments: Vec<&Event> = store.comments_of(&root).collect();
|
||||
@@ -559,8 +537,6 @@ pub(crate) fn comments_section(store: &Entity<RepoStore>, root: EventId, cx: &Ap
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// The comment form posting to an issue or PR root event.
|
||||
///
|
||||
/// `roots` selects the root's list within the store, issues or pull requests.
|
||||
pub(crate) fn comment_form(
|
||||
store: &Entity<RepoStore>,
|
||||
@@ -621,11 +597,8 @@ pub(crate) fn comment_form(
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Height of one commit row in a commit virtual list.
|
||||
pub(crate) const COMMIT_ROW_HEIGHT: f32 = 56.;
|
||||
|
||||
/// One commit row of a virtual list, shared by the commits tab and the
|
||||
/// new-pull-request commit picker.
|
||||
pub(crate) fn commit_row(
|
||||
ix: usize,
|
||||
commit: &FileCommit,
|
||||
@@ -745,45 +718,4 @@ mod tests {
|
||||
assert_eq!(items[0].children.len(), 2);
|
||||
assert_eq!(items[1].label, "b");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_seeds_convert_to_tree_items() {
|
||||
let entries = vec![
|
||||
PathBuf::from("src"),
|
||||
PathBuf::from("src/main.rs"),
|
||||
PathBuf::from("README.md"),
|
||||
];
|
||||
|
||||
let items: Vec<TreeItem> = tree_items(build_tree_items(&entries), false);
|
||||
assert_eq!(items.len(), 2);
|
||||
assert_eq!(items[0].label, "src");
|
||||
assert_eq!(items[0].children.len(), 1);
|
||||
assert_eq!(items[0].children[0].label, "main.rs");
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn naddr_link_keeps_url_and_tail() {
|
||||
assert_eq!(
|
||||
truncate_naddr_link("https://gitworkshop.dev/naddr1qqqxyzabc1234", 4),
|
||||
"https://gitworkshop.dev/naddr1...1234"
|
||||
);
|
||||
// Without the naddr1 prefix, unchanged.
|
||||
assert_eq!(
|
||||
truncate_naddr_link("https://example.com/x", 4),
|
||||
"https://example.com/x"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,8 +105,6 @@ impl RepoDetailView {
|
||||
}
|
||||
|
||||
impl RepoDetailView {
|
||||
/// Queue `path` for the per-file commit query.
|
||||
/// Requests are batched into one history walk, see [`Self::load_commits`].
|
||||
pub(super) fn load_commit(&mut self, path: &str, cx: &mut Context<Self>) {
|
||||
if self.commits.contains_key(path) || self.pending_commits.iter().any(|p| p == path) {
|
||||
return;
|
||||
@@ -117,10 +115,6 @@ impl RepoDetailView {
|
||||
}
|
||||
}
|
||||
|
||||
/// Walk history once for every queued path on a background task.
|
||||
/// Cache the latest commit touching each path in [`Self::commits`].
|
||||
/// That feeds the file header in the content column.
|
||||
/// Batching shares one walk across paths queued while the previous walk ran.
|
||||
fn load_commits(&mut self, cx: &mut Context<Self>) {
|
||||
if self.pending_commits.is_empty() || self.loading_commits {
|
||||
return;
|
||||
@@ -167,9 +161,6 @@ impl RepoDetailView {
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Walk all commits reachable from HEAD on a background task.
|
||||
/// For the Commits tab and its total-count badge.
|
||||
/// [`CommitList`] caps the list, only the newest commits are materialized.
|
||||
pub(super) fn load_all_commits(&mut self, cx: &mut Context<Self>) {
|
||||
if self.loading_all_commits || self.all_commits.is_some() {
|
||||
return;
|
||||
@@ -209,7 +200,6 @@ impl RepoDetailView {
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Open a new panel showing the diff of `commit_id`.
|
||||
pub(super) fn open_commit_diff(
|
||||
&mut self,
|
||||
commit_id: &str,
|
||||
|
||||
@@ -19,10 +19,8 @@ use crate::views::sidebar::grasp_servers::{
|
||||
GraspServersState, grasp_servers_field, load_user_grasp_servers,
|
||||
};
|
||||
|
||||
/// Shared state for the Init dialog, so async results can be rendered.
|
||||
pub type InitRepoState = DialogProgress;
|
||||
|
||||
/// Open the Init dialog for the local repository at `local_path`.
|
||||
pub fn open(
|
||||
local_path: PathBuf,
|
||||
view: WeakEntity<RepoDetailView>,
|
||||
@@ -51,7 +49,6 @@ pub fn open(
|
||||
.placeholder("Short description")
|
||||
});
|
||||
|
||||
// Load the user's grasp servers.
|
||||
load_user_grasp_servers(grasp_state.clone(), window, cx);
|
||||
|
||||
window.open_dialog(cx, move |dialog, _window, _cx| {
|
||||
@@ -146,9 +143,6 @@ pub fn open(
|
||||
});
|
||||
}
|
||||
|
||||
/// Run the init flow.
|
||||
///
|
||||
/// Closes the dialog and switches the repository into NIP-34 mode on success.
|
||||
fn init_repository(
|
||||
local_path: PathBuf,
|
||||
inputs: (Entity<InputState>, Entity<TextareaState>),
|
||||
|
||||
@@ -15,7 +15,6 @@ use crate::views::repo::helpers::{
|
||||
TreeItemSeed, build_tree_items, sorted_worktree_paths, tree_items,
|
||||
};
|
||||
|
||||
/// Everything loaded from the local clone for the explorer.
|
||||
struct RepoData {
|
||||
tree: Vec<TreeItemSeed>,
|
||||
/// Relative paths of the worktree entries, for [`RepoDetailView::worktree_paths`].
|
||||
@@ -32,8 +31,8 @@ struct RepoData {
|
||||
impl RepoDetailView {
|
||||
/// Load the repository and populate the file explorer.
|
||||
///
|
||||
/// A local, not yet published, repository opens straight from disk.
|
||||
/// An announced repository's clone, if any, loads first without touching the network.
|
||||
/// An announced repository's clone, if any, loads first without touching
|
||||
/// the network.
|
||||
pub(super) fn load_repo(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.loading = true;
|
||||
self.error = None;
|
||||
@@ -110,7 +109,6 @@ impl RepoDetailView {
|
||||
let disk = disk.await;
|
||||
let had_clone = matches!(&disk, Ok(Some(_)));
|
||||
|
||||
// No local clone yet, so clone from the network then load.
|
||||
let data = match disk {
|
||||
Ok(Some(data)) => Ok(data),
|
||||
Ok(None) => {
|
||||
@@ -247,7 +245,6 @@ impl RepoDetailView {
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Apply the loaded repository data.
|
||||
fn apply_repo_data(&mut self, data: RepoData, window: &mut Window, cx: &mut Context<Self>) {
|
||||
log::debug!("repo detail: apply_repo_data");
|
||||
let RepoData {
|
||||
@@ -273,8 +270,6 @@ impl RepoDetailView {
|
||||
state.set_items(tree_items(tree, false), cx);
|
||||
});
|
||||
|
||||
// Populate the branch/tag selectors with the local refs.
|
||||
// Select the branch HEAD points to.
|
||||
let branches: Vec<SharedString> = branches.into_iter().map(Into::into).collect();
|
||||
let tags: Vec<SharedString> = tags.into_iter().map(Into::into).collect();
|
||||
|
||||
@@ -338,7 +333,6 @@ impl RepoDetailView {
|
||||
true
|
||||
}
|
||||
|
||||
/// Clone the repository into a user-chosen folder outside the cache.
|
||||
pub(super) fn clone_to_folder(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let store = self.store.clone();
|
||||
|
||||
@@ -404,9 +398,6 @@ impl RepoDetailView {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the worktree state of `repo`, no network.
|
||||
///
|
||||
/// Entries, README, refs and HEAD commit.
|
||||
fn load_repo_data(repo: &Repository) -> Result<RepoData, Error> {
|
||||
let entries = signed_git::worktree_entries(repo)?;
|
||||
let tree = build_tree_items(&entries);
|
||||
|
||||
@@ -33,121 +33,84 @@ pub(crate) use actions::{RepoItem, open_repo_item, open_repo_panel};
|
||||
|
||||
use self::files::{CodeView, FileContent, MarkdownView};
|
||||
|
||||
/// What kind of ref the header selectors switch to.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum RefKind {
|
||||
/// A local branch `refs/heads/*`, HEAD stays attached.
|
||||
/// HEAD stays attached.
|
||||
Branch,
|
||||
/// A tag `refs/tags/*`, HEAD becomes detached.
|
||||
/// HEAD becomes detached.
|
||||
Tag,
|
||||
}
|
||||
|
||||
/// Header actions dispatched by the dropdown menus of the header buttons.
|
||||
/// `pub(crate)` because the pull-request list panel shares this action set.
|
||||
/// It offers the New-PR and Send-patch actions in its own dropdown.
|
||||
/// Header actions dispatched by the header dropdown menus.
|
||||
///
|
||||
/// `pub(crate)` because the pull-request list panel shares this action set,
|
||||
/// offering the New-PR and Send-patch actions in its own dropdown.
|
||||
#[derive(Clone, Action, PartialEq, Eq)]
|
||||
#[action(namespace = repo, no_json)]
|
||||
pub(crate) enum RepoAction {
|
||||
/// Open the new issue dialog.
|
||||
NewIssue,
|
||||
/// Open the new pull request dialog.
|
||||
NewPR,
|
||||
/// Open the send patch panel.
|
||||
SendPatch,
|
||||
/// Open the about dialog.
|
||||
About,
|
||||
/// Re-push the repository to its grasp servers.
|
||||
Push,
|
||||
/// Delete the repository from nostr, owner only.
|
||||
Delete,
|
||||
}
|
||||
|
||||
/// Detail view of a repository, header, stats and metadata.
|
||||
///
|
||||
/// A file explorer with README preview, cloned from the announcement's `clone` URLs.
|
||||
pub struct RepoDetailView {
|
||||
focus_handle: FocusHandle,
|
||||
/// Dock area the detail view lives in.
|
||||
///
|
||||
/// New panels, commit diffs, are added there.
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
/// Per-repository store, holding the local path and the announcement,
|
||||
/// issues, PRs and statuses. The single identity of both modes.
|
||||
store: Entity<RepoStore>,
|
||||
/// The initial explorer load has been started.
|
||||
///
|
||||
/// A repository opened by address alone starts without an announcement; the
|
||||
/// store observer starts the load once the first one lands.
|
||||
repo_started: bool,
|
||||
/// File explorer state, the worktree of the local clone.
|
||||
tree_state: Entity<TreeState>,
|
||||
/// Root of the local clone, for reading files on demand.
|
||||
worktree: Option<PathBuf>,
|
||||
/// Sorted relative paths of the tree currently shown.
|
||||
///
|
||||
/// A background refresh that did not change the tree skips rebuilding it,
|
||||
/// see [`Self::catch_up_worktree`], so a fetch that learned nothing new
|
||||
/// does not flash the explorer.
|
||||
worktree_paths: Vec<String>,
|
||||
/// 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, a relative path, and its contents.
|
||||
selected_file: Option<SharedString>,
|
||||
files: HashMap<String, FileContent>,
|
||||
/// Paths of cached previews, oldest first.
|
||||
/// Feeds the eviction caps in [`Self::evict_previews`].
|
||||
/// Paths of cached previews, oldest first. Feeds the eviction caps in
|
||||
/// [`Self::evict_previews`].
|
||||
file_order: VecDeque<String>,
|
||||
/// Total text bytes held by [`Self::files`].
|
||||
preview_bytes: usize,
|
||||
/// Reads in flight, to avoid duplicate loads.
|
||||
loading_files: HashSet<String>,
|
||||
/// Latest commit touching a previewed file or the README, keyed by path.
|
||||
commits: HashMap<String, FileCommit>,
|
||||
/// Paths queued for the next batched commit query, see [`Self::load_commits`].
|
||||
pending_commits: Vec<String>,
|
||||
/// A batched commit query is in flight.
|
||||
loading_commits: bool,
|
||||
/// Active header tab, 0 = Files tree, 1 = Commits.
|
||||
/// 0 = Files tree, 1 = Commits.
|
||||
active_tab: usize,
|
||||
/// Commits reachable from HEAD, newest first.
|
||||
/// `None` until the walk finishes or fails.
|
||||
/// [`CommitList`] caps the list, `total` feeds the tab badge.
|
||||
/// Commits reachable from HEAD, newest first. `None` until the walk
|
||||
/// finishes or fails. `total` feeds the tab badge.
|
||||
all_commits: Option<CommitList>,
|
||||
/// Commit walk in flight.
|
||||
loading_all_commits: bool,
|
||||
/// Virtual list state of the Commits tab.
|
||||
scroll_handle: VirtualListScrollHandle,
|
||||
item_sizes: Rc<Vec<Size<Pixels>>>,
|
||||
/// A clone/fetch is in flight.
|
||||
loading: bool,
|
||||
error: Option<SharedString>,
|
||||
/// Commit HEAD currently points to, shown in the header button.
|
||||
head_commit: Option<FileCommit>,
|
||||
/// Branch selector in the header, local branches, searchable.
|
||||
branch_select: Entity<ComboboxState<SearchableVec<SharedString>>>,
|
||||
/// Tag selector in the header, tags, searchable.
|
||||
tag_select: Entity<ComboboxState<SearchableVec<SharedString>>>,
|
||||
/// Branch names currently in `branch_select`, for cheap no-op detection.
|
||||
ref_branches: Vec<SharedString>,
|
||||
/// Tag names currently in `tag_select`, for cheap no-op detection.
|
||||
ref_tags: Vec<SharedString>,
|
||||
/// A branch/tag switch is in flight, checkout plus explorer reload.
|
||||
switching_ref: bool,
|
||||
/// Bumped on every branch/tag switch.
|
||||
/// In-flight loads with an older generation are discarded when they complete.
|
||||
ref_generation: u64,
|
||||
/// Subscriptions keeping the selectors' confirm events alive.
|
||||
_subscriptions: Vec<Subscription>,
|
||||
/// `(path, branch)` ready-suggestions dismissed by the user, per panel.
|
||||
banner_dismissed: HashSet<(PathBuf, String)>,
|
||||
/// The announced HEAD the ready statuses were last requested with.
|
||||
/// Whether they were requested at all.
|
||||
/// Re-requested only when the HEAD, the base default, changes.
|
||||
/// e.g. when the store's first refresh lands.
|
||||
/// Whether the ready statuses were requested at all.
|
||||
ready_requested: bool,
|
||||
/// The announced HEAD they were last requested with. Re-requested only when
|
||||
/// the HEAD, the base default, changes, e.g. when the store's first refresh
|
||||
/// lands.
|
||||
ready_head: Option<String>,
|
||||
/// The global checkouts store's ready-to-contribute statuses of this
|
||||
/// repository, last seen when they drove a render.
|
||||
@@ -155,14 +118,10 @@ pub struct RepoDetailView {
|
||||
/// The store notifies on any recompute pass; the observer re-renders this
|
||||
/// panel only when these slices changed.
|
||||
ready_statuses: Vec<CheckoutStatus>,
|
||||
/// The global checkouts store's ready-to-push statuses of this repository,
|
||||
/// last seen when they drove a render.
|
||||
push_statuses: Vec<CheckoutStatus>,
|
||||
}
|
||||
|
||||
impl RepoDetailView {
|
||||
/// Open a repository by address.
|
||||
///
|
||||
/// `hint` is an announcement already in hand. It seeds the store's relays
|
||||
/// and the explorer's clone URLs; without it the panel waits for the store
|
||||
/// to load the announcement from the local database.
|
||||
@@ -177,7 +136,6 @@ impl RepoDetailView {
|
||||
Self::new_common(dock_area, store, window, cx)
|
||||
}
|
||||
|
||||
/// Open a local repository discovered by the scan.
|
||||
pub fn new_local(
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
local_path: PathBuf,
|
||||
@@ -188,9 +146,6 @@ impl RepoDetailView {
|
||||
Self::new_common(dock_area, store, window, cx)
|
||||
}
|
||||
|
||||
/// Shared construction.
|
||||
///
|
||||
/// File explorer state, ref selectors and the deferred repository load.
|
||||
fn new_common(
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
store: Entity<RepoStore>,
|
||||
@@ -306,8 +261,8 @@ impl RepoDetailView {
|
||||
self.store.read(cx).announcement.as_ref()
|
||||
}
|
||||
|
||||
/// Display name, the announcement's name or ID for announced repositories.
|
||||
/// The directory name for local ones.
|
||||
/// The announcement's name or ID for announced repositories, the directory
|
||||
/// name for local ones.
|
||||
fn display_name(&self, cx: &App) -> SharedString {
|
||||
let store = self.store.read(cx);
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@ use super::{RefKind, RepoDetailView};
|
||||
use crate::views::repo::helpers::{build_tree_items, sorted_worktree_paths, tree_items};
|
||||
|
||||
impl RepoDetailView {
|
||||
/// Check out `name`, a branch or tag picked in the header.
|
||||
/// Refresh the explorer once the switch completes.
|
||||
pub(super) fn switch_ref(
|
||||
&mut self,
|
||||
kind: RefKind,
|
||||
@@ -81,7 +79,6 @@ impl RepoDetailView {
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Restore a selector to `previous`, or clear it after a failed switch.
|
||||
fn restore_selection(
|
||||
&self,
|
||||
select: &Entity<ComboboxState<SearchableVec<SharedString>>>,
|
||||
@@ -95,10 +92,6 @@ impl RepoDetailView {
|
||||
});
|
||||
}
|
||||
|
||||
/// Refresh the file explorer, preview pane and commit list after a successful switch.
|
||||
/// The selectors were already updated by [`Self::switch_ref`].
|
||||
/// [`Self::switching_ref`] stays set until this reload finishes.
|
||||
/// A second switch cannot interleave.
|
||||
fn reload_worktree(&mut self, cx: &mut Context<Self>) {
|
||||
let Some(worktree) = self.worktree.clone() else {
|
||||
return;
|
||||
|
||||
@@ -26,8 +26,6 @@ impl RepoDetailView {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Observe the repository's store and re-render on its refreshes.
|
||||
/// Start the explorer once the store has an announcement.
|
||||
pub(super) fn attach_store(
|
||||
&mut self,
|
||||
store: &Entity<RepoStore>,
|
||||
@@ -82,7 +80,6 @@ impl RepoDetailView {
|
||||
});
|
||||
}
|
||||
|
||||
/// The ready-to-contribute and ready-to-push statuses of this repository.
|
||||
pub(super) fn refresh_statuses(&mut self, cx: &mut Context<Self>) -> bool {
|
||||
let Some(addr) = self.store.read(cx).addr().cloned() else {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user