Compare commits

...

8 Commits

Author SHA1 Message Date
Jakub Konka
370a418fb7 git: Dedup paths needing status update in the job 2025-11-26 13:28:57 +01:00
Jakub Konka
78879876c1 git: Log job queue 2025-11-26 13:28:05 +01:00
ihavecoke
684a58fc84 Implement vertical scrolling for extended keymap load error information (#42542)
This PR fix an issue where, if an error occurs while loading the keymap
file during application startup, an excessively long error message would
be truncated and not fully displayed.

Before:

<img width="1567" height="1056" alt="before"
src="https://github.com/user-attachments/assets/ab80c204-b642-4e8f-aaf5-eae070ab01a2"
/>


After:

<img width="1567" height="1056" alt="image"
src="https://github.com/user-attachments/assets/1b2ff2ce-3796-41d5-b145-dc7d69f04f11"
/>


Release Notes:

- N/A
2025-11-26 10:09:26 +01:00
Floyd Wang
9150346a43 outline_panel: Fix the panel frequent flickering during search (#43530)
The outline panel flickers when searching or when the file content
changes. This happens because an empty UI appears during the search
process, but it only lasts for a few milliseconds, so we can safely
ignore it.

## Before

https://github.com/user-attachments/assets/9b409827-75ee-4a45-864a-58f0ca43191f

## After

https://github.com/user-attachments/assets/b6d48143-1f1a-4811-8754-0a679428eec2

Release Notes:

- N/A
2025-11-26 09:03:52 +00:00
Bhuminjay Soni
425d4c73f3 git: Use correct file mode when staging (#41900)
Closes #28667

Release Notes:

- Fixed git not preserving file mode when committing. Now if an input file is executable it will be preserved when committed with Zed.

---------

Signed-off-by: 11happy <soni5happy@gmail.com>
Signed-off-by: 11happy <bhuminjaysoni@gmail.com>
Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
2025-11-26 09:01:20 +00:00
Lukas Wirth
00e93bfa11 shell: Correctly identifiy powershell shells on windows (#43526)
Release Notes:

- Fixed zed only finding pwsh but not powershell on windows
2025-11-26 08:00:46 +00:00
Oscar Vargas Torres
9d8b5077b4 zeta: Avoid logging an error for not having SWEEP_AI_TOKEN (#43504)
Closes #43503 

Release Notes:

- Fixes ERROR No SWEEP_AI_TOKEN environment variable set

Co-authored-by: oscarvarto <oscarvarto@users.noreply.github.com>
2025-11-26 07:48:06 +01:00
qystishere
3072133e59 Improve bash detection on Windows (#43455)
I have git installed via [scoop](https://scoop.sh). The current
implementation finds `git.exe` in scoop's shims folder and then tries to
find `bash.exe` relative to it.

For example, `git.exe` (shim) is located at:
```
C:\Users\<username>\scoop\shims\git.exe
```

And the code tries to find `bash.exe` at:
```
C:\Users\<username>\scoop\shims\..\bin\bash.exe
```
which doesn't exist.

This PR changes the logic to first check if `bash.exe` is available in
PATH (using `which::which`), and only falls back to the git-relative
path if that fails.
2025-11-26 07:45:50 +01:00
14 changed files with 256 additions and 83 deletions

10
Cargo.lock generated
View File

@@ -6406,6 +6406,7 @@ dependencies = [
"git",
"gpui",
"ignore",
"is_executable",
"libc",
"log",
"notify 8.2.0",
@@ -8436,6 +8437,15 @@ dependencies = [
"once_cell",
]
[[package]]
name = "is_executable"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4"
dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"

View File

@@ -250,6 +250,7 @@ impl PasswordProxy {
.await
.with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?;
make_file_executable(&askpass_script_path).await?;
// todo(shell): There might be no powershell on the system
#[cfg(target_os = "windows")]
let askpass_helper = format!(
"powershell.exe -ExecutionPolicy Bypass -File {}",

View File

@@ -1850,7 +1850,7 @@ impl Editor {
.cursor_pointer()
.child("")
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
.on_click(move |_, _window, cx| {
.on_click(move |_, _win, cx| {
editor
.update(cx, |editor, cx| {
editor.unfold_ranges(
@@ -6605,7 +6605,7 @@ impl Editor {
.when(show_tooltip, |this| {
this.tooltip({
let focus_handle = self.focus_handle.clone();
move |_window, cx| {
move |_win, cx| {
Tooltip::for_action_in(
"Toggle Code Actions",
&ToggleCodeActions {
@@ -7929,7 +7929,7 @@ impl Editor {
fn update_visible_edit_prediction(
&mut self,
_window: &mut Window,
_win: &mut Window,
cx: &mut Context<Self>,
) -> Option<()> {
if DisableAiSettings::get_global(cx).disable_ai {
@@ -8308,7 +8308,7 @@ impl Editor {
this.entry(msg, None, {
let weak_editor = weak_editor.clone();
let breakpoint = breakpoint.clone();
move |_window, cx| {
move |_win, cx| {
weak_editor
.update(cx, |this, cx| {
this.edit_breakpoint_at_anchor(
@@ -25434,7 +25434,7 @@ fn render_diff_hunk_controls(
Button::new(("restore", row as u64), "Restore")
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |_window, cx| {
move |_win, cx| {
Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
}
})
@@ -25460,7 +25460,7 @@ fn render_diff_hunk_controls(
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |_window, cx| {
move |_win, cx| {
Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
}
})
@@ -25490,7 +25490,7 @@ fn render_diff_hunk_controls(
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |_window, cx| {
move |_win, cx| {
Tooltip::for_action_in(
"Previous Hunk",
&GoToPreviousHunk,

View File

@@ -33,6 +33,7 @@ tempfile.workspace = true
text.workspace = true
time.workspace = true
util.workspace = true
is_executable = "1.0.5"
[target.'cfg(target_os = "macos")'.dependencies]
fsevent.workspace = true

View File

@@ -138,6 +138,7 @@ impl GitRepository for FakeGitRepository {
path: RepoPath,
content: Option<String>,
_env: Arc<HashMap<String, String>>,
_is_executable: bool,
) -> BoxFuture<'_, anyhow::Result<()>> {
self.with_state_async(true, move |state| {
if let Some(message) = &state.simulated_index_write_error_message {

View File

@@ -32,6 +32,7 @@ use std::mem::MaybeUninit;
use async_tar::Archive;
use futures::{AsyncRead, Stream, StreamExt, future::BoxFuture};
use git::repository::{GitRepository, RealGitRepository};
use is_executable::IsExecutable;
use rope::Rope;
use serde::{Deserialize, Serialize};
use smol::io::AsyncWriteExt;
@@ -208,6 +209,7 @@ pub struct Metadata {
pub is_dir: bool,
pub len: u64,
pub is_fifo: bool,
pub is_executable: bool,
}
/// Filesystem modification time. The purpose of this newtype is to discourage use of operations
@@ -895,6 +897,12 @@ impl Fs for RealFs {
#[cfg(unix)]
let is_fifo = metadata.file_type().is_fifo();
let path_buf = path.to_path_buf();
let is_executable = self
.executor
.spawn(async move { path_buf.is_executable() })
.await;
Ok(Some(Metadata {
inode,
mtime: MTime(metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH)),
@@ -902,6 +910,7 @@ impl Fs for RealFs {
is_symlink,
is_dir: metadata.file_type().is_dir(),
is_fifo,
is_executable,
}))
}
@@ -2602,6 +2611,7 @@ impl Fs for FakeFs {
is_dir: false,
is_symlink,
is_fifo: false,
is_executable: false,
},
FakeFsEntry::Dir {
inode, mtime, len, ..
@@ -2612,6 +2622,7 @@ impl Fs for FakeFs {
is_dir: true,
is_symlink,
is_fifo: false,
is_executable: false,
},
FakeFsEntry::Symlink { .. } => unreachable!(),
}))

View File

@@ -400,6 +400,7 @@ pub trait GitRepository: Send + Sync {
path: RepoPath,
content: Option<String>,
env: Arc<HashMap<String, String>>,
is_executable: bool,
) -> BoxFuture<'_, anyhow::Result<()>>;
/// Returns the URL of the remote with the given name.
@@ -987,12 +988,15 @@ impl GitRepository for RealGitRepository {
path: RepoPath,
content: Option<String>,
env: Arc<HashMap<String, String>>,
is_executable: bool,
) -> BoxFuture<'_, anyhow::Result<()>> {
let working_directory = self.working_directory();
let git_binary_path = self.any_git_binary_path.clone();
self.executor
.spawn(async move {
let working_directory = working_directory?;
let mode = if is_executable { "100755" } else { "100644" };
if let Some(content) = content {
let mut child = new_smol_command(&git_binary_path)
.current_dir(&working_directory)
@@ -1013,7 +1017,7 @@ impl GitRepository for RealGitRepository {
let output = new_smol_command(&git_binary_path)
.current_dir(&working_directory)
.envs(env.iter())
.args(["update-index", "--add", "--cacheinfo", "100644", sha])
.args(["update-index", "--add", "--cacheinfo", mode, sha])
.arg(path.as_unix_str())
.output()
.await?;

View File

@@ -389,11 +389,12 @@ impl Platform for WindowsPlatform {
#[allow(
clippy::disallowed_methods,
reason = "We are restarting ourselves, using std command thus is fine"
)]
let restart_process = util::command::new_std_command("powershell.exe")
.arg("-command")
.arg(script)
.spawn();
)] // todo(shell): There might be no powershell on the system
let restart_process =
util::command::new_std_command(util::shell::get_windows_system_shell())
.arg("-command")
.arg(script)
.spawn();
match restart_process {
Ok(_) => self.quit(),

View File

@@ -111,8 +111,6 @@ pub struct OutlinePanel {
selected_entry: SelectedEntry,
active_item: Option<ActiveItem>,
_subscriptions: Vec<Subscription>,
updating_fs_entries: bool,
updating_cached_entries: bool,
new_entries_for_fs_update: HashSet<ExcerptId>,
fs_entries_update_task: Task<()>,
cached_entries_update_task: Task<()>,
@@ -853,8 +851,6 @@ impl OutlinePanel {
width: None,
active_item: None,
pending_serialization: Task::ready(None),
updating_fs_entries: false,
updating_cached_entries: false,
new_entries_for_fs_update: HashSet::default(),
preserve_selection_on_buffer_fold_toggles: HashSet::default(),
pending_default_expansion_depth: None,
@@ -2658,7 +2654,6 @@ impl OutlinePanel {
let repo_snapshots = self.project.update(cx, |project, cx| {
project.git_store().read(cx).repo_snapshots(cx)
});
self.updating_fs_entries = true;
self.fs_entries_update_task = cx.spawn_in(window, async move |outline_panel, cx| {
if let Some(debounce) = debounce {
cx.background_executor().timer(debounce).await;
@@ -3016,7 +3011,6 @@ impl OutlinePanel {
outline_panel
.update_in(cx, |outline_panel, window, cx| {
outline_panel.updating_fs_entries = false;
outline_panel.new_entries_for_fs_update.clear();
outline_panel.excerpts = new_excerpts;
outline_panel.collapsed_entries = new_collapsed_entries;
@@ -3579,7 +3573,6 @@ impl OutlinePanel {
let is_singleton = self.is_singleton_active(cx);
let query = self.query(cx);
self.updating_cached_entries = true;
self.cached_entries_update_task = cx.spawn_in(window, async move |outline_panel, cx| {
if let Some(debounce) = debounce {
cx.background_executor().timer(debounce).await;
@@ -3612,7 +3605,6 @@ impl OutlinePanel {
}
outline_panel.autoscroll(cx);
outline_panel.updating_cached_entries = false;
cx.notify();
})
.ok();
@@ -4542,12 +4534,10 @@ impl OutlinePanel {
cx: &mut Context<Self>,
) -> impl IntoElement {
let contents = if self.cached_entries.is_empty() {
let header = if self.updating_fs_entries || self.updating_cached_entries {
None
} else if query.is_some() {
Some("No matches for query")
let header = if query.is_some() {
"No matches for query"
} else {
Some("No outlines available")
"No outlines available"
};
v_flex()
@@ -4556,33 +4546,28 @@ impl OutlinePanel {
.flex_1()
.justify_center()
.size_full()
.when_some(header, |panel, header| {
panel
.child(h_flex().justify_center().child(Label::new(header)))
.when_some(query.clone(), |panel, query| {
panel.child(
h_flex()
.px_0p5()
.justify_center()
.bg(cx.theme().colors().element_selected.opacity(0.2))
.child(Label::new(query)),
)
})
.child(h_flex().justify_center().child({
let keystroke = match self.position(window, cx) {
DockPosition::Left => {
window.keystroke_text_for(&workspace::ToggleLeftDock)
}
DockPosition::Bottom => {
window.keystroke_text_for(&workspace::ToggleBottomDock)
}
DockPosition::Right => {
window.keystroke_text_for(&workspace::ToggleRightDock)
}
};
Label::new(format!("Toggle Panel With {keystroke}")).color(Color::Muted)
}))
.child(h_flex().justify_center().child(Label::new(header)))
.when_some(query, |panel, query| {
panel.child(
h_flex()
.px_0p5()
.justify_center()
.bg(cx.theme().colors().element_selected.opacity(0.2))
.child(Label::new(query)),
)
})
.child(h_flex().justify_center().child({
let keystroke = match self.position(window, cx) {
DockPosition::Left => window.keystroke_text_for(&workspace::ToggleLeftDock),
DockPosition::Bottom => {
window.keystroke_text_for(&workspace::ToggleBottomDock)
}
DockPosition::Right => {
window.keystroke_text_for(&workspace::ToggleRightDock)
}
};
Label::new(format!("Toggle Panel With {keystroke}")).color(Color::Muted)
}))
} else {
let list_contents = {
let items_len = self.cached_entries.len();

View File

@@ -301,6 +301,7 @@ impl std::ops::Deref for Repository {
#[derive(Clone)]
pub enum RepositoryState {
Local {
fs: Arc<dyn Fs>,
backend: Arc<dyn GitRepository>,
environment: Arc<HashMap<String, String>>,
},
@@ -342,7 +343,13 @@ pub struct GitJob {
key: Option<GitJobKey>,
}
#[derive(PartialEq, Eq)]
impl std::fmt::Debug for GitJob {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "GitJob {{ {:?} }}", self.key)
}
}
#[derive(Debug, PartialEq, Eq)]
enum GitJobKey {
WriteIndex(Vec<RepoPath>),
ReloadBufferDiffBases,
@@ -4288,6 +4295,7 @@ impl Repository {
RepositoryState::Local {
backend,
environment,
..
} => backend.run_hook(hook, environment.clone()).await,
RepositoryState::Remote { project_id, client } => {
client
@@ -4580,6 +4588,7 @@ impl Repository {
let id = self.id;
let this = cx.weak_entity();
let git_store = self.git_store.clone();
let abs_path = self.snapshot.repo_path_to_abs_path(&path);
self.send_keyed_job(
Some(GitJobKey::WriteIndex(vec![path.clone()])),
None,
@@ -4588,14 +4597,21 @@ impl Repository {
"start updating index text for buffer {}",
path.as_unix_str()
);
match git_repo {
RepositoryState::Local {
fs,
backend,
environment,
..
} => {
let executable = match fs.metadata(&abs_path).await {
Ok(Some(meta)) => meta.is_executable,
Ok(None) => false,
Err(_err) => false,
};
backend
.set_index_text(path.clone(), content, environment.clone())
.set_index_text(path.clone(), content, environment.clone(), executable)
.await?;
}
RepositoryState::Remote { project_id, client } => {
@@ -5164,6 +5180,7 @@ impl Repository {
cx: &mut Context<Self>,
) -> mpsc::UnboundedSender<GitJob> {
let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
let fs_cloned = fs.clone();
cx.spawn(async move |_, cx| {
let environment = project_environment
@@ -5195,29 +5212,36 @@ impl Repository {
backend.clone(),
);
}
let state = RepositoryState::Local {
fs: fs_cloned,
backend,
environment: Arc::new(environment),
};
let mut jobs = VecDeque::new();
loop {
println!(" Current job queue: {:?}", jobs);
while let Ok(Some(next_job)) = job_rx.try_next() {
println!(" >>> pushing new job: {next_job:?}");
jobs.push_back(next_job);
}
if let Some(job) = jobs.pop_front() {
println!(" >>> popping front: {job:?}");
if let Some(current_key) = &job.key
&& jobs
.iter()
.any(|other_job| other_job.key.as_ref() == Some(current_key))
{
println!(" >>> SKIPPING");
continue;
}
println!(" >>> RUNNING!");
(job.job)(state.clone(), cx).await;
} else if let Some(job) = job_rx.next().await {
println!(" >>> blocking for new job: {job:?}");
jobs.push_back(job);
} else {
// TODO: as far as I understand, this prong will never be reached
break;
}
}
@@ -5361,13 +5385,17 @@ impl Repository {
updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
cx: &mut Context<Self>,
) {
self.paths_needing_status_update.extend(paths);
println!("paths_neeeding_status_update: {paths:?}");
// self.paths_needing_status_update.extend(paths.clone());
let this = cx.weak_entity();
let _ = self.send_keyed_job(
let res = self.send_keyed_job(
Some(GitJobKey::RefreshStatuses),
None,
|state, mut cx| async move {
this.update(&mut cx, |this, _| {
this.paths_needing_status_update.extend(paths);
})?;
let (prev_snapshot, mut changed_paths) = this.update(&mut cx, |this, _| {
(
this.snapshot.clone(),
@@ -5439,6 +5467,21 @@ impl Repository {
})
},
);
cx.spawn(async move |this, cx| match res.await {
Ok(res) => {
let _ = res;
}
Err(Canceled) => {
println!(
"skipped!!! this.paths_needing_status_update.len() = {:?}",
this.update(cx, |this, _cx| {
this.paths_needing_status_update.iter().count()
})
);
}
})
.detach();
}
/// currently running git command and when it started

View File

@@ -8174,6 +8174,91 @@ async fn test_single_file_diffs(cx: &mut gpui::TestAppContext) {
});
}
// TODO: Should we test this on Windows also?
#[gpui::test]
#[cfg(not(windows))]
async fn test_staging_hunk_preserve_executable_permission(cx: &mut gpui::TestAppContext) {
use std::os::unix::fs::PermissionsExt;
init_test(cx);
cx.executor().allow_parking();
let committed_contents = "bar\n";
let file_contents = "baz\n";
let root = TempTree::new(json!({
"project": {
"foo": committed_contents
},
}));
let work_dir = root.path().join("project");
let file_path = work_dir.join("foo");
let repo = git_init(work_dir.as_path());
let mut perms = std::fs::metadata(&file_path).unwrap().permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&file_path, perms).unwrap();
git_add("foo", &repo);
git_commit("Initial commit", &repo);
std::fs::write(&file_path, file_contents).unwrap();
let project = Project::test(
Arc::new(RealFs::new(None, cx.executor())),
[root.path()],
cx,
)
.await;
let buffer = project
.update(cx, |project, cx| {
project.open_local_buffer(file_path.as_path(), cx)
})
.await
.unwrap();
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
let uncommitted_diff = project
.update(cx, |project, cx| {
project.open_uncommitted_diff(buffer.clone(), cx)
})
.await
.unwrap();
uncommitted_diff.update(cx, |diff, cx| {
let hunks = diff.hunks(&snapshot, cx).collect::<Vec<_>>();
diff.stage_or_unstage_hunks(true, &hunks, &snapshot, true, cx);
});
cx.run_until_parked();
let output = smol::process::Command::new("git")
.current_dir(&work_dir)
.args(["diff", "--staged"])
.output()
.await
.unwrap();
let staged_diff = String::from_utf8_lossy(&output.stdout);
assert!(
!staged_diff.contains("new mode 100644"),
"Staging should not change file mode from 755 to 644.\ngit diff --staged:\n{}",
staged_diff
);
let output = smol::process::Command::new("git")
.current_dir(&work_dir)
.args(["ls-files", "-s"])
.output()
.await
.unwrap();
let index_contents = String::from_utf8_lossy(&output.stdout);
assert!(
index_contents.contains("100755"),
"Index should show file as executable (100755).\ngit ls-files -s:\n{}",
index_contents
);
}
#[gpui::test]
async fn test_repository_and_path_for_project_path(
background_executor: BackgroundExecutor,

View File

@@ -79,29 +79,42 @@ pub fn get_default_system_shell() -> String {
}
}
/// Get the default system shell, preferring git-bash on Windows.
/// Get the default system shell, preferring bash on Windows.
pub fn get_default_system_shell_preferring_bash() -> String {
if cfg!(windows) {
get_windows_git_bash().unwrap_or_else(|| get_windows_system_shell())
get_windows_bash().unwrap_or_else(|| get_windows_system_shell())
} else {
"/bin/sh".to_string()
}
}
pub fn get_windows_git_bash() -> Option<String> {
static GIT_BASH: LazyLock<Option<String>> = LazyLock::new(|| {
pub fn get_windows_bash() -> Option<String> {
use std::path::PathBuf;
fn find_bash_in_scoop() -> Option<PathBuf> {
let bash_exe =
PathBuf::from(std::env::var_os("USERPROFILE")?).join("scoop\\shims\\bash.exe");
bash_exe.exists().then_some(bash_exe)
}
fn find_bash_in_git() -> Option<PathBuf> {
// /path/to/git/cmd/git.exe/../../bin/bash.exe
let git = which::which("git").ok()?;
let git_bash = git.parent()?.parent()?.join("bin").join("bash.exe");
if git_bash.is_file() {
log::info!("Found git-bash at {}", git_bash.display());
Some(git_bash.to_string_lossy().to_string())
} else {
None
git_bash.exists().then_some(git_bash)
}
static BASH: LazyLock<Option<String>> = LazyLock::new(|| {
let bash = find_bash_in_scoop()
.or_else(|| find_bash_in_git())
.map(|p| p.to_string_lossy().into_owned());
if let Some(ref path) = bash {
log::info!("Found bash at {}", path);
}
bash
});
(*GIT_BASH).clone()
(*BASH).clone()
}
pub fn get_windows_system_shell() -> String {
@@ -191,14 +204,22 @@ pub fn get_windows_system_shell() -> String {
}
static SYSTEM_SHELL: LazyLock<String> = LazyLock::new(|| {
find_pwsh_in_programfiles(false, false)
.or_else(|| find_pwsh_in_programfiles(true, false))
.or_else(|| find_pwsh_in_msix(false))
.or_else(|| find_pwsh_in_programfiles(false, true))
.or_else(|| find_pwsh_in_msix(true))
.or_else(|| find_pwsh_in_programfiles(true, true))
.or_else(find_pwsh_in_scoop)
.map(|p| p.to_string_lossy().into_owned())
let locations = [
|| find_pwsh_in_programfiles(false, false),
|| find_pwsh_in_programfiles(true, false),
|| find_pwsh_in_msix(false),
|| find_pwsh_in_programfiles(false, true),
|| find_pwsh_in_msix(true),
|| find_pwsh_in_programfiles(true, true),
|| find_pwsh_in_scoop(),
|| which::which_global("pwsh.exe").ok(),
|| which::which_global("powershell.exe").ok(),
];
locations
.into_iter()
.find_map(|f| f())
.map(|p| p.to_string_lossy().trim().to_owned())
.inspect(|shell| log::info!("Found powershell in: {}", shell))
.unwrap_or_else(|| {
log::warn!("Powershell not found, falling back to `cmd`");

View File

@@ -593,9 +593,9 @@ pub mod simple_message_notification {
use gpui::{
AnyElement, DismissEvent, EventEmitter, FocusHandle, Focusable, ParentElement, Render,
SharedString, Styled,
ScrollHandle, SharedString, Styled,
};
use ui::prelude::*;
use ui::{WithScrollbar, prelude::*};
use crate::notifications::NotificationFrame;
@@ -617,6 +617,7 @@ pub mod simple_message_notification {
show_close_button: bool,
show_suppress_button: bool,
title: Option<SharedString>,
scroll_handle: ScrollHandle,
}
impl Focusable for MessageNotification {
@@ -661,6 +662,7 @@ pub mod simple_message_notification {
show_suppress_button: true,
title: None,
focus_handle: cx.focus_handle(),
scroll_handle: ScrollHandle::new(),
}
}
@@ -777,7 +779,18 @@ pub mod simple_message_notification {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
NotificationFrame::new()
.with_title(self.title.clone())
.with_content((self.build_content)(window, cx))
.with_content(
div()
.child(
div()
.id("message-notification-content")
.max_h(vh(0.6, window))
.overflow_y_scroll()
.track_scroll(&self.scroll_handle.clone())
.child((self.build_content)(window, cx)),
)
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx),
)
.show_close_button(self.show_close_button)
.show_suppress_button(self.show_suppress_button)
.on_close(cx.listener(|_, suppress, _, cx| {

View File

@@ -1,4 +1,4 @@
use anyhow::{Context as _, Result};
use anyhow::Result;
use cloud_llm_client::predict_edits_v3::Event;
use futures::AsyncReadExt as _;
use gpui::{
@@ -17,7 +17,6 @@ use std::{
sync::Arc,
time::Instant,
};
use util::ResultExt as _;
use crate::{EditPrediction, EditPredictionId, EditPredictionInputs};
@@ -31,9 +30,7 @@ pub struct SweepAi {
impl SweepAi {
pub fn new(cx: &App) -> Self {
SweepAi {
api_token: std::env::var("SWEEP_AI_TOKEN")
.context("No SWEEP_AI_TOKEN environment variable set")
.log_err(),
api_token: std::env::var("SWEEP_AI_TOKEN").ok(),
debug_info: debug_info(cx),
}
}