Compare commits
10 Commits
git-panel-
...
pkexec2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8fe5de669 | ||
|
|
83889bb235 | ||
|
|
ebc4688c2a | ||
|
|
7f0e13258c | ||
|
|
8cd2afeacc | ||
|
|
b890a12030 | ||
|
|
115aa43354 | ||
|
|
bbb473b8df | ||
|
|
36301442dd | ||
|
|
52f29b4a1f |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -16199,7 +16199,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.169.0"
|
||||
version = "0.170.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.4286 9H10.5714C9.70355 9 9 9.70355 9 10.5714V18.4286C9 19.2964 9.70355 20 10.5714 20H18.4286C19.2964 20 20 19.2964 20 18.4286V10.5714C20 9.70355 19.2964 9 18.4286 9Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.57143 15C4.70714 15 4 14.2929 4 13.4286V5.57143C4 4.70714 4.70714 4 5.57143 4H13.4286C14.2929 4 15 4.70714 15 5.57143" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 338 B After Width: | Height: | Size: 576 B |
@@ -3654,7 +3654,7 @@ impl ContextEditor {
|
||||
|
||||
let (style, tooltip) = match token_state(&self.context, cx) {
|
||||
Some(TokenState::NoTokensLeft { .. }) => (
|
||||
ButtonStyle::Tinted(TintColor::Negative),
|
||||
ButtonStyle::Tinted(TintColor::Error),
|
||||
Some(Tooltip::text("Token limit reached", cx)),
|
||||
),
|
||||
Some(TokenState::HasMoreTokens {
|
||||
@@ -3711,7 +3711,7 @@ impl ContextEditor {
|
||||
|
||||
let (style, tooltip) = match token_state(&self.context, cx) {
|
||||
Some(TokenState::NoTokensLeft { .. }) => (
|
||||
ButtonStyle::Tinted(TintColor::Negative),
|
||||
ButtonStyle::Tinted(TintColor::Error),
|
||||
Some(Tooltip::text("Token limit reached", cx)),
|
||||
),
|
||||
Some(TokenState::HasMoreTokens {
|
||||
|
||||
@@ -231,27 +231,36 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
.collect::<Vec<_>>()
|
||||
})?;
|
||||
|
||||
let open_all_buffers_tasks = cx.background_executor().spawn(async move {
|
||||
let mut buffers = Vec::with_capacity(open_buffer_tasks.len());
|
||||
|
||||
for open_buffer_task in open_buffer_tasks {
|
||||
let buffer = open_buffer_task.await?;
|
||||
|
||||
buffers.push(buffer);
|
||||
}
|
||||
|
||||
anyhow::Ok(buffers)
|
||||
});
|
||||
|
||||
let buffers = open_all_buffers_tasks.await?;
|
||||
let buffers = futures::future::join_all(open_buffer_tasks).await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let mut text = String::new();
|
||||
|
||||
for buffer in buffers {
|
||||
let mut ok_count = 0;
|
||||
|
||||
for buffer in buffers.into_iter().flatten() {
|
||||
let buffer = buffer.read(cx);
|
||||
let path = buffer.file().map_or(&path, |file| file.path());
|
||||
push_fenced_codeblock(&path, buffer.text(), &mut text);
|
||||
ok_count += 1;
|
||||
}
|
||||
|
||||
if ok_count == 0 {
|
||||
let Some(workspace) = workspace.upgrade() else {
|
||||
return anyhow::Ok(());
|
||||
};
|
||||
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.show_error(
|
||||
&anyhow::anyhow!(
|
||||
"Could not read any text files from {}",
|
||||
path.display()
|
||||
),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
return anyhow::Ok(());
|
||||
}
|
||||
|
||||
this.delegate
|
||||
|
||||
@@ -239,21 +239,34 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
return anyhow::Ok(());
|
||||
};
|
||||
|
||||
let buffer = open_buffer_task.await?;
|
||||
let result = open_buffer_task.await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.delegate
|
||||
.context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
context_store.insert_file(buffer.read(cx));
|
||||
})?;
|
||||
this.update(&mut cx, |this, cx| match result {
|
||||
Ok(buffer) => {
|
||||
this.delegate
|
||||
.context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
context_store.insert_file(buffer.read(cx));
|
||||
})?;
|
||||
|
||||
match confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => {}
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(cx),
|
||||
match confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => {}
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(cx),
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
let Some(workspace) = workspace.upgrade() else {
|
||||
return anyhow::Ok(());
|
||||
};
|
||||
|
||||
anyhow::Ok(())
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.show_error(&err, cx);
|
||||
});
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
})??;
|
||||
|
||||
anyhow::Ok(())
|
||||
|
||||
@@ -10,7 +10,7 @@ use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
use rope::Point;
|
||||
use settings::Settings;
|
||||
use theme::ThemeSettings;
|
||||
use theme::{get_ui_font_size, ThemeSettings};
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, ElevationIndex, KeyBinding, PopoverMenu, PopoverMenuHandle,
|
||||
SwitchWithLabel,
|
||||
@@ -276,7 +276,7 @@ impl Render for MessageEditor {
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(-16.0),
|
||||
y: (-get_ui_font_size(cx) * 2) - px(4.0),
|
||||
})
|
||||
.with_handle(self.inline_context_picker_menu_handle.clone()),
|
||||
)
|
||||
|
||||
@@ -175,7 +175,7 @@ pub(crate) fn suggest(buffer: Model<Buffer>, cx: &mut ViewContext<Workspace>) {
|
||||
"Do you want to install the recommended '{}' extension for '{}' files?",
|
||||
extension_id, file_name_or_extension
|
||||
))
|
||||
.with_click_message("Yes")
|
||||
.with_click_message("Yes, install extension")
|
||||
.on_click({
|
||||
let extension_id = extension_id.clone();
|
||||
move |cx| {
|
||||
@@ -186,7 +186,7 @@ pub(crate) fn suggest(buffer: Model<Buffer>, cx: &mut ViewContext<Workspace>) {
|
||||
});
|
||||
}
|
||||
})
|
||||
.with_secondary_click_message("No")
|
||||
.with_secondary_click_message("No, don't install it")
|
||||
.on_secondary_click(move |cx| {
|
||||
let key = language_extension_key(&extension_id);
|
||||
db::write_and_log(cx, move || {
|
||||
|
||||
@@ -9,8 +9,6 @@ use git::GitHostingProviderRegistry;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use ashpd::desktop::trash;
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
use smol::process::Command;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::AsFd;
|
||||
@@ -547,25 +545,36 @@ impl Fs for RealFs {
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||
if cfg!(any(target_os = "linux", target_os = "freebsd")) {
|
||||
let target_path = path.to_path_buf();
|
||||
let temp_file = smol::unblock(move || create_temp_file(&target_path)).await?;
|
||||
let pkexec_path = smol::unblock(|| which::which("pkexec"))
|
||||
.await
|
||||
.map_err(|_| anyhow::anyhow!("pkexec not found in PATH"))?;
|
||||
|
||||
let temp_path = temp_file.into_temp_path();
|
||||
let temp_path_for_write = temp_path.to_path_buf();
|
||||
let mut child = smol::process::Command::new(&pkexec_path)
|
||||
.arg("sh")
|
||||
.arg("-c")
|
||||
.arg(format!(
|
||||
"cat > {}",
|
||||
shlex::try_quote(&path.to_string_lossy())?
|
||||
))
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let async_file = smol::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.open(&temp_path)
|
||||
.await?;
|
||||
|
||||
let mut writer = smol::io::BufWriter::with_capacity(buffer_size, async_file);
|
||||
|
||||
for chunk in chunks(text, line_ending) {
|
||||
let Some(stdin) = child.stdin.take() else {
|
||||
return Err(anyhow::anyhow!("Failed to write to file as root, no stdin"));
|
||||
};
|
||||
let mut writer = smol::io::BufWriter::with_capacity(buffer_size, stdin);
|
||||
for chunk in text.chunks() {
|
||||
writer.write_all(chunk.as_bytes()).await?;
|
||||
}
|
||||
writer.flush().await?;
|
||||
|
||||
write_to_file_as_root(temp_path_for_write, path.to_path_buf()).await
|
||||
let output = child.output().await?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Failed to write to file as root, exit status {}",
|
||||
output.status
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
// Todo: Implement for Mac and Windows
|
||||
Err(e.into())
|
||||
@@ -2002,61 +2011,6 @@ fn create_temp_file(path: &Path) -> Result<NamedTempFile> {
|
||||
Ok(temp_file)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
async fn write_to_file_as_root(_temp_file_path: PathBuf, _target_file_path: PathBuf) -> Result<()> {
|
||||
unimplemented!("write_to_file_as_root is not implemented")
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
async fn write_to_file_as_root(_temp_file_path: PathBuf, _target_file_path: PathBuf) -> Result<()> {
|
||||
unimplemented!("write_to_file_as_root is not implemented")
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
async fn write_to_file_as_root(temp_file_path: PathBuf, target_file_path: PathBuf) -> Result<()> {
|
||||
use shlex::try_quote;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use which::which;
|
||||
|
||||
let pkexec_path = smol::unblock(|| which("pkexec"))
|
||||
.await
|
||||
.map_err(|_| anyhow::anyhow!("pkexec not found in PATH"))?;
|
||||
|
||||
let script_file = smol::unblock(move || {
|
||||
let script_file = tempfile::Builder::new()
|
||||
.prefix("write-to-file-as-root-")
|
||||
.tempfile_in(paths::temp_dir())?;
|
||||
|
||||
writeln!(
|
||||
script_file.as_file(),
|
||||
"#!/usr/bin/env sh\nset -eu\ncat \"{}\" > \"{}\"",
|
||||
try_quote(&temp_file_path.to_string_lossy())?,
|
||||
try_quote(&target_file_path.to_string_lossy())?
|
||||
)?;
|
||||
|
||||
let mut perms = script_file.as_file().metadata()?.permissions();
|
||||
perms.set_mode(0o700); // rwx------
|
||||
script_file.as_file().set_permissions(perms)?;
|
||||
|
||||
Result::<_>::Ok(script_file)
|
||||
})
|
||||
.await?;
|
||||
|
||||
let script_path = script_file.into_temp_path();
|
||||
|
||||
let output = Command::new(&pkexec_path)
|
||||
.arg("--disable-internal-agent")
|
||||
.arg(&script_path)
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!("Failed to write to file as root"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn normalize_path(path: &Path) -> PathBuf {
|
||||
let mut components = path.components().peekable();
|
||||
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
|
||||
|
||||
@@ -1418,6 +1418,11 @@ impl AppContext {
|
||||
pub fn get_name(&self) -> &'static str {
|
||||
self.name.as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// Returns `true` if the platform file picker supports selecting a mix of files and directories.
|
||||
pub fn can_select_mixed_files_and_dirs(&self) -> bool {
|
||||
self.platform.can_select_mixed_files_and_dirs()
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AppContext {
|
||||
|
||||
@@ -19,6 +19,7 @@ pub struct Anchored {
|
||||
fit_mode: AnchoredFitMode,
|
||||
anchor_position: Option<Point<Pixels>>,
|
||||
position_mode: AnchoredPositionMode,
|
||||
offset: Option<Point<Pixels>>,
|
||||
}
|
||||
|
||||
/// anchored gives you an element that will avoid overflowing the window bounds.
|
||||
@@ -30,6 +31,7 @@ pub fn anchored() -> Anchored {
|
||||
fit_mode: AnchoredFitMode::SwitchAnchor,
|
||||
anchor_position: None,
|
||||
position_mode: AnchoredPositionMode::Window,
|
||||
offset: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +49,13 @@ impl Anchored {
|
||||
self
|
||||
}
|
||||
|
||||
/// Offset the final position by this amount.
|
||||
/// Useful when you want to anchor to an element but offset from it, such as in PopoverMenu.
|
||||
pub fn offset(mut self, offset: Point<Pixels>) -> Self {
|
||||
self.offset = Some(offset);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the position mode for this anchored element. Local will have this
|
||||
/// interpret its [`Anchored::position`] as relative to the parent element.
|
||||
/// While Window will have it interpret the position as relative to the window.
|
||||
@@ -129,6 +138,7 @@ impl Element for Anchored {
|
||||
self.anchor_corner,
|
||||
size,
|
||||
bounds,
|
||||
self.offset,
|
||||
);
|
||||
|
||||
let limits = Bounds {
|
||||
@@ -245,18 +255,22 @@ impl AnchoredPositionMode {
|
||||
anchor_corner: Corner,
|
||||
size: Size<Pixels>,
|
||||
bounds: Bounds<Pixels>,
|
||||
offset: Option<Point<Pixels>>,
|
||||
) -> (Point<Pixels>, Bounds<Pixels>) {
|
||||
let offset = offset.unwrap_or_default();
|
||||
|
||||
match self {
|
||||
AnchoredPositionMode::Window => {
|
||||
let anchor_position = anchor_position.unwrap_or(bounds.origin);
|
||||
let bounds = Bounds::from_corner_and_size(anchor_corner, anchor_position, size);
|
||||
let bounds =
|
||||
Bounds::from_corner_and_size(anchor_corner, anchor_position + offset, size);
|
||||
(anchor_position, bounds)
|
||||
}
|
||||
AnchoredPositionMode::Local => {
|
||||
let anchor_position = anchor_position.unwrap_or_default();
|
||||
let bounds = Bounds::from_corner_and_size(
|
||||
anchor_corner,
|
||||
bounds.origin + anchor_position,
|
||||
bounds.origin + anchor_position + offset,
|
||||
size,
|
||||
);
|
||||
(anchor_position, bounds)
|
||||
|
||||
@@ -175,6 +175,7 @@ pub(crate) trait Platform: 'static {
|
||||
options: PathPromptOptions,
|
||||
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
|
||||
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>>;
|
||||
fn can_select_mixed_files_and_dirs(&self) -> bool;
|
||||
fn reveal_path(&self, path: &Path);
|
||||
fn open_with_system(&self, path: &Path);
|
||||
|
||||
|
||||
@@ -372,6 +372,11 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
done_rx
|
||||
}
|
||||
|
||||
fn can_select_mixed_files_and_dirs(&self) -> bool {
|
||||
// org.freedesktop.portal.FileChooser only supports "pick files" and "pick directories".
|
||||
false
|
||||
}
|
||||
|
||||
fn reveal_path(&self, path: &Path) {
|
||||
self.reveal_path(path.to_owned());
|
||||
}
|
||||
|
||||
@@ -759,6 +759,10 @@ impl Platform for MacPlatform {
|
||||
done_rx
|
||||
}
|
||||
|
||||
fn can_select_mixed_files_and_dirs(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn reveal_path(&self, path: &Path) {
|
||||
unsafe {
|
||||
let path = path.to_path_buf();
|
||||
|
||||
@@ -299,6 +299,10 @@ impl Platform for TestPlatform {
|
||||
rx
|
||||
}
|
||||
|
||||
fn can_select_mixed_files_and_dirs(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn reveal_path(&self, _path: &std::path::Path) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -407,6 +407,11 @@ impl Platform for WindowsPlatform {
|
||||
rx
|
||||
}
|
||||
|
||||
fn can_select_mixed_files_and_dirs(&self) -> bool {
|
||||
// The FOS_PICKFOLDERS flag toggles between "only files" and "only folders".
|
||||
false
|
||||
}
|
||||
|
||||
fn reveal_path(&self, path: &Path) {
|
||||
let Ok(file_full_path) = path.canonicalize() else {
|
||||
log::error!("unable to parse file path");
|
||||
|
||||
@@ -381,7 +381,7 @@ impl TitleBar {
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon_size(IconSize::Small)
|
||||
.toggle_state(is_muted)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Error))
|
||||
.on_click(move |_, cx| {
|
||||
toggle_mute(&Default::default(), cx);
|
||||
})
|
||||
@@ -398,7 +398,7 @@ impl TitleBar {
|
||||
},
|
||||
)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Error))
|
||||
.icon_size(IconSize::Small)
|
||||
.toggle_state(is_deafened)
|
||||
.tooltip(move |cx| {
|
||||
|
||||
@@ -474,9 +474,9 @@ impl ComponentPreview for Button {
|
||||
.style(ButtonStyle::Tinted(TintColor::Accent)),
|
||||
),
|
||||
single_example(
|
||||
"Negative",
|
||||
Button::new("tinted_negative", "Negative")
|
||||
.style(ButtonStyle::Tinted(TintColor::Negative)),
|
||||
"Error",
|
||||
Button::new("tinted_negative", "Error")
|
||||
.style(ButtonStyle::Tinted(TintColor::Error)),
|
||||
),
|
||||
single_example(
|
||||
"Warning",
|
||||
@@ -484,9 +484,9 @@ impl ComponentPreview for Button {
|
||||
.style(ButtonStyle::Tinted(TintColor::Warning)),
|
||||
),
|
||||
single_example(
|
||||
"Positive",
|
||||
Button::new("tinted_positive", "Positive")
|
||||
.style(ButtonStyle::Tinted(TintColor::Positive)),
|
||||
"Success",
|
||||
Button::new("tinted_positive", "Success")
|
||||
.style(ButtonStyle::Tinted(TintColor::Success)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -527,8 +527,8 @@ impl ComponentPreview for Button {
|
||||
),
|
||||
single_example(
|
||||
"Tinted Icons",
|
||||
Button::new("icon_color", "Delete")
|
||||
.style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
Button::new("icon_color", "Error")
|
||||
.style(ButtonStyle::Tinted(TintColor::Error))
|
||||
.color(Color::Error)
|
||||
.icon_color(Color::Error)
|
||||
.icon(IconName::Trash)
|
||||
|
||||
@@ -49,9 +49,9 @@ pub enum IconPosition {
|
||||
pub enum TintColor {
|
||||
#[default]
|
||||
Accent,
|
||||
Negative,
|
||||
Error,
|
||||
Warning,
|
||||
Positive,
|
||||
Success,
|
||||
}
|
||||
|
||||
impl TintColor {
|
||||
@@ -63,7 +63,7 @@ impl TintColor {
|
||||
label_color: cx.theme().colors().text,
|
||||
icon_color: cx.theme().colors().text,
|
||||
},
|
||||
TintColor::Negative => ButtonLikeStyles {
|
||||
TintColor::Error => ButtonLikeStyles {
|
||||
background: cx.theme().status().error_background,
|
||||
border_color: cx.theme().status().error_border,
|
||||
label_color: cx.theme().colors().text,
|
||||
@@ -75,7 +75,7 @@ impl TintColor {
|
||||
label_color: cx.theme().colors().text,
|
||||
icon_color: cx.theme().colors().text,
|
||||
},
|
||||
TintColor::Positive => ButtonLikeStyles {
|
||||
TintColor::Success => ButtonLikeStyles {
|
||||
background: cx.theme().status().success_background,
|
||||
border_color: cx.theme().status().success_border,
|
||||
label_color: cx.theme().colors().text,
|
||||
@@ -89,9 +89,9 @@ impl From<TintColor> for Color {
|
||||
fn from(tint: TintColor) -> Self {
|
||||
match tint {
|
||||
TintColor::Accent => Color::Accent,
|
||||
TintColor::Negative => Color::Error,
|
||||
TintColor::Error => Color::Error,
|
||||
TintColor::Warning => Color::Warning,
|
||||
TintColor::Positive => Color::Success,
|
||||
TintColor::Success => Color::Success,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,13 +254,14 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
|
||||
let mut menu_layout_id = None;
|
||||
|
||||
let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| {
|
||||
let offset = self.resolved_offset(cx);
|
||||
let mut anchored = anchored()
|
||||
.snap_to_window_with_margin(px(8.))
|
||||
.anchor(self.anchor);
|
||||
.anchor(self.anchor)
|
||||
.offset(offset);
|
||||
if let Some(child_bounds) = element_state.child_bounds {
|
||||
anchored = anchored.position(
|
||||
child_bounds.corner(self.resolved_attach()) + self.resolved_offset(cx),
|
||||
);
|
||||
anchored =
|
||||
anchored.position(child_bounds.corner(self.resolved_attach()) + offset);
|
||||
}
|
||||
let mut element = deferred(anchored.child(div().occlude().child(menu.clone())))
|
||||
.with_priority(1)
|
||||
|
||||
@@ -449,6 +449,10 @@ where
|
||||
);
|
||||
}
|
||||
|
||||
pub fn log_err<E: std::fmt::Debug>(error: &E) {
|
||||
log_error_with_caller(*Location::caller(), error, log::Level::Warn);
|
||||
}
|
||||
|
||||
pub trait TryFutureExt {
|
||||
fn log_err(self) -> LogErrorFuture<Self>
|
||||
where
|
||||
|
||||
@@ -5,7 +5,6 @@ use gpui::{
|
||||
EventEmitter, Global, PromptLevel, Render, ScrollHandle, Task, View, ViewContext,
|
||||
VisualContext, WindowContext,
|
||||
};
|
||||
use language::DiagnosticSeverity;
|
||||
|
||||
use std::{any::TypeId, ops::DerefMut, time::Duration};
|
||||
use ui::{prelude::*, Tooltip};
|
||||
@@ -266,89 +265,57 @@ impl Render for LanguageServerPrompt {
|
||||
return div().id("language_server_prompt_notification");
|
||||
};
|
||||
|
||||
h_flex()
|
||||
let (icon, color) = match request.level {
|
||||
PromptLevel::Info => (IconName::Info, Color::Accent),
|
||||
PromptLevel::Warning => (IconName::Warning, Color::Warning),
|
||||
PromptLevel::Critical => (IconName::XCircle, Color::Error),
|
||||
};
|
||||
|
||||
div()
|
||||
.id("language_server_prompt_notification")
|
||||
.group("language_server_prompt_notification")
|
||||
.occlude()
|
||||
.elevation_3(cx)
|
||||
.items_start()
|
||||
.justify_between()
|
||||
.p_2()
|
||||
.gap_2()
|
||||
.w_full()
|
||||
.max_h(vh(0.8, cx))
|
||||
.elevation_3(cx)
|
||||
.overflow_y_scroll()
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.group("")
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.p_3()
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_grow()
|
||||
.children(
|
||||
match request.level {
|
||||
PromptLevel::Info => None,
|
||||
PromptLevel::Warning => {
|
||||
Some(DiagnosticSeverity::WARNING)
|
||||
}
|
||||
PromptLevel::Critical => {
|
||||
Some(DiagnosticSeverity::ERROR)
|
||||
}
|
||||
}
|
||||
.map(|severity| {
|
||||
svg()
|
||||
.size(cx.text_style().font_size)
|
||||
.flex_none()
|
||||
.mr_1()
|
||||
.mt(px(-2.0))
|
||||
.map(|icon| {
|
||||
if severity == DiagnosticSeverity::ERROR {
|
||||
icon.path(IconName::Warning.path())
|
||||
.text_color(Color::Error.color(cx))
|
||||
} else {
|
||||
icon.path(IconName::Warning.path())
|
||||
.text_color(Color::Warning.color(cx))
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Label::new(request.lsp_name.clone())
|
||||
.size(LabelSize::Default),
|
||||
),
|
||||
.gap_2()
|
||||
.child(Icon::new(icon).color(color))
|
||||
.child(Label::new(request.lsp_name.clone())),
|
||||
)
|
||||
.child(
|
||||
ui::IconButton::new("close", ui::IconName::Close)
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(gpui::DismissEvent))),
|
||||
h_flex()
|
||||
.child(
|
||||
IconButton::new("copy", IconName::Copy)
|
||||
.on_click({
|
||||
let message = request.message.clone();
|
||||
move |_, cx| {
|
||||
cx.write_to_clipboard(
|
||||
ClipboardItem::new_string(message.clone()),
|
||||
)
|
||||
}
|
||||
})
|
||||
.tooltip(|cx| Tooltip::text("Copy Description", cx)),
|
||||
)
|
||||
.child(IconButton::new("close", IconName::Close).on_click(
|
||||
cx.listener(|_, _, cx| cx.emit(gpui::DismissEvent)),
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.child(
|
||||
h_flex().absolute().right_0().rounded_md().child(
|
||||
ui::IconButton::new("copy", ui::IconName::Copy)
|
||||
.on_click({
|
||||
let message = request.message.clone();
|
||||
move |_, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||
message.clone(),
|
||||
))
|
||||
}
|
||||
})
|
||||
.tooltip(|cx| Tooltip::text("Copy", cx))
|
||||
.visible_on_hover(""),
|
||||
),
|
||||
)
|
||||
.child(Label::new(request.message.to_string()).size(LabelSize::Small)),
|
||||
)
|
||||
.child(Label::new(request.message.to_string()).size(LabelSize::Small))
|
||||
.children(request.actions.iter().enumerate().map(|(ix, action)| {
|
||||
let this_handle = cx.view().clone();
|
||||
ui::Button::new(ix, action.title.clone())
|
||||
Button::new(ix, action.title.clone())
|
||||
.size(ButtonSize::Large)
|
||||
.on_click(move |_, cx| {
|
||||
let this_handle = this_handle.clone();
|
||||
@@ -444,12 +411,10 @@ impl EventEmitter<DismissEvent> for ErrorMessagePrompt {}
|
||||
|
||||
pub mod simple_message_notification {
|
||||
use gpui::{
|
||||
div, DismissEvent, EventEmitter, InteractiveElement, ParentElement, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, ViewContext,
|
||||
div, DismissEvent, EventEmitter, ParentElement, Render, SharedString, Styled, ViewContext,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use ui::prelude::*;
|
||||
use ui::{h_flex, v_flex, Button, Icon, IconName, Label, StyledExt};
|
||||
|
||||
pub struct MessageNotification {
|
||||
message: SharedString,
|
||||
@@ -515,36 +480,43 @@ pub mod simple_message_notification {
|
||||
impl Render for MessageNotification {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.p_3()
|
||||
.gap_2()
|
||||
.elevation_3(cx)
|
||||
.p_4()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_4()
|
||||
.justify_between()
|
||||
.child(div().max_w_80().child(Label::new(self.message.clone())))
|
||||
.child(
|
||||
div()
|
||||
.id("cancel")
|
||||
.child(Icon::new(IconName::Close))
|
||||
.cursor_pointer()
|
||||
IconButton::new("close", IconName::Close)
|
||||
.on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_3()
|
||||
.gap_2()
|
||||
.children(self.click_message.iter().map(|message| {
|
||||
Button::new(message.clone(), message.clone()).on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
Button::new(message.clone(), message.clone())
|
||||
.label_size(LabelSize::Small)
|
||||
.icon(IconName::Check)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Success)
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
if let Some(on_click) = this.on_click.as_ref() {
|
||||
(on_click)(cx)
|
||||
};
|
||||
this.dismiss(cx)
|
||||
},
|
||||
))
|
||||
}))
|
||||
}))
|
||||
.children(self.secondary_click_message.iter().map(|message| {
|
||||
Button::new(message.clone(), message.clone())
|
||||
.style(ButtonStyle::Filled)
|
||||
.label_size(LabelSize::Small)
|
||||
.icon(IconName::Close)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Error)
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
if let Some(on_click) = this.secondary_on_click.as_ref() {
|
||||
(on_click)(cx)
|
||||
|
||||
@@ -34,10 +34,10 @@ use gpui::{
|
||||
action_as, actions, canvas, impl_action_as, impl_actions, point, relative, size,
|
||||
transparent_black, Action, AnyView, AnyWeakView, AppContext, AsyncAppContext,
|
||||
AsyncWindowContext, Bounds, CursorStyle, Decorations, DragMoveEvent, Entity as _, EntityId,
|
||||
EventEmitter, Flatten, FocusHandle, FocusableView, Global, Hsla, KeyContext, Keystroke,
|
||||
ManagedView, Model, ModelContext, MouseButton, PathPromptOptions, Point, PromptLevel, Render,
|
||||
ResizeEdge, Size, Stateful, Subscription, Task, Tiling, View, WeakView, WindowBounds,
|
||||
WindowHandle, WindowId, WindowOptions,
|
||||
EventEmitter, FocusHandle, FocusableView, Global, Hsla, KeyContext, Keystroke, ManagedView,
|
||||
Model, ModelContext, MouseButton, PathPromptOptions, Point, PromptLevel, Render, ResizeEdge,
|
||||
Size, Stateful, Subscription, Task, Tiling, View, WeakView, WindowBounds, WindowHandle,
|
||||
WindowId, WindowOptions,
|
||||
};
|
||||
pub use item::{
|
||||
FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
|
||||
@@ -145,6 +145,7 @@ actions!(
|
||||
NewTerminal,
|
||||
NewWindow,
|
||||
Open,
|
||||
OpenFiles,
|
||||
OpenInTerminal,
|
||||
ReloadActiveItem,
|
||||
SaveAs,
|
||||
@@ -332,6 +333,42 @@ pub fn init_settings(cx: &mut AppContext) {
|
||||
TabBarSettings::register(cx);
|
||||
}
|
||||
|
||||
fn prompt_and_open_paths(
|
||||
app_state: Arc<AppState>,
|
||||
options: PathPromptOptions,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
let paths = cx.prompt_for_paths(options);
|
||||
cx.spawn(|cx| async move {
|
||||
match paths.await.anyhow().and_then(|res| res) {
|
||||
Ok(Some(paths)) => {
|
||||
cx.update(|cx| {
|
||||
open_paths(&paths, app_state, OpenOptions::default(), cx).detach_and_log_err(cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(err) => {
|
||||
util::log_err(&err);
|
||||
cx.update(|cx| {
|
||||
if let Some(workspace_window) = cx
|
||||
.active_window()
|
||||
.and_then(|window| window.downcast::<Workspace>())
|
||||
{
|
||||
workspace_window
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.show_portal_error(err.to_string(), cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
init_settings(cx);
|
||||
notifications::init(cx);
|
||||
@@ -343,41 +380,33 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
cx.on_action({
|
||||
let app_state = Arc::downgrade(&app_state);
|
||||
move |_: &Open, cx: &mut AppContext| {
|
||||
let paths = cx.prompt_for_paths(PathPromptOptions {
|
||||
files: true,
|
||||
directories: true,
|
||||
multiple: true,
|
||||
});
|
||||
|
||||
if let Some(app_state) = app_state.upgrade() {
|
||||
cx.spawn(move |cx| async move {
|
||||
match Flatten::flatten(paths.await.map_err(|e| e.into())) {
|
||||
Ok(Some(paths)) => {
|
||||
cx.update(|cx| {
|
||||
open_paths(&paths, app_state, OpenOptions::default(), cx)
|
||||
.detach_and_log_err(cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(err) => {
|
||||
cx.update(|cx| {
|
||||
if let Some(workspace_window) = cx
|
||||
.active_window()
|
||||
.and_then(|window| window.downcast::<Workspace>())
|
||||
{
|
||||
workspace_window
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.show_portal_error(err.to_string(), cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
prompt_and_open_paths(
|
||||
app_state,
|
||||
PathPromptOptions {
|
||||
files: true,
|
||||
directories: true,
|
||||
multiple: true,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
cx.on_action({
|
||||
let app_state = Arc::downgrade(&app_state);
|
||||
move |_: &OpenFiles, cx: &mut AppContext| {
|
||||
let directories = cx.can_select_mixed_files_and_dirs();
|
||||
if let Some(app_state) = app_state.upgrade() {
|
||||
prompt_and_open_paths(
|
||||
app_state,
|
||||
PathPromptOptions {
|
||||
files: true,
|
||||
directories,
|
||||
multiple: true,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition = "2021"
|
||||
name = "zed"
|
||||
version = "0.169.0"
|
||||
version = "0.170.0"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Zed Team <hi@zed.dev>"]
|
||||
|
||||
@@ -66,7 +66,7 @@ git checkout -q ${prev_minor_branch_name}
|
||||
git clean -q -dff
|
||||
stable_tag_name="v$(script/get-crate-version zed)"
|
||||
if git show-ref --quiet refs/tags/${stable_tag_name}; then
|
||||
echo "tag ${preview_tag_name} already exists"
|
||||
echo "tag ${stable_tag_name} already exists"
|
||||
exit 1
|
||||
fi
|
||||
old_prev_minor_sha=$(git rev-parse HEAD)
|
||||
@@ -77,7 +77,6 @@ git tag ${stable_tag_name}
|
||||
echo "Creating new preview branch ${minor_branch_name}..."
|
||||
git checkout -q main
|
||||
git checkout -q -b ${minor_branch_name}
|
||||
git branch --set-upstream-to=origin/${minor_branch_name} ${minor_branch_name}
|
||||
echo -n preview > crates/zed/RELEASE_CHANNEL
|
||||
git commit -q --all --message "${minor_branch_name} preview"
|
||||
git tag ${preview_tag_name}
|
||||
|
||||
Reference in New Issue
Block a user