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

@ -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();