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

@ -3,7 +3,6 @@ pub use language::*;
use node_runtime::NodeRuntime;
use rust_embed::RustEmbed;
use std::{borrow::Cow, str, sync::Arc};
use theme::ThemeRegistry;
mod c;
mod elixir;
@ -32,11 +31,7 @@ mod yaml;
#[exclude = "*.rs"]
struct LanguageDir;
pub fn init(
languages: Arc<LanguageRegistry>,
themes: Arc<ThemeRegistry>,
node_runtime: Arc<NodeRuntime>,
) {
pub fn init(languages: Arc<LanguageRegistry>, node_runtime: Arc<NodeRuntime>) {
fn adapter_arc(adapter: impl LspAdapter) -> Arc<dyn LspAdapter> {
Arc::new(adapter)
}
@ -69,7 +64,6 @@ pub fn init(
vec![adapter_arc(json::JsonLspAdapter::new(
node_runtime.clone(),
languages.clone(),
themes.clone(),
))],
),
("markdown", tree_sitter_markdown::language(), vec![]),

View file

@ -249,16 +249,21 @@ impl super::LspAdapter for CLspAdapter {
#[cfg(test)]
mod tests {
use gpui::TestAppContext;
use language::{AutoindentMode, Buffer};
use settings::Settings;
use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
use settings::SettingsStore;
use std::num::NonZeroU32;
#[gpui::test]
async fn test_c_autoindent(cx: &mut TestAppContext) {
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
cx.update(|cx| {
let mut settings = Settings::test(cx);
settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
cx.set_global(settings);
cx.set_global(SettingsStore::test(cx));
language::init(cx);
cx.update_global::<SettingsStore, _, _>(|store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |s| {
s.defaults.tab_size = NonZeroU32::new(2);
});
});
});
let language = crate::languages::language("c", tree_sitter_c::language(), None).await;

View file

@ -6,7 +6,7 @@ use gpui::AppContext;
use language::{LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter};
use node_runtime::NodeRuntime;
use serde_json::json;
use settings::{keymap_file_json_schema, settings_file_json_schema};
use settings::{keymap_file_json_schema, SettingsJsonSchemaParams, SettingsStore};
use smol::fs;
use staff_mode::StaffMode;
use std::{
@ -16,7 +16,6 @@ use std::{
path::{Path, PathBuf},
sync::Arc,
};
use theme::ThemeRegistry;
use util::http::HttpClient;
use util::{paths, ResultExt};
@ -30,20 +29,11 @@ fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
pub struct JsonLspAdapter {
node: Arc<NodeRuntime>,
languages: Arc<LanguageRegistry>,
themes: Arc<ThemeRegistry>,
}
impl JsonLspAdapter {
pub fn new(
node: Arc<NodeRuntime>,
languages: Arc<LanguageRegistry>,
themes: Arc<ThemeRegistry>,
) -> Self {
JsonLspAdapter {
node,
languages,
themes,
}
pub fn new(node: Arc<NodeRuntime>, languages: Arc<LanguageRegistry>) -> Self {
JsonLspAdapter { node, languages }
}
}
@ -128,12 +118,15 @@ impl LspAdapter for JsonLspAdapter {
cx: &mut AppContext,
) -> Option<BoxFuture<'static, serde_json::Value>> {
let action_names = cx.all_action_names().collect::<Vec<_>>();
let theme_names = self
.themes
.list(**cx.default_global::<StaffMode>())
.map(|meta| meta.name)
.collect();
let language_names = self.languages.language_names();
let staff_mode = cx.default_global::<StaffMode>().0;
let language_names = &self.languages.language_names();
let settings_schema = cx.global::<SettingsStore>().json_schema(
&SettingsJsonSchemaParams {
language_names,
staff_mode,
},
cx,
);
Some(
future::ready(serde_json::json!({
"json": {
@ -143,7 +136,7 @@ impl LspAdapter for JsonLspAdapter {
"schemas": [
{
"fileMatch": [schema_file_match(&paths::SETTINGS)],
"schema": settings_file_json_schema(theme_names, &language_names),
"schema": settings_schema,
},
{
"fileMatch": [schema_file_match(&paths::KEYMAP)],

View file

@ -170,8 +170,9 @@ impl LspAdapter for PythonLspAdapter {
#[cfg(test)]
mod tests {
use gpui::{ModelContext, TestAppContext};
use language::{AutoindentMode, Buffer};
use settings::Settings;
use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
use settings::SettingsStore;
use std::num::NonZeroU32;
#[gpui::test]
async fn test_python_autoindent(cx: &mut TestAppContext) {
@ -179,9 +180,13 @@ mod tests {
let language =
crate::languages::language("python", tree_sitter_python::language(), None).await;
cx.update(|cx| {
let mut settings = Settings::test(cx);
settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
cx.set_global(settings);
cx.set_global(SettingsStore::test(cx));
language::init(cx);
cx.update_global::<SettingsStore, _, _>(|store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |s| {
s.defaults.tab_size = NonZeroU32::new(2);
});
});
});
cx.add_model(|cx| {

View file

@ -253,10 +253,13 @@ impl LspAdapter for RustLspAdapter {
#[cfg(test)]
mod tests {
use std::num::NonZeroU32;
use super::*;
use crate::languages::language;
use gpui::{color::Color, TestAppContext};
use settings::Settings;
use language::language_settings::AllLanguageSettings;
use settings::SettingsStore;
use theme::SyntaxTheme;
#[gpui::test]
@ -435,9 +438,13 @@ mod tests {
async fn test_rust_autoindent(cx: &mut TestAppContext) {
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
cx.update(|cx| {
let mut settings = Settings::test(cx);
settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
cx.set_global(settings);
cx.set_global(SettingsStore::test(cx));
language::init(cx);
cx.update_global::<SettingsStore, _, _>(|store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |s| {
s.defaults.tab_size = NonZeroU32::new(2);
});
});
});
let language = crate::languages::language("rust", tree_sitter_rust::language(), None).await;

View file

@ -2,10 +2,11 @@ use anyhow::{anyhow, Result};
use async_trait::async_trait;
use futures::{future::BoxFuture, FutureExt, StreamExt};
use gpui::AppContext;
use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
use language::{
language_settings::language_settings, LanguageServerBinary, LanguageServerName, LspAdapter,
};
use node_runtime::NodeRuntime;
use serde_json::Value;
use settings::Settings;
use smol::fs;
use std::{
any::Any,
@ -100,14 +101,13 @@ impl LspAdapter for YamlLspAdapter {
}
fn workspace_configuration(&self, cx: &mut AppContext) -> Option<BoxFuture<'static, Value>> {
let settings = cx.global::<Settings>();
Some(
future::ready(serde_json::json!({
"yaml": {
"keyOrdering": false
},
"[yaml]": {
"editor.tabSize": settings.tab_size(Some("YAML"))
"editor.tabSize": language_settings(Some("YAML"), cx).tab_size,
}
}))
.boxed(),

View file

@ -6,55 +6,61 @@ use assets::Assets;
use backtrace::Backtrace;
use cli::{
ipc::{self, IpcSender},
CliRequest, CliResponse, IpcHandshake,
CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME,
};
use client::{self, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
use client::{self, TelemetrySettings, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
use db::kvp::KEY_VALUE_STORE;
use editor::Editor;
use editor::{scroll::autoscroll::Autoscroll, Editor};
use futures::{
channel::{mpsc, oneshot},
FutureExt, SinkExt, StreamExt,
};
use gpui::{Action, App, AppContext, AssetSource, AsyncAppContext, Task, ViewContext};
use isahc::{config::Configurable, Request};
use language::LanguageRegistry;
use language::{LanguageRegistry, Point};
use log::LevelFilter;
use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use project::Fs;
use serde::{Deserialize, Serialize};
use settings::{
self, settings_file::SettingsFile, KeymapFileContent, Settings, SettingsFileContent,
WorkingDirectory,
};
use settings::{default_settings, handle_settings_file_changes, watch_config_file, SettingsStore};
use simplelog::ConfigBuilder;
use smol::process::Command;
use std::{
collections::HashMap,
env,
ffi::OsStr,
fs::OpenOptions,
io::Write as _,
os::unix::prelude::OsStrExt,
panic,
path::PathBuf,
sync::{Arc, Weak},
path::{Path, PathBuf},
str,
sync::{
atomic::{AtomicBool, Ordering},
Arc, Weak,
},
thread,
time::Duration,
};
use terminal_view::{get_working_directory, TerminalView};
use util::http::{self, HttpClient};
use sum_tree::Bias;
use terminal_view::{get_working_directory, TerminalSettings, TerminalView};
use util::{
http::{self, HttpClient},
paths::PathLikeWithPosition,
};
use welcome::{show_welcome_experience, FIRST_OPEN};
use fs::RealFs;
use settings::watched_json::WatchedJsonFile;
#[cfg(debug_assertions)]
use staff_mode::StaffMode;
use theme::ThemeRegistry;
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
use workspace::{
item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, Workspace,
};
use zed::{self, build_window_options, initialize_workspace, languages, menus};
use zed::{
self, build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus,
};
fn main() {
let http = http::client();
@ -74,10 +80,10 @@ fn main() {
load_embedded_fonts(&app);
let fs = Arc::new(RealFs);
let themes = ThemeRegistry::new(Assets, app.font_cache());
let default_settings = Settings::defaults(Assets, &app.font_cache(), &themes);
let config_files = load_config_files(&app, fs.clone());
let user_settings_file_rx =
watch_config_file(app.background(), fs.clone(), paths::SETTINGS.clone());
let user_keymap_file_rx =
watch_config_file(app.background(), fs.clone(), paths::KEYMAP.clone());
let login_shell_env_loaded = if stdout_is_a_pty() {
Task::ready(())
@ -88,29 +94,17 @@ fn main() {
};
let (cli_connections_tx, mut cli_connections_rx) = mpsc::unbounded();
let cli_connections_tx = Arc::new(cli_connections_tx);
let (open_paths_tx, mut open_paths_rx) = mpsc::unbounded();
let open_paths_tx = Arc::new(open_paths_tx);
let urls_callback_triggered = Arc::new(AtomicBool::new(false));
let callback_cli_connections_tx = Arc::clone(&cli_connections_tx);
let callback_open_paths_tx = Arc::clone(&open_paths_tx);
let callback_urls_callback_triggered = Arc::clone(&urls_callback_triggered);
app.on_open_urls(move |urls, _| {
if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) {
if let Some(cli_connection) = connect_to_cli(server_name).log_err() {
cli_connections_tx
.unbounded_send(cli_connection)
.map_err(|_| anyhow!("no listener for cli connections"))
.log_err();
};
} else {
let paths: Vec<_> = urls
.iter()
.flat_map(|url| url.strip_prefix("file://"))
.map(|url| {
let decoded = urlencoding::decode_binary(url.as_bytes());
PathBuf::from(OsStr::from_bytes(decoded.as_ref()))
})
.collect();
open_paths_tx
.unbounded_send(paths)
.map_err(|_| anyhow!("no listener for open urls requests"))
.log_err();
}
callback_urls_callback_triggered.store(true, Ordering::Release);
open_urls(urls, &callback_cli_connections_tx, &callback_open_paths_tx);
})
.on_reopen(move |cx| {
if cx.has_global::<Weak<AppState>>() {
@ -129,26 +123,13 @@ fn main() {
#[cfg(debug_assertions)]
cx.set_global(StaffMode(true));
let (settings_file_content, keymap_file) = cx.background().block(config_files).unwrap();
//Setup settings global before binding actions
cx.set_global(SettingsFile::new(
&paths::SETTINGS,
settings_file_content.clone(),
fs.clone(),
));
settings::watch_files(
default_settings,
settings_file_content,
themes.clone(),
keymap_file,
cx,
);
if !stdout_is_a_pty() {
upload_previous_panics(http.clone(), cx);
}
let mut store = SettingsStore::default();
store
.set_default_settings(default_settings().as_ref(), cx)
.unwrap();
cx.set_global(store);
handle_settings_file_changes(user_settings_file_rx, cx);
handle_keymap_file_changes(user_keymap_file_rx, cx);
let client = client::Client::new(http.clone(), cx);
let mut languages = LanguageRegistry::new(login_shell_env_loaded);
@ -157,15 +138,17 @@ fn main() {
let languages = Arc::new(languages);
let node_runtime = NodeRuntime::new(http.clone(), cx.background().to_owned());
languages::init(languages.clone(), themes.clone(), node_runtime.clone());
languages::init(languages.clone(), node_runtime.clone());
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
cx.set_global(client.clone());
theme::init(Assets, cx);
context_menu::init(cx);
project::Project::init(&client);
client::init(client.clone(), cx);
project::Project::init(&client, cx);
client::init(&client, cx);
command_palette::init(cx);
language::init(cx);
editor::init(cx);
go_to_line::init(cx);
file_finder::init(cx);
@ -179,13 +162,12 @@ fn main() {
theme_testbench::init(cx);
copilot::init(http.clone(), node_runtime, cx);
cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx))
.detach();
cx.spawn(|cx| watch_themes(fs.clone(), cx)).detach();
languages.set_theme(cx.global::<Settings>().theme.clone());
cx.observe_global::<Settings, _>({
languages.set_theme(theme::current(cx).clone());
cx.observe_global::<SettingsStore, _>({
let languages = languages.clone();
move |cx| languages.set_theme(cx.global::<Settings>().theme.clone())
move |cx| languages.set_theme(theme::current(cx).clone())
})
.detach();
@ -193,12 +175,11 @@ fn main() {
client.telemetry().report_mixpanel_event(
"start app",
Default::default(),
cx.global::<Settings>().telemetry(),
*settings::get::<TelemetrySettings>(cx),
);
let app_state = Arc::new(AppState {
languages,
themes,
client: client.clone(),
user_store,
fs,
@ -207,7 +188,7 @@ fn main() {
background_actions,
});
cx.set_global(Arc::downgrade(&app_state));
auto_update::init(http, client::ZED_SERVER_URL.clone(), cx);
auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx);
workspace::init(app_state.clone(), cx);
recent_projects::init(cx);
@ -215,10 +196,13 @@ fn main() {
journal::init(app_state.clone(), cx);
language_selector::init(cx);
theme_selector::init(cx);
zed::init(&app_state, cx);
activity_indicator::init(cx);
lsp_log::init(cx);
call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
collab_ui::init(&app_state, cx);
feedback::init(cx);
welcome::init(cx);
zed::init(&app_state, cx);
cx.set_menus(menus::menus());
@ -232,6 +216,16 @@ fn main() {
workspace::open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx);
}
} else {
upload_previous_panics(http.clone(), cx);
// TODO Development mode that forces the CLI mode usually runs Zed binary as is instead
// of an *app, hence gets no specific callbacks run. Emulate them here, if needed.
if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some()
&& !urls_callback_triggered.load(Ordering::Acquire)
{
open_urls(collect_url_args(), &cli_connections_tx, &open_paths_tx)
}
if let Ok(Some(connection)) = cli_connections_rx.try_next() {
cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx))
.detach();
@ -282,6 +276,37 @@ fn main() {
});
}
fn open_urls(
urls: Vec<String>,
cli_connections_tx: &mpsc::UnboundedSender<(
mpsc::Receiver<CliRequest>,
IpcSender<CliResponse>,
)>,
open_paths_tx: &mpsc::UnboundedSender<Vec<PathBuf>>,
) {
if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) {
if let Some(cli_connection) = connect_to_cli(server_name).log_err() {
cli_connections_tx
.unbounded_send(cli_connection)
.map_err(|_| anyhow!("no listener for cli connections"))
.log_err();
};
} else {
let paths: Vec<_> = urls
.iter()
.flat_map(|url| url.strip_prefix("file://"))
.map(|url| {
let decoded = urlencoding::decode_binary(url.as_bytes());
PathBuf::from(OsStr::from_bytes(decoded.as_ref()))
})
.collect();
open_paths_tx
.unbounded_send(paths)
.map_err(|_| anyhow!("no listener for open urls requests"))
.log_err();
}
}
async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncAppContext) {
if let Some(location) = workspace::last_opened_workspace_paths().await {
cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx))
@ -412,7 +437,7 @@ fn init_panic_hook(app_version: String) {
}
fn upload_previous_panics(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
let diagnostics_telemetry = cx.global::<Settings>().telemetry_diagnostics();
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
cx.background()
.spawn({
@ -442,7 +467,7 @@ fn upload_previous_panics(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
continue;
};
if diagnostics_telemetry {
if telemetry_settings.diagnostics {
let panic_data_text = smol::fs::read_to_string(&child_path)
.await
.context("error reading panic file")?;
@ -512,7 +537,8 @@ async fn load_login_shell_environment() -> Result<()> {
}
fn stdout_is_a_pty() -> bool {
unsafe { libc::isatty(libc::STDOUT_FILENO as i32) != 0 }
std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none()
&& unsafe { libc::isatty(libc::STDOUT_FILENO as i32) != 0 }
}
fn collect_path_args() -> Vec<PathBuf> {
@ -525,7 +551,11 @@ fn collect_path_args() -> Vec<PathBuf> {
None
}
})
.collect::<Vec<_>>()
.collect()
}
fn collect_url_args() -> Vec<String> {
env::args().skip(1).collect()
}
fn load_embedded_fonts(app: &App) {
@ -547,11 +577,7 @@ fn load_embedded_fonts(app: &App) {
}
#[cfg(debug_assertions)]
async fn watch_themes(
fs: Arc<dyn Fs>,
themes: Arc<ThemeRegistry>,
mut cx: AsyncAppContext,
) -> Option<()> {
async fn watch_themes(fs: Arc<dyn Fs>, mut cx: AsyncAppContext) -> Option<()> {
let mut events = fs
.watch("styles/src".as_ref(), Duration::from_millis(100))
.await;
@ -563,7 +589,7 @@ async fn watch_themes(
.await
.log_err()?;
if output.status.success() {
cx.update(|cx| theme_selector::reload(themes.clone(), cx))
cx.update(|cx| theme_selector::reload(cx))
} else {
eprintln!(
"build script failed {}",
@ -575,35 +601,10 @@ async fn watch_themes(
}
#[cfg(not(debug_assertions))]
async fn watch_themes(
_fs: Arc<dyn Fs>,
_themes: Arc<ThemeRegistry>,
_cx: AsyncAppContext,
) -> Option<()> {
async fn watch_themes(_fs: Arc<dyn Fs>, _cx: AsyncAppContext) -> Option<()> {
None
}
fn load_config_files(
app: &App,
fs: Arc<dyn Fs>,
) -> oneshot::Receiver<(
WatchedJsonFile<SettingsFileContent>,
WatchedJsonFile<KeymapFileContent>,
)> {
let executor = app.background();
let (tx, rx) = oneshot::channel();
executor
.clone()
.spawn(async move {
let settings_file =
WatchedJsonFile::new(fs.clone(), &executor, paths::SETTINGS.clone()).await;
let keymap_file = WatchedJsonFile::new(fs, &executor, paths::KEYMAP.clone()).await;
tx.send((settings_file, keymap_file)).ok()
})
.detach();
rx
}
fn connect_to_cli(
server_name: &str,
) -> Result<(mpsc::Receiver<CliRequest>, IpcSender<CliResponse>)> {
@ -641,13 +642,38 @@ async fn handle_cli_connection(
if let Some(request) = requests.next().await {
match request {
CliRequest::Open { paths, wait } => {
let mut caret_positions = HashMap::new();
let paths = if paths.is_empty() {
workspace::last_opened_workspace_paths()
.await
.map(|location| location.paths().to_vec())
.unwrap_or(paths)
.unwrap_or_default()
} else {
paths
.into_iter()
.filter_map(|path_with_position_string| {
let path_with_position = PathLikeWithPosition::parse_str(
&path_with_position_string,
|path_str| {
Ok::<_, std::convert::Infallible>(
Path::new(path_str).to_path_buf(),
)
},
)
.expect("Infallible");
let path = path_with_position.path_like;
if let Some(row) = path_with_position.row {
if path.is_file() {
let row = row.saturating_sub(1);
let col =
path_with_position.column.unwrap_or(0).saturating_sub(1);
caret_positions.insert(path.clone(), Point::new(row, col));
}
}
Some(path)
})
.collect()
};
let mut errored = false;
@ -657,11 +683,32 @@ async fn handle_cli_connection(
{
Ok((workspace, items)) => {
let mut item_release_futures = Vec::new();
cx.update(|cx| {
for (item, path) in items.into_iter().zip(&paths) {
match item {
Some(Ok(item)) => {
let released = oneshot::channel();
for (item, path) in items.into_iter().zip(&paths) {
match item {
Some(Ok(item)) => {
if let Some(point) = caret_positions.remove(path) {
if let Some(active_editor) = item.downcast::<Editor>() {
active_editor
.downgrade()
.update(&mut cx, |editor, cx| {
let snapshot =
editor.snapshot(cx).display_snapshot;
let point = snapshot
.buffer_snapshot
.clip_point(point, Bias::Left);
editor.change_selections(
Some(Autoscroll::center()),
cx,
|s| s.select_ranges([point..point]),
);
})
.log_err();
}
}
let released = oneshot::channel();
cx.update(|cx| {
item.on_release(
cx,
Box::new(move |_| {
@ -669,23 +716,20 @@ async fn handle_cli_connection(
}),
)
.detach();
item_release_futures.push(released.1);
}
Some(Err(err)) => {
responses
.send(CliResponse::Stderr {
message: format!(
"error opening {:?}: {}",
path, err
),
})
.log_err();
errored = true;
}
None => {}
});
item_release_futures.push(released.1);
}
Some(Err(err)) => {
responses
.send(CliResponse::Stderr {
message: format!("error opening {:?}: {}", path, err),
})
.log_err();
errored = true;
}
None => {}
}
});
}
if wait {
let background = cx.background();
@ -748,13 +792,9 @@ pub fn dock_default_item_factory(
workspace: &mut Workspace,
cx: &mut ViewContext<Workspace>,
) -> Option<Box<dyn ItemHandle>> {
let strategy = cx
.global::<Settings>()
.terminal_overrides
let strategy = settings::get::<TerminalSettings>(cx)
.working_directory
.clone()
.unwrap_or(WorkingDirectory::CurrentProjectDirectory);
.clone();
let working_directory = get_working_directory(workspace, cx, strategy);
let window_id = cx.window_id();

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