Merge branch 'main' into panels

This commit is contained in:
Antonio Scandurra 2023-05-22 13:52:50 +02:00
commit 146809eef0
183 changed files with 10202 additions and 5720 deletions

View file

@ -15,7 +15,7 @@ use anyhow::anyhow;
use feedback::{
feedback_info_text::FeedbackInfoText, submit_feedback_button::SubmitFeedbackButton,
};
use futures::StreamExt;
use futures::{channel::mpsc, StreamExt};
use gpui::{
actions,
anyhow::{self, Result},
@ -30,15 +30,16 @@ use project_panel::ProjectPanel;
use search::{BufferSearchBar, ProjectSearchBar};
use serde::Deserialize;
use serde_json::to_string_pretty;
use settings::{Settings, DEFAULT_SETTINGS_ASSET_PATH};
use settings::{KeymapFileContent, SettingsStore, DEFAULT_SETTINGS_ASSET_PATH};
use std::{borrow::Cow, str, sync::Arc};
use terminal_view::terminal_panel::{self, TerminalPanel};
use util::{channel::ReleaseChannel, paths, ResultExt};
use uuid::Uuid;
use welcome::BaseKeymap;
pub use workspace;
use workspace::{
create_and_open_local_file, dock::PanelHandle, open_new, AppState, NewFile, NewWindow,
Workspace,
Workspace, WorkspaceSettings,
};
#[derive(Deserialize, Clone, PartialEq)]
@ -73,8 +74,6 @@ actions!(
]
);
const MIN_FONT_SIZE: f32 = 6.0;
pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
cx.add_action(about);
cx.add_global_action(|_: &Hide, cx: &mut gpui::AppContext| {
@ -118,30 +117,12 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
cx.add_global_action(quit);
cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url));
cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| {
cx.update_global::<Settings, _, _>(|settings, cx| {
settings.buffer_font_size = (settings.buffer_font_size + 1.0).max(MIN_FONT_SIZE);
if let Some(terminal_font_size) = settings.terminal_overrides.font_size.as_mut() {
*terminal_font_size = (*terminal_font_size + 1.0).max(MIN_FONT_SIZE);
}
cx.refresh_windows();
});
theme::adjust_font_size(cx, |size| *size += 1.0)
});
cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| {
cx.update_global::<Settings, _, _>(|settings, cx| {
settings.buffer_font_size = (settings.buffer_font_size - 1.0).max(MIN_FONT_SIZE);
if let Some(terminal_font_size) = settings.terminal_overrides.font_size.as_mut() {
*terminal_font_size = (*terminal_font_size - 1.0).max(MIN_FONT_SIZE);
}
cx.refresh_windows();
});
});
cx.add_global_action(move |_: &ResetBufferFontSize, cx| {
cx.update_global::<Settings, _, _>(|settings, cx| {
settings.buffer_font_size = settings.default_buffer_font_size;
settings.terminal_overrides.font_size = settings.terminal_defaults.font_size;
cx.refresh_windows();
});
theme::adjust_font_size(cx, |size| *size -= 1.0)
});
cx.add_global_action(move |_: &ResetBufferFontSize, cx| theme::reset_font_size(cx));
cx.add_global_action(move |_: &install_cli::Install, cx| {
cx.spawn(|cx| async move {
install_cli::install_cli(&cx)
@ -275,10 +256,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
}
}
});
activity_indicator::init(cx);
lsp_log::init(cx);
call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
settings::KeymapFileContent::load_defaults(cx);
load_default_keymap(cx);
}
pub fn initialize_workspace(
@ -323,7 +301,8 @@ pub fn initialize_workspace(
cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx));
workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx));
let copilot =
cx.add_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
let diagnostic_summary =
cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
let activity_indicator = activity_indicator::ActivityIndicator::new(
@ -408,7 +387,7 @@ pub fn build_window_options(
}
fn quit(_: &Quit, cx: &mut gpui::AppContext) {
let should_confirm = cx.global::<Settings>().confirm_quit;
let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
cx.spawn(|mut cx| async move {
let mut workspaces = cx
.window_ids()
@ -519,6 +498,51 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
.detach();
}
pub fn load_default_keymap(cx: &mut AppContext) {
for path in ["keymaps/default.json", "keymaps/vim.json"] {
KeymapFileContent::load_asset(path, cx).unwrap();
}
if let Some(asset_path) = settings::get::<BaseKeymap>(cx).asset_path() {
KeymapFileContent::load_asset(asset_path, cx).unwrap();
}
}
pub fn handle_keymap_file_changes(
mut user_keymap_file_rx: mpsc::UnboundedReceiver<String>,
cx: &mut AppContext,
) {
cx.spawn(move |mut cx| async move {
let mut settings_subscription = None;
while let Some(user_keymap_content) = user_keymap_file_rx.next().await {
if let Ok(keymap_content) = KeymapFileContent::parse(&user_keymap_content) {
cx.update(|cx| {
cx.clear_bindings();
load_default_keymap(cx);
keymap_content.clone().add_to_cx(cx).log_err();
});
let mut old_base_keymap = cx.read(|cx| *settings::get::<BaseKeymap>(cx));
drop(settings_subscription);
settings_subscription = Some(cx.update(|cx| {
cx.observe_global::<SettingsStore, _>(move |cx| {
let new_base_keymap = *settings::get::<BaseKeymap>(cx);
if new_base_keymap != old_base_keymap {
old_base_keymap = new_base_keymap.clone();
cx.clear_bindings();
load_default_keymap(cx);
keymap_content.clone().add_to_cx(cx).log_err();
}
})
.detach();
}));
}
}
})
.detach();
}
fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
workspace.with_local_workspace(cx, move |workspace, cx| {
let app_state = workspace.app_state().clone();
@ -620,16 +644,21 @@ mod tests {
use super::*;
use assets::Assets;
use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
use gpui::{executor::Deterministic, AppContext, AssetSource, TestAppContext, ViewHandle};
use fs::{FakeFs, Fs};
use gpui::{
elements::Empty, executor::Deterministic, Action, AnyElement, AppContext, AssetSource,
Element, Entity, TestAppContext, View, ViewHandle,
};
use language::LanguageRegistry;
use node_runtime::NodeRuntime;
use project::{Project, ProjectPath};
use serde_json::json;
use settings::{handle_settings_file_changes, watch_config_file, SettingsStore};
use std::{
collections::HashSet,
path::{Path, PathBuf},
};
use theme::ThemeRegistry;
use theme::{ThemeRegistry, ThemeSettings};
use util::http::FakeHttpClient;
use workspace::{
item::{Item, ItemHandle},
@ -638,7 +667,7 @@ mod tests {
#[gpui::test]
async fn test_open_paths_action(cx: &mut TestAppContext) {
let app_state = init(cx);
let app_state = init_test(cx);
app_state
.fs
.as_fake()
@ -738,7 +767,7 @@ mod tests {
#[gpui::test]
async fn test_window_edit_state(executor: Arc<Deterministic>, cx: &mut TestAppContext) {
let app_state = init(cx);
let app_state = init_test(cx);
app_state
.fs
.as_fake()
@ -819,7 +848,7 @@ mod tests {
#[gpui::test]
async fn test_new_empty_workspace(cx: &mut TestAppContext) {
let app_state = init(cx);
let app_state = init_test(cx);
cx.update(|cx| {
open_new(&app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx)
@ -858,7 +887,7 @@ mod tests {
#[gpui::test]
async fn test_open_entry(cx: &mut TestAppContext) {
let app_state = init(cx);
let app_state = init_test(cx);
app_state
.fs
.as_fake()
@ -971,7 +1000,7 @@ mod tests {
#[gpui::test]
async fn test_open_paths(cx: &mut TestAppContext) {
let app_state = init(cx);
let app_state = init_test(cx);
app_state
.fs
@ -1141,7 +1170,7 @@ mod tests {
#[gpui::test]
async fn test_save_conflicting_item(cx: &mut TestAppContext) {
let app_state = init(cx);
let app_state = init_test(cx);
app_state
.fs
.as_fake()
@ -1185,7 +1214,7 @@ mod tests {
#[gpui::test]
async fn test_open_and_save_new_file(cx: &mut TestAppContext) {
let app_state = init(cx);
let app_state = init_test(cx);
app_state.fs.create_dir(Path::new("/root")).await.unwrap();
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
@ -1274,7 +1303,7 @@ mod tests {
#[gpui::test]
async fn test_setting_language_when_saving_as_single_file_worktree(cx: &mut TestAppContext) {
let app_state = init(cx);
let app_state = init_test(cx);
app_state.fs.create_dir(Path::new("/root")).await.unwrap();
let project = Project::test(app_state.fs.clone(), [], cx).await;
@ -1313,9 +1342,7 @@ mod tests {
#[gpui::test]
async fn test_pane_actions(cx: &mut TestAppContext) {
init(cx);
let app_state = cx.update(AppState::test);
let app_state = init_test(cx);
app_state
.fs
.as_fake()
@ -1389,7 +1416,7 @@ mod tests {
#[gpui::test]
async fn test_navigation(cx: &mut TestAppContext) {
let app_state = init(cx);
let app_state = init_test(cx);
app_state
.fs
.as_fake()
@ -1665,7 +1692,7 @@ mod tests {
#[gpui::test]
async fn test_reopening_closed_items(cx: &mut TestAppContext) {
let app_state = init(cx);
let app_state = init_test(cx);
app_state
.fs
.as_fake()
@ -1828,6 +1855,175 @@ mod tests {
}
}
#[gpui::test]
async fn test_base_keymap(cx: &mut gpui::TestAppContext) {
struct TestView;
impl Entity for TestView {
type Event = ();
}
impl View for TestView {
fn ui_name() -> &'static str {
"TestView"
}
fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
Empty::new().into_any()
}
}
let executor = cx.background();
let fs = FakeFs::new(executor.clone());
actions!(test, [A, B]);
// From the Atom keymap
actions!(workspace, [ActivatePreviousPane]);
// From the JetBrains keymap
actions!(pane, [ActivatePrevItem]);
fs.save(
"/settings.json".as_ref(),
&r#"
{
"base_keymap": "Atom"
}
"#
.into(),
Default::default(),
)
.await
.unwrap();
fs.save(
"/keymap.json".as_ref(),
&r#"
[
{
"bindings": {
"backspace": "test::A"
}
}
]
"#
.into(),
Default::default(),
)
.await
.unwrap();
cx.update(|cx| {
cx.set_global(SettingsStore::test(cx));
theme::init(Assets, cx);
welcome::init(cx);
cx.add_global_action(|_: &A, _cx| {});
cx.add_global_action(|_: &B, _cx| {});
cx.add_global_action(|_: &ActivatePreviousPane, _cx| {});
cx.add_global_action(|_: &ActivatePrevItem, _cx| {});
let settings_rx = watch_config_file(
executor.clone(),
fs.clone(),
PathBuf::from("/settings.json"),
);
let keymap_rx =
watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json"));
handle_keymap_file_changes(keymap_rx, cx);
handle_settings_file_changes(settings_rx, cx);
});
cx.foreground().run_until_parked();
let (window_id, _view) = cx.add_window(|_| TestView);
// Test loading the keymap base at all
assert_key_bindings_for(
window_id,
cx,
vec![("backspace", &A), ("k", &ActivatePreviousPane)],
line!(),
);
// Test modifying the users keymap, while retaining the base keymap
fs.save(
"/keymap.json".as_ref(),
&r#"
[
{
"bindings": {
"backspace": "test::B"
}
}
]
"#
.into(),
Default::default(),
)
.await
.unwrap();
cx.foreground().run_until_parked();
assert_key_bindings_for(
window_id,
cx,
vec![("backspace", &B), ("k", &ActivatePreviousPane)],
line!(),
);
// Test modifying the base, while retaining the users keymap
fs.save(
"/settings.json".as_ref(),
&r#"
{
"base_keymap": "JetBrains"
}
"#
.into(),
Default::default(),
)
.await
.unwrap();
cx.foreground().run_until_parked();
assert_key_bindings_for(
window_id,
cx,
vec![("backspace", &B), ("[", &ActivatePrevItem)],
line!(),
);
fn assert_key_bindings_for<'a>(
window_id: usize,
cx: &TestAppContext,
actions: Vec<(&'static str, &'a dyn Action)>,
line: u32,
) {
for (key, action) in actions {
// assert that...
assert!(
cx.available_actions(window_id, 0)
.into_iter()
.any(|(_, bound_action, b)| {
// action names match...
bound_action.name() == action.name()
&& bound_action.namespace() == action.namespace()
// and key strokes contain the given key
&& b.iter()
.any(|binding| binding.keystrokes().iter().any(|k| k.key == key))
}),
"On {} Failed to find {} with key binding {}",
line,
action.name(),
key
);
}
}
}
#[gpui::test]
fn test_bundled_settings_and_themes(cx: &mut AppContext) {
cx.platform()
@ -1846,15 +2042,20 @@ mod tests {
])
.unwrap();
let themes = ThemeRegistry::new(Assets, cx.font_cache().clone());
let settings = Settings::defaults(Assets, cx.font_cache(), &themes);
let mut settings = SettingsStore::default();
settings
.set_default_settings(&settings::default_settings(), cx)
.unwrap();
cx.set_global(settings);
theme::init(Assets, cx);
let mut has_default_theme = false;
for theme_name in themes.list(false).map(|meta| meta.name) {
let theme = themes.get(&theme_name).unwrap();
if theme.meta.name == settings.theme.meta.name {
assert_eq!(theme.meta.name, theme_name);
if theme.meta.name == settings::get::<ThemeSettings>(cx).theme.meta.name {
has_default_theme = true;
}
assert_eq!(theme.meta.name, theme_name);
}
assert!(has_default_theme);
}
@ -1864,25 +2065,26 @@ mod tests {
let mut languages = LanguageRegistry::test();
languages.set_executor(cx.background().clone());
let languages = Arc::new(languages);
let themes = ThemeRegistry::new((), cx.font_cache().clone());
let http = FakeHttpClient::with_404_response();
let node_runtime = NodeRuntime::new(http, cx.background().to_owned());
languages::init(languages.clone(), themes, node_runtime);
languages::init(languages.clone(), node_runtime);
for name in languages.language_names() {
languages.language_for_name(&name);
}
cx.foreground().run_until_parked();
}
fn init(cx: &mut TestAppContext) -> Arc<AppState> {
fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
cx.foreground().forbid_parking();
cx.update(|cx| {
let mut app_state = AppState::test(cx);
let state = Arc::get_mut(&mut app_state).unwrap();
state.initialize_workspace = initialize_workspace;
state.build_window_options = build_window_options;
theme::init((), cx);
call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
workspace::init(app_state.clone(), cx);
language::init(cx);
editor::init(cx);
pane::init(cx);
app_state