logiguard fork v3: full patch set on verified 8c74db0 tree

Includes prior-session patches (carry forward so the app compiles):
  - crates/gpui/build.rs: cross-compile manifest fix
  - crates/gpui/src/platform.rs: PlatformWindow::activate_with_token trait method
  - crates/gpui/src/window.rs: Window::activate_with_token public API
  - crates/gpui_linux/src/linux/wayland/window.rs: WaylandWindow::activate_with_token + activate() keyboard-serial fix

Plus the focus-serial fix:
  - serial.rs: SerialKind::KeyboardEnter
  - client.rs: store wl_keyboard.enter serial; latest_serial_of()

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mohamad Khani
2026-07-14 01:52:12 +03:30
commit b9819977a5
3984 changed files with 1487015 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
[package]
name = "extensions_ui"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/extensions_ui.rs"
[dependencies]
anyhow.workspace = true
client.workspace = true
cloud_api_types.workspace = true
collections.workspace = true
db.workspace = true
editor.workspace = true
extension.workspace = true
extension_host.workspace = true
fs.workspace = true
fuzzy.workspace = true
gpui.workspace = true
language.workspace = true
log.workspace = true
num-format.workspace = true
picker.workspace = true
project.workspace = true
release_channel.workspace = true
semver.workspace = true
serde.workspace = true
settings.workspace = true
smallvec.workspace = true
strum.workspace = true
telemetry.workspace = true
theme_settings.workspace = true
ui.workspace = true
util.workspace = true
vim_mode_setting.workspace = true
workspace.workspace = true
zed_actions.workspace = true
[dev-dependencies]
editor = { workspace = true, features = ["test-support"] }

View File

@@ -0,0 +1 @@
../../LICENSE-GPL

View File

@@ -0,0 +1,3 @@
mod extension_card;
pub use extension_card::*;

View File

@@ -0,0 +1,61 @@
use gpui::{AnyElement, prelude::*};
use smallvec::SmallVec;
use ui::prelude::*;
#[derive(IntoElement)]
pub struct ExtensionCard {
overridden_by_dev_extension: bool,
children: SmallVec<[AnyElement; 2]>,
}
impl ExtensionCard {
pub fn new() -> Self {
Self {
overridden_by_dev_extension: false,
children: SmallVec::new(),
}
}
pub fn overridden_by_dev_extension(mut self, overridden: bool) -> Self {
self.overridden_by_dev_extension = overridden;
self
}
}
impl ParentElement for ExtensionCard {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements)
}
}
impl RenderOnce for ExtensionCard {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
div().w_full().child(
v_flex()
.mt_4()
.w_full()
.h(rems_from_px(110.))
.p_3()
.gap_2()
.bg(cx.theme().colors().elevated_surface_background.opacity(0.5))
.border_1()
.border_color(cx.theme().colors().border_variant)
.rounded_md()
.children(self.children)
.when(self.overridden_by_dev_extension, |card| {
card.child(
h_flex()
.absolute()
.top_0()
.left_0()
.block_mouse_except_scroll()
.cursor_default()
.size_full()
.justify_center()
.bg(cx.theme().colors().elevated_surface_background.alpha(0.8))
.child(Label::new("Overridden by dev extension.")),
)
}),
)
}
}

View File

@@ -0,0 +1,252 @@
use std::collections::HashMap;
use std::sync::{Arc, OnceLock};
use db::kvp::KeyValueStore;
use editor::Editor;
use extension_host::ExtensionStore;
use gpui::{AppContext as _, Context, Entity, SharedString, Window};
use language::Buffer;
use ui::prelude::*;
use util::ResultExt;
use util::rel_path::RelPath;
use workspace::notifications::simple_message_notification::MessageNotification;
use workspace::{Workspace, notifications::NotificationId};
const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[
("astro", &["astro"]),
("beancount", &["beancount"]),
("clojure", &["bb", "clj", "cljc", "cljs", "edn"]),
("neocmake", &["CMakeLists.txt", "cmake"]),
("csharp", &["cs"]),
("cython", &["pyx", "pxd", "pxi"]),
("dart", &["dart"]),
("dockerfile", &["Dockerfile"]),
("elisp", &["el"]),
("elixir", &["eex", "ex", "exs", "heex", "leex", "neex"]),
("elm", &["elm"]),
("erlang", &["erl", "hrl"]),
("fish", &["fish"]),
(
"git-firefly",
&[
".gitconfig",
".gitignore",
"COMMIT_EDITMSG",
"EDIT_DESCRIPTION",
"MERGE_MSG",
"NOTES_EDITMSG",
"TAG_EDITMSG",
"git-rebase-todo",
],
),
("gleam", &["gleam"]),
("glsl", &["vert", "frag"]),
("graphql", &["gql", "graphql"]),
("haskell", &["hs"]),
("html", &["htm", "html", "shtml"]),
("java", &["java"]),
("kotlin", &["kt"]),
("latex", &["tex"]),
("log", &["log"]),
("lua", &["lua"]),
("make", &["Makefile"]),
("nim", &["nim"]),
("nix", &["nix"]),
("nu", &["nu"]),
("ocaml", &["ml", "mli"]),
("php", &["php"]),
("powershell", &["ps1", "psm1"]),
("prisma", &["prisma"]),
("proto", &["proto"]),
("purescript", &["purs"]),
("r", &["r", "R"]),
("racket", &["rkt"]),
("rescript", &["res", "resi"]),
("rst", &["rst"]),
("ruby", &["rb", "erb"]),
("scheme", &["scm"]),
("scss", &["scss"]),
("sql", &["sql"]),
("svelte", &["svelte"]),
("swift", &["swift"]),
("templ", &["templ"]),
("terraform", &["tf", "tfvars", "hcl"]),
("toml", &["Cargo.lock", "toml"]),
("typst", &["typ"]),
("vue", &["vue"]),
("wgsl", &["wgsl"]),
("wit", &["wit"]),
("xml", &["xml"]),
("zig", &["zig"]),
];
fn suggested_extensions() -> &'static HashMap<&'static str, Arc<str>> {
static SUGGESTIONS_BY_PATH_SUFFIX: OnceLock<HashMap<&str, Arc<str>>> = OnceLock::new();
SUGGESTIONS_BY_PATH_SUFFIX.get_or_init(|| {
SUGGESTIONS_BY_EXTENSION_ID
.iter()
.flat_map(|(name, path_suffixes)| {
let name = Arc::<str>::from(*name);
path_suffixes
.iter()
.map(move |suffix| (*suffix, name.clone()))
})
.collect()
})
}
#[derive(Debug, PartialEq, Eq, Clone)]
struct SuggestedExtension {
pub extension_id: Arc<str>,
pub file_name_or_extension: Arc<str>,
}
/// Returns the suggested extension for the given [`Path`].
fn suggested_extension(path: &RelPath) -> Option<SuggestedExtension> {
let file_extension: Option<Arc<str>> = path.extension().map(|extension| extension.into());
let file_name: Option<Arc<str>> = path.file_name().map(|name| name.into());
let (file_name_or_extension, extension_id) = None
// We suggest against file names first, as these suggestions will be more
// specific than ones based on the file extension.
.or_else(|| {
file_name.clone().zip(
file_name
.as_deref()
.and_then(|file_name| suggested_extensions().get(file_name)),
)
})
.or_else(|| {
file_extension.clone().zip(
file_extension
.as_deref()
.and_then(|file_extension| suggested_extensions().get(file_extension)),
)
})?;
Some(SuggestedExtension {
extension_id: extension_id.clone(),
file_name_or_extension,
})
}
fn language_extension_key(extension_id: &str) -> String {
format!("{}_extension_suggest", extension_id)
}
pub(crate) fn suggest(buffer: Entity<Buffer>, window: &mut Window, cx: &mut Context<Workspace>) {
let Some(file) = buffer.read(cx).file().cloned() else {
return;
};
let Some(SuggestedExtension {
extension_id,
file_name_or_extension,
}) = suggested_extension(file.path())
else {
return;
};
let key = language_extension_key(&extension_id);
let kvp = KeyValueStore::global(cx);
let Ok(None) = kvp.read_kvp(&key) else {
return;
};
cx.on_next_frame(window, move |workspace, _, cx| {
let Some(editor) = workspace.active_item_as::<Editor>(cx) else {
return;
};
if editor.read(cx).buffer().read(cx).as_singleton().as_ref() != Some(&buffer) {
return;
}
struct ExtensionSuggestionNotification;
let notification_id = NotificationId::composite::<ExtensionSuggestionNotification>(
SharedString::from(extension_id.clone()),
);
workspace.show_notification(notification_id, cx, |cx| {
cx.new(move |cx| {
MessageNotification::new(
format!(
"Do you want to install the recommended '{}' extension for '{}' files?",
extension_id, file_name_or_extension
),
cx,
)
.primary_message("Yes, install extension")
.primary_icon(IconName::Check)
.primary_icon_color(Color::Success)
.primary_on_click({
let extension_id = extension_id.clone();
move |_window, cx| {
let extension_id = extension_id.clone();
let extension_store = ExtensionStore::global(cx);
extension_store.update(cx, move |store, cx| {
store.install_latest_extension(extension_id, cx);
});
}
})
.secondary_message("No, don't install it")
.secondary_icon(IconName::Close)
.secondary_icon_color(Color::Error)
.secondary_on_click(move |_window, cx| {
let key = language_extension_key(&extension_id);
let kvp = KeyValueStore::global(cx);
cx.background_spawn(async move {
kvp.write_kvp(key, "dismissed".to_string()).await.log_err()
})
.detach();
})
})
});
})
}
#[cfg(test)]
mod tests {
use super::*;
use util::rel_path::rel_path;
#[test]
pub fn test_suggested_extension() {
assert_eq!(
suggested_extension(rel_path("Cargo.toml")),
Some(SuggestedExtension {
extension_id: "toml".into(),
file_name_or_extension: "toml".into()
})
);
assert_eq!(
suggested_extension(rel_path("Cargo.lock")),
Some(SuggestedExtension {
extension_id: "toml".into(),
file_name_or_extension: "Cargo.lock".into()
})
);
assert_eq!(
suggested_extension(rel_path("Dockerfile")),
Some(SuggestedExtension {
extension_id: "dockerfile".into(),
file_name_or_extension: "Dockerfile".into()
})
);
assert_eq!(
suggested_extension(rel_path("a/b/c/d/.gitignore")),
Some(SuggestedExtension {
extension_id: "git-firefly".into(),
file_name_or_extension: ".gitignore".into()
})
);
assert_eq!(
suggested_extension(rel_path("a/b/c/d/test.gleam")),
Some(SuggestedExtension {
extension_id: "gleam".into(),
file_name_or_extension: "gleam".into()
})
);
}
}

View File

@@ -0,0 +1,251 @@
use std::str::FromStr;
use std::sync::Arc;
use cloud_api_types::ExtensionMetadata;
use extension_host::ExtensionStore;
use fs::Fs;
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
use gpui::{App, DismissEvent, Entity, EventEmitter, Focusable, Task, WeakEntity, prelude::*};
use picker::{Picker, PickerDelegate};
use release_channel::ReleaseChannel;
use semver::Version;
use settings::update_settings_file;
use ui::{HighlightedLabel, ListItem, ListItemSpacing, prelude::*};
use util::ResultExt;
use workspace::ModalView;
pub struct ExtensionVersionSelector {
picker: Entity<Picker<ExtensionVersionSelectorDelegate>>,
}
impl ModalView for ExtensionVersionSelector {}
impl EventEmitter<DismissEvent> for ExtensionVersionSelector {}
impl Focusable for ExtensionVersionSelector {
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for ExtensionVersionSelector {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
v_flex().w(rems(34.)).child(self.picker.clone())
}
}
impl ExtensionVersionSelector {
pub fn new(
delegate: ExtensionVersionSelectorDelegate,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
}
pub struct ExtensionVersionSelectorDelegate {
fs: Arc<dyn Fs>,
selector: WeakEntity<ExtensionVersionSelector>,
extension_versions: Vec<ExtensionMetadata>,
selected_index: usize,
matches: Vec<StringMatch>,
}
impl ExtensionVersionSelectorDelegate {
pub fn new(
fs: Arc<dyn Fs>,
selector: WeakEntity<ExtensionVersionSelector>,
mut extension_versions: Vec<ExtensionMetadata>,
) -> Self {
extension_versions.sort_unstable_by(|a, b| {
let a_version = Version::from_str(&a.manifest.version);
let b_version = Version::from_str(&b.manifest.version);
match (a_version, b_version) {
(Ok(a_version), Ok(b_version)) => b_version.cmp(&a_version),
_ => b.published_at.cmp(&a.published_at),
}
});
let matches = extension_versions
.iter()
.map(|extension| StringMatch {
candidate_id: 0,
score: 0.0,
positions: Default::default(),
string: format!("v{}", extension.manifest.version),
})
.collect();
Self {
fs,
selector,
extension_versions,
selected_index: 0,
matches,
}
}
}
impl PickerDelegate for ExtensionVersionSelectorDelegate {
type ListItem = ui::ListItem;
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
"Select extension version...".into()
}
fn match_count(&self) -> usize {
self.matches.len()
}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_cx: &mut Context<Picker<Self>>,
) {
self.selected_index = ix;
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let background_executor = cx.background_executor().clone();
let candidates = self
.extension_versions
.iter()
.enumerate()
.map(|(id, extension)| {
StringMatchCandidate::new(id, &format!("v{}", extension.manifest.version))
})
.collect::<Vec<_>>();
cx.spawn_in(window, async move |this, cx| {
let matches = if query.is_empty() {
candidates
.into_iter()
.enumerate()
.map(|(index, candidate)| StringMatch {
candidate_id: index,
string: candidate.string,
positions: Vec::new(),
score: 0.0,
})
.collect()
} else {
match_strings(
&candidates,
&query,
false,
true,
100,
&Default::default(),
background_executor,
)
.await
};
this.update(cx, |this, _cx| {
this.delegate.matches = matches;
this.delegate.selected_index = this
.delegate
.selected_index
.min(this.delegate.matches.len().saturating_sub(1));
})
.log_err();
})
}
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
if self.matches.is_empty() {
self.dismissed(window, cx);
return;
}
let candidate_id = self.matches[self.selected_index].candidate_id;
let extension_version = &self.extension_versions[candidate_id];
if !extension_host::is_version_compatible(ReleaseChannel::global(cx), extension_version) {
return;
}
let extension_store = ExtensionStore::global(cx);
extension_store.update(cx, |store, cx| {
let extension_id = extension_version.id.clone();
let version = extension_version.manifest.version.clone();
update_settings_file(self.fs.clone(), cx, {
let extension_id = extension_id.clone();
move |settings, _| {
settings
.extension
.auto_update_extensions
.insert(extension_id, false);
}
});
store.install_extension(extension_id, version, cx);
});
}
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
self.selector
.update(cx, |_, cx| cx.emit(DismissEvent))
.log_err();
}
fn render_match(
&self,
ix: usize,
selected: bool,
_: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let version_match = &self.matches.get(ix)?;
let extension_version = &self.extension_versions.get(version_match.candidate_id)?;
let is_version_compatible =
extension_host::is_version_compatible(ReleaseChannel::global(cx), extension_version);
let disabled = !is_version_compatible;
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.disabled(disabled)
.child(
HighlightedLabel::new(
version_match.string.clone(),
version_match.positions.clone(),
)
.when(disabled, |label| label.color(Color::Muted)),
)
.end_slot(
h_flex()
.gap_2()
.when(!is_version_compatible, |this| {
this.child(Label::new("Incompatible").color(Color::Muted))
})
.child(
Label::new(
extension_version
.published_at
.format("%Y-%m-%d")
.to_string(),
)
.when(disabled, |label| label.color(Color::Muted)),
),
),
)
}
}

File diff suppressed because it is too large Load Diff