Merge branch 'main' into joseph/z-226-add-terminal-popup-menu

This commit is contained in:
Petros Amoiridis 2023-03-11 16:42:42 +02:00
commit bccc34c61a
No known key found for this signature in database
159 changed files with 5447 additions and 1711 deletions

View file

@ -2,7 +2,7 @@ pub mod languages;
pub mod menus;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
use anyhow::{anyhow, Context, Result};
use anyhow::Context;
use assets::Assets;
use breadcrumbs::Breadcrumbs;
pub use client;
@ -20,7 +20,7 @@ use gpui::{
geometry::vector::vec2f,
impl_actions,
platform::{WindowBounds, WindowOptions},
AssetSource, AsyncAppContext, Platform, PromptLevel, TitlebarOptions, ViewContext, WindowKind,
AssetSource, Platform, PromptLevel, TitlebarOptions, ViewContext, WindowKind,
};
use language::Rope;
pub use lsp;
@ -66,7 +66,6 @@ actions!(
IncreaseBufferFontSize,
DecreaseBufferFontSize,
ResetBufferFontSize,
InstallCommandLineInterface,
ResetDatabase,
]
);
@ -142,9 +141,13 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
cx.refresh_windows();
});
});
cx.add_global_action(move |_: &InstallCommandLineInterface, cx| {
cx.spawn(|cx| async move { install_cli(&cx).await.context("error creating CLI symlink") })
.detach_and_log_err(cx);
cx.add_global_action(move |_: &install_cli::Install, cx| {
cx.spawn(|cx| async move {
install_cli::install_cli(&cx)
.await
.context("error creating CLI symlink")
})
.detach_and_log_err(cx);
});
cx.add_action({
let app_state = app_state.clone();
@ -169,9 +172,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
});
cx.add_action({
let app_state = app_state.clone();
move |workspace: &mut Workspace, _: &OpenLicenses, cx: &mut ViewContext<Workspace>| {
move |_: &mut Workspace, _: &OpenLicenses, cx: &mut ViewContext<Workspace>| {
open_bundled_file(
workspace,
app_state.clone(),
"licenses.md",
"Open Source License Attribution",
@ -194,9 +196,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
});
cx.add_action({
let app_state = app_state.clone();
move |workspace: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext<Workspace>| {
move |_: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext<Workspace>| {
open_bundled_file(
workspace,
app_state.clone(),
"keymaps/default.json",
"Default Key Bindings",
@ -207,11 +208,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
});
cx.add_action({
let app_state = app_state.clone();
move |workspace: &mut Workspace,
_: &OpenDefaultSettings,
cx: &mut ViewContext<Workspace>| {
move |_: &mut Workspace, _: &OpenDefaultSettings, cx: &mut ViewContext<Workspace>| {
open_bundled_file(
workspace,
app_state.clone(),
"settings/default.json",
"Default Settings",
@ -220,32 +218,41 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
);
}
});
cx.add_action(
|workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| {
cx.add_action({
let app_state = app_state.clone();
move |_: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| {
let app_state = app_state.clone();
let markdown = app_state.languages.language_for_name("JSON");
let content = to_string_pretty(&cx.debug_elements()).unwrap();
let project = workspace.project().clone();
let json_language = project
.read(cx)
.languages()
.language_for_name("JSON")
.unwrap();
if project.read(cx).is_remote() {
cx.propagate_action();
} else if let Some(buffer) = project
.update(cx, |project, cx| {
project.create_buffer(&content, Some(json_language), cx)
})
.log_err()
{
workspace.add_item(
Box::new(
cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
),
cx,
);
}
},
);
cx.spawn(|workspace, mut cx| async move {
let markdown = markdown.await.log_err();
workspace
.update(&mut cx, |workspace, cx| {
workspace.with_local_workspace(&app_state, cx, move |workspace, cx| {
let project = workspace.project().clone();
let buffer = project
.update(cx, |project, cx| {
project.create_buffer(&content, markdown, cx)
})
.expect("creating buffers on a local workspace always succeeds");
let buffer = cx.add_model(|cx| {
MultiBuffer::singleton(buffer, cx)
.with_title("Debug Elements".into())
});
workspace.add_item(
Box::new(cx.add_view(|cx| {
Editor::for_multibuffer(buffer, Some(project.clone()), cx)
})),
cx,
);
})
})
.await;
})
.detach();
}
});
cx.add_action(
|workspace: &mut Workspace,
_: &project_panel::ToggleFocus,
@ -253,7 +260,6 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
workspace.toggle_sidebar_item_focus(SidebarSide::Left, 0, cx);
},
);
activity_indicator::init(cx);
call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
settings::KeymapFileContent::load_defaults(cx);
@ -337,18 +343,19 @@ pub fn initialize_workspace(
cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace.project(), cx));
let activity_indicator =
activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
let active_buffer_language = cx.add_view(|_| language_selector::ActiveBufferLanguage::new());
let feedback_button =
cx.add_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton {});
cx.add_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new());
let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
workspace.status_bar().update(cx, |status_bar, cx| {
status_bar.add_left_item(diagnostic_summary, cx);
status_bar.add_left_item(activity_indicator, cx);
// TODO: Remove this when things are done
if **cx.default_global::<StaffMode>() {
status_bar.add_right_item(toggle_terminal, cx);
}
status_bar.add_right_item(cursor_position, cx);
status_bar.add_right_item(feedback_button, cx);
status_bar.add_right_item(active_buffer_language, cx);
status_bar.add_right_item(cursor_position, cx);
});
auto_update::notify_of_any_new_update(cx.weak_handle(), cx);
@ -485,54 +492,6 @@ fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
);
}
async fn install_cli(cx: &AsyncAppContext) -> Result<()> {
let cli_path = cx.platform().path_for_auxiliary_executable("cli")?;
let link_path = Path::new("/usr/local/bin/zed");
let bin_dir_path = link_path.parent().unwrap();
// Don't re-create symlink if it points to the same CLI binary.
if smol::fs::read_link(link_path).await.ok().as_ref() == Some(&cli_path) {
return Ok(());
}
// If the symlink is not there or is outdated, first try replacing it
// without escalating.
smol::fs::remove_file(link_path).await.log_err();
if smol::fs::unix::symlink(&cli_path, link_path)
.await
.log_err()
.is_some()
{
return Ok(());
}
// The symlink could not be created, so use osascript with admin privileges
// to create it.
let status = smol::process::Command::new("osascript")
.args([
"-e",
&format!(
"do shell script \" \
mkdir -p \'{}\' && \
ln -sf \'{}\' \'{}\' \
\" with administrator privileges",
bin_dir_path.to_string_lossy(),
cli_path.to_string_lossy(),
link_path.to_string_lossy(),
),
])
.stdout(smol::process::Stdio::inherit())
.stderr(smol::process::Stdio::inherit())
.output()
.await?
.status;
if status.success() {
Ok(())
} else {
Err(anyhow!("error running osascript"))
}
}
fn open_config_file(
path: &'static Path,
app_state: Arc<AppState>,
@ -626,8 +585,13 @@ fn open_telemetry_log_file(
workspace.with_local_workspace(&app_state.clone(), cx, move |_, cx| {
cx.spawn_weak(|workspace, mut cx| async move {
let workspace = workspace.upgrade(&cx)?;
let path = app_state.client.telemetry_log_file_path()?;
let log = app_state.fs.load(&path).await.log_err()?;
async fn fetch_log_string(app_state: &Arc<AppState>) -> Option<String> {
let path = app_state.client.telemetry_log_file_path()?;
app_state.fs.load(&path).await.log_err()
}
let log = fetch_log_string(&app_state).await.unwrap_or_else(|| "// No data has been collected yet".to_string());
const MAX_TELEMETRY_LOG_LEN: usize = 5 * 1024 * 1024;
let mut start_offset = log.len().saturating_sub(MAX_TELEMETRY_LOG_LEN);
@ -635,6 +599,7 @@ fn open_telemetry_log_file(
start_offset += newline_offset + 1;
}
let log_suffix = &log[start_offset..];
let json = app_state.languages.language_for_name("JSON").await.log_err();
workspace.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
@ -642,7 +607,7 @@ fn open_telemetry_log_file(
.update(cx, |project, cx| project.create_buffer("", None, cx))
.expect("creating buffers on a local workspace always succeeds");
buffer.update(cx, |buffer, cx| {
buffer.set_language(app_state.languages.language_for_name("JSON"), cx);
buffer.set_language(json, cx);
buffer.edit(
[(
0..0,
@ -675,35 +640,42 @@ fn open_telemetry_log_file(
}
fn open_bundled_file(
workspace: &mut Workspace,
app_state: Arc<AppState>,
asset_path: &'static str,
title: &'static str,
language: &'static str,
cx: &mut ViewContext<Workspace>,
) {
workspace
.with_local_workspace(&app_state, cx, |workspace, cx| {
let project = workspace.project().clone();
let buffer = project.update(cx, |project, cx| {
let text = Assets::get(asset_path)
.map(|f| f.data)
.unwrap_or_else(|| Cow::Borrowed(b"File not found"));
let text = str::from_utf8(text.as_ref()).unwrap();
project
.create_buffer(text, project.languages().language_for_name(language), cx)
.expect("creating buffers on a local workspace always succeeds")
});
let buffer =
cx.add_model(|cx| MultiBuffer::singleton(buffer, cx).with_title(title.into()));
workspace.add_item(
Box::new(
cx.add_view(|cx| Editor::for_multibuffer(buffer, Some(project.clone()), cx)),
),
cx,
);
})
.detach();
let language = app_state.languages.language_for_name(language);
cx.spawn(|workspace, mut cx| async move {
let language = language.await.log_err();
workspace
.update(&mut cx, |workspace, cx| {
workspace.with_local_workspace(&app_state, cx, |workspace, cx| {
let project = workspace.project();
let buffer = project.update(cx, |project, cx| {
let text = Assets::get(asset_path)
.map(|f| f.data)
.unwrap_or_else(|| Cow::Borrowed(b"File not found"));
let text = str::from_utf8(text.as_ref()).unwrap();
project
.create_buffer(text, language, cx)
.expect("creating buffers on a local workspace always succeeds")
});
let buffer = cx.add_model(|cx| {
MultiBuffer::singleton(buffer, cx).with_title(title.into())
});
workspace.add_item(
Box::new(cx.add_view(|cx| {
Editor::for_multibuffer(buffer, Some(project.clone()), cx)
})),
cx,
);
})
})
.await;
})
.detach();
}
fn schema_file_match(path: &Path) -> &Path {
@ -753,6 +725,10 @@ mod tests {
"ca": null,
"cb": null,
},
"d": {
"da": null,
"db": null,
},
}),
)
.await;
@ -761,13 +737,14 @@ mod tests {
open_paths(
&[PathBuf::from("/root/a"), PathBuf::from("/root/b")],
&app_state,
None,
cx,
)
})
.await;
assert_eq!(cx.window_ids().len(), 1);
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, cx))
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
.await;
assert_eq!(cx.window_ids().len(), 1);
let workspace_1 = cx.root_view::<Workspace>(cx.window_ids()[0]).unwrap();
@ -781,11 +758,37 @@ mod tests {
open_paths(
&[PathBuf::from("/root/b"), PathBuf::from("/root/c")],
&app_state,
None,
cx,
)
})
.await;
assert_eq!(cx.window_ids().len(), 2);
// Replace existing windows
let window_id = cx.window_ids()[0];
cx.update(|cx| {
open_paths(
&[PathBuf::from("/root/c"), PathBuf::from("/root/d")],
&app_state,
Some(window_id),
cx,
)
})
.await;
assert_eq!(cx.window_ids().len(), 2);
let workspace_1 = cx.root_view::<Workspace>(window_id).unwrap();
workspace_1.read_with(cx, |workspace, cx| {
assert_eq!(
workspace
.worktrees(cx)
.map(|w| w.read(cx).abs_path())
.collect::<Vec<_>>(),
&[Path::new("/root/c").into(), Path::new("/root/d").into()]
);
assert!(workspace.left_sidebar().read(cx).is_open());
assert!(workspace.active_pane().is_focused(cx));
});
}
#[gpui::test]
@ -797,7 +800,7 @@ mod tests {
.insert_tree("/root", json!({"a": "hey"}))
.await;
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, cx))
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
.await;
assert_eq!(cx.window_ids().len(), 1);
@ -835,7 +838,7 @@ mod tests {
assert!(!cx.is_window_edited(workspace.window_id()));
// Opening the buffer again doesn't impact the window's edited state.
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, cx))
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
.await;
let editor = workspace.read_with(cx, |workspace, cx| {
workspace
@ -865,7 +868,8 @@ mod tests {
#[gpui::test]
async fn test_new_empty_workspace(cx: &mut TestAppContext) {
let app_state = init(cx);
cx.update(|cx| open_new(&app_state, cx)).await;
cx.update(|cx| open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile)))
.await;
let window_id = *cx.window_ids().first().unwrap();
let workspace = cx.root_view::<Workspace>(window_id).unwrap();
@ -910,9 +914,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let entries = cx.read(|cx| workspace.file_project_paths(cx));
let file1 = entries[0].clone();
@ -1031,9 +1033,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/dir1".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
// Open a file within an existing worktree.
cx.update(|cx| {
@ -1192,9 +1192,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
// Open a file within an existing worktree.
cx.update(|cx| {
@ -1236,9 +1234,7 @@ mod tests {
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(rust_lang()));
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
// Create a new untitled buffer
@ -1327,9 +1323,7 @@ mod tests {
let project = Project::test(app_state.fs.clone(), [], cx).await;
project.update(cx, |project, _| project.languages().add(rust_lang()));
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
// Create a new untitled buffer
cx.dispatch_action(window_id, NewFile);
@ -1382,9 +1376,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let entries = cx.read(|cx| workspace.file_project_paths(cx));
let file1 = entries[0].clone();
@ -1458,15 +1450,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| {
Workspace::new(
Default::default(),
0,
project.clone(),
|_, _| unimplemented!(),
cx,
)
});
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let entries = cx.read(|cx| workspace.file_project_paths(cx));
let file1 = entries[0].clone();
@ -1730,15 +1714,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| {
Workspace::new(
Default::default(),
0,
project.clone(),
|_, _| unimplemented!(),
cx,
)
});
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let entries = cx.read(|cx| workspace.file_project_paths(cx));