Detailed changes
@@ -370,6 +370,7 @@ mod tests {
use gpui::TestAppContext;
use language::Point;
use project::Project;
+ use settings::KeymapFile;
use workspace::{AppState, Workspace};
#[test]
@@ -503,7 +504,20 @@ mod tests {
workspace::init(app_state.clone(), cx);
init(cx);
Project::init_settings(cx);
- settings::load_default_keymap(cx);
+ KeymapFile::parse(
+ r#"[
+ {
+ "bindings": {
+ "cmd-n": "workspace::NewFile",
+ "enter": "menu::Confirm",
+ "cmd-shift-p": "command_palette::Toggle"
+ }
+ }
+ ]"#,
+ )
+ .unwrap()
+ .add_to_cx(cx)
+ .unwrap();
app_state
})
}
@@ -9565,7 +9565,7 @@ impl InputHandler for Editor {
) -> Option<gpui::Bounds<Pixels>> {
let text_layout_details = self.text_layout_details(cx);
let style = &text_layout_details.editor_style;
- let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
+ let font_id = cx.text_system().resolve_font(&style.text.font());
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let line_height = style.text.line_height_in_pixels(cx.rem_size());
let em_width = cx
@@ -1230,6 +1230,14 @@ impl EditorElement {
return;
}
+ // If a drag took place after we started dragging the scrollbar,
+ // cancel the scrollbar drag.
+ if cx.has_active_drag() {
+ self.editor.update(cx, |editor, cx| {
+ editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
+ });
+ }
+
let top = bounds.origin.y;
let bottom = bounds.lower_left().y;
let right = bounds.lower_right().x;
@@ -1767,7 +1775,7 @@ impl EditorElement {
let snapshot = editor.snapshot(cx);
let style = self.style.clone();
- let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
+ let font_id = cx.text_system().resolve_font(&style.text.font());
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let line_height = style.text.line_height_in_pixels(cx.rem_size());
let em_width = cx
@@ -3774,7 +3782,7 @@ fn compute_auto_height_layout(
}
let style = editor.style.as_ref().unwrap();
- let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
+ let font_id = cx.text_system().resolve_font(&style.text.font());
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let line_height = style.text.line_height_in_pixels(cx.rem_size());
let em_width = cx
@@ -327,6 +327,7 @@ impl AppContext {
pub fn refresh(&mut self) {
self.pending_effects.push_back(Effect::Refresh);
}
+
pub(crate) fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
self.pending_updates += 1;
let result = update(self);
@@ -840,10 +841,12 @@ impl AppContext {
/// Update the global of the given type with a closure. Unlike `global_mut`, this method provides
/// your closure with mutable access to the `AppContext` and the global simultaneously.
pub fn update_global<G: 'static, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R {
- let mut global = self.lease_global::<G>();
- let result = f(&mut global, self);
- self.end_global_lease(global);
- result
+ self.update(|cx| {
+ let mut global = cx.lease_global::<G>();
+ let result = f(&mut global, cx);
+ cx.end_global_lease(global);
+ result
+ })
}
/// Register a callback to be invoked when a global of the given type is updated.
@@ -941,6 +944,11 @@ impl AppContext {
self.pending_effects.push_back(Effect::Refresh);
}
+ pub fn clear_key_bindings(&mut self) {
+ self.keymap.lock().clear();
+ self.pending_effects.push_back(Effect::Refresh);
+ }
+
/// Register a global listener for actions invoked via the keyboard.
pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + 'static) {
self.global_action_listeners
@@ -15,8 +15,9 @@ use crate::{
use anyhow::anyhow;
use collections::FxHashMap;
use core::fmt;
+use itertools::Itertools;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
-use smallvec::SmallVec;
+use smallvec::{smallvec, SmallVec};
use std::{
cmp,
fmt::{Debug, Display, Formatter},
@@ -42,6 +43,7 @@ pub struct TextSystem {
raster_bounds: RwLock<FxHashMap<RenderGlyphParams, Bounds<DevicePixels>>>,
wrapper_pool: Mutex<FxHashMap<FontIdWithSize, Vec<LineWrapper>>>,
font_runs_pool: Mutex<Vec<Vec<FontRun>>>,
+ fallback_font_stack: SmallVec<[Font; 2]>,
}
impl TextSystem {
@@ -54,6 +56,12 @@ impl TextSystem {
font_ids_by_font: RwLock::default(),
wrapper_pool: Mutex::default(),
font_runs_pool: Mutex::default(),
+ fallback_font_stack: smallvec![
+ // TODO: This is currently Zed-specific.
+ // We should allow GPUI users to provide their own fallback font stack.
+ font("Zed Mono"),
+ font("Helvetica")
+ ],
}
}
@@ -72,6 +80,33 @@ impl TextSystem {
}
}
+ /// Resolves the specified font, falling back to the default font stack if
+ /// the font fails to load.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the font and none of the fallbacks can be resolved.
+ pub fn resolve_font(&self, font: &Font) -> FontId {
+ if let Ok(font_id) = self.font_id(font) {
+ return font_id;
+ }
+
+ for fallback in &self.fallback_font_stack {
+ if let Ok(font_id) = self.font_id(fallback) {
+ return font_id;
+ }
+ }
+
+ panic!(
+ "failed to resolve font '{}' or any of the fallbacks: {}",
+ font.family,
+ self.fallback_font_stack
+ .iter()
+ .map(|fallback| &fallback.family)
+ .join(", ")
+ );
+ }
+
pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
}
@@ -159,7 +194,7 @@ impl TextSystem {
) -> Result<Arc<LineLayout>> {
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
for run in runs.iter() {
- let font_id = self.font_id(&run.font)?;
+ let font_id = self.resolve_font(&run.font);
if let Some(last_run) = font_runs.last_mut() {
if last_run.font_id == font_id {
last_run.len += run.len;
@@ -253,7 +288,7 @@ impl TextSystem {
last_font = Some(run.font.clone());
font_runs.push(FontRun {
len: run_len_within_line,
- font_id: self.platform_text_system.font_id(&run.font)?,
+ font_id: self.resolve_font(&run.font),
});
}
@@ -113,7 +113,6 @@ pub struct LanguageServerName(pub Arc<str>);
pub struct CachedLspAdapter {
pub name: LanguageServerName,
pub short_name: &'static str,
- pub initialization_options: Option<Value>,
pub disk_based_diagnostic_sources: Vec<String>,
pub disk_based_diagnostics_progress_token: Option<String>,
pub language_ids: HashMap<String, String>,
@@ -125,7 +124,6 @@ impl CachedLspAdapter {
pub async fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
let name = adapter.name().await;
let short_name = adapter.short_name();
- let initialization_options = adapter.initialization_options().await;
let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await;
let disk_based_diagnostics_progress_token =
adapter.disk_based_diagnostics_progress_token().await;
@@ -134,7 +132,6 @@ impl CachedLspAdapter {
Arc::new(CachedLspAdapter {
name,
short_name,
- initialization_options,
disk_based_diagnostic_sources,
disk_based_diagnostics_progress_token,
language_ids,
@@ -2816,15 +2816,6 @@ impl Project {
let lsp = project_settings.lsp.get(&adapter.name.0);
let override_options = lsp.map(|s| s.initialization_options.clone()).flatten();
- let mut initialization_options = adapter.initialization_options.clone();
- match (&mut initialization_options, override_options) {
- (Some(initialization_options), Some(override_options)) => {
- merge_json_value_into(override_options, initialization_options);
- }
- (None, override_options) => initialization_options = override_options,
- _ => {}
- }
-
let server_id = pending_server.server_id;
let container_dir = pending_server.container_dir.clone();
let state = LanguageServerState::Starting({
@@ -2837,7 +2828,7 @@ impl Project {
let result = Self::setup_and_insert_language_server(
this.clone(),
&worktree_path,
- initialization_options,
+ override_options,
pending_server,
adapter.clone(),
language.clone(),
@@ -2958,7 +2949,7 @@ impl Project {
async fn setup_and_insert_language_server(
this: WeakModel<Self>,
worktree_path: &Path,
- initialization_options: Option<serde_json::Value>,
+ override_initialization_options: Option<serde_json::Value>,
pending_server: PendingLanguageServer,
adapter: Arc<CachedLspAdapter>,
language: Arc<Language>,
@@ -2968,7 +2959,7 @@ impl Project {
) -> Result<Option<Arc<LanguageServer>>> {
let language_server = Self::setup_pending_language_server(
this.clone(),
- initialization_options,
+ override_initialization_options,
pending_server,
worktree_path,
adapter.clone(),
@@ -2998,7 +2989,7 @@ impl Project {
async fn setup_pending_language_server(
this: WeakModel<Self>,
- initialization_options: Option<serde_json::Value>,
+ override_options: Option<serde_json::Value>,
pending_server: PendingLanguageServer,
worktree_path: &Path,
adapter: Arc<CachedLspAdapter>,
@@ -3164,7 +3155,14 @@ impl Project {
}
})
.detach();
-
+ let mut initialization_options = adapter.adapter.initialization_options().await;
+ match (&mut initialization_options, override_options) {
+ (Some(initialization_options), Some(override_options)) => {
+ merge_json_value_into(override_options, initialization_options);
+ }
+ (None, override_options) => initialization_options = override_options,
+ _ => {}
+ }
let language_server = language_server.initialize(initialization_options).await?;
language_server
@@ -971,25 +971,16 @@ impl ProjectPanel {
}
}
- fn open_in_terminal(&mut self, _: &OpenInTerminal, _cx: &mut ViewContext<Self>) {
- todo!()
- // if let Some((worktree, entry)) = self.selected_entry(cx) {
- // let window = cx.window();
- // let view_id = cx.view_id();
- // let path = worktree.abs_path().join(&entry.path);
-
- // cx.app_context()
- // .spawn(|mut cx| async move {
- // window.dispatch_action(
- // view_id,
- // &workspace::OpenTerminal {
- // working_directory: path,
- // },
- // &mut cx,
- // );
- // })
- // .detach();
- // }
+ fn open_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
+ if let Some((worktree, entry)) = self.selected_entry(cx) {
+ let path = worktree.abs_path().join(&entry.path);
+ cx.dispatch_action(
+ workspace::OpenTerminal {
+ working_directory: path,
+ }
+ .boxed_clone(),
+ )
+ }
}
pub fn new_search_in_directory(
@@ -1,4 +1,4 @@
-use crate::{settings_store::SettingsStore, KeymapFile, Settings};
+use crate::{settings_store::SettingsStore, Settings};
use anyhow::Result;
use fs::Fs;
use futures::{channel::mpsc, StreamExt};
@@ -77,7 +77,6 @@ pub fn handle_settings_file_changes(
});
cx.spawn(move |mut cx| async move {
while let Some(user_settings_content) = user_settings_file_rx.next().await {
- eprintln!("settings file changed");
let result = cx.update_global(|store: &mut SettingsStore, cx| {
store
.set_user_settings(&user_settings_content, cx)
@@ -121,14 +120,3 @@ pub fn update_settings_file<T: Settings>(
})
.detach_and_log_err(cx);
}
-
-pub fn load_default_keymap(cx: &mut AppContext) {
- for path in ["keymaps/default.json", "keymaps/vim.json"] {
- KeymapFile::load_asset(path, cx).unwrap();
- }
-
- // todo!()
- // if let Some(asset_path) = settings::get::<BaseKeymap>(cx).asset_path() {
- // KeymapFile::load_asset(asset_path, cx).unwrap();
- // }
-}
@@ -421,7 +421,7 @@ impl TerminalElement {
let rem_size = cx.rem_size();
let font_pixels = text_style.font_size.to_pixels(rem_size);
let line_height = font_pixels * line_height.to_pixels(rem_size);
- let font_id = cx.text_system().font_id(&text_style.font()).unwrap();
+ let font_id = cx.text_system().resolve_font(&text_style.font());
// todo!(do we need to keep this unwrap?)
let cell_width = text_system
@@ -126,13 +126,14 @@ impl RenderOnce for Tab {
if self.selected {
this.border_l().border_r().pb_px()
} else {
- this.pr_px().pl_px().border_b()
+ this.pr_px().pl_px().border_b().border_r()
}
}
TabPosition::Middle(Ordering::Equal) => this.border_l().border_r().pb_px(),
TabPosition::Middle(Ordering::Less) => this.border_l().pr_px().border_b(),
TabPosition::Middle(Ordering::Greater) => this.border_r().pl_px().border_b(),
})
+ .cursor_pointer()
.child(
h_stack()
.group("")
@@ -18,11 +18,11 @@ pub use only_instance::*;
pub use open_listener::*;
use anyhow::{anyhow, Context as _};
-use futures::{channel::mpsc, StreamExt};
+use futures::{channel::mpsc, select_biased, StreamExt};
use project_panel::ProjectPanel;
use quick_action_bar::QuickActionBar;
use search::project_search::ProjectSearchBar;
-use settings::{initial_local_settings_content, load_default_keymap, KeymapFile, Settings};
+use settings::{initial_local_settings_content, KeymapFile, Settings, SettingsStore};
use std::{borrow::Cow, ops::Deref, sync::Arc};
use terminal_view::terminal_panel::TerminalPanel;
use util::{
@@ -32,6 +32,7 @@ use util::{
ResultExt,
};
use uuid::Uuid;
+use welcome::BaseKeymap;
use workspace::Pane;
use workspace::{
create_and_open_local_file, notifications::simple_message_notification::MessageNotification,
@@ -399,8 +400,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
});
workspace.focus_handle(cx).focus(cx);
- //todo!()
- // load_default_keymap(cx);
+ load_default_keymap(cx);
})
.detach();
}
@@ -558,38 +558,58 @@ pub fn handle_keymap_file_changes(
mut user_keymap_file_rx: mpsc::UnboundedReceiver<String>,
cx: &mut AppContext,
) {
+ BaseKeymap::register(cx);
+
+ let (base_keymap_tx, mut base_keymap_rx) = mpsc::unbounded();
+ let mut old_base_keymap = *BaseKeymap::get_global(cx);
+ cx.observe_global::<SettingsStore>(move |cx| {
+ let new_base_keymap = *BaseKeymap::get_global(cx);
+ if new_base_keymap != old_base_keymap {
+ old_base_keymap = new_base_keymap.clone();
+ base_keymap_tx.unbounded_send(()).unwrap();
+ }
+ })
+ .detach();
+
cx.spawn(move |cx| async move {
- // let mut settings_subscription = None;
- while let Some(user_keymap_content) = user_keymap_file_rx.next().await {
- if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() {
- cx.update(|cx| reload_keymaps(cx, &keymap_content)).ok();
-
- // todo!()
- // 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();
- // reload_keymaps(cx, &keymap_content);
- // }
- // })
- // }));
+ let mut user_keymap = KeymapFile::default();
+ loop {
+ select_biased! {
+ _ = base_keymap_rx.next() => {}
+ user_keymap_content = user_keymap_file_rx.next() => {
+ if let Some(user_keymap_content) = user_keymap_content {
+ if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() {
+ user_keymap = keymap_content;
+ } else {
+ continue
+ }
+ }
+ }
}
+
+ cx.update(|cx| reload_keymaps(cx, &user_keymap)).ok();
}
})
.detach();
}
fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) {
- // todo!()
- // cx.clear_bindings();
+ cx.clear_key_bindings();
load_default_keymap(cx);
keymap_content.clone().add_to_cx(cx).log_err();
cx.set_menus(app_menus());
}
+pub fn load_default_keymap(cx: &mut AppContext) {
+ for path in ["keymaps/default.json", "keymaps/vim.json"] {
+ KeymapFile::load_asset(path, cx).unwrap();
+ }
+
+ if let Some(asset_path) = BaseKeymap::get_global(cx).asset_path() {
+ KeymapFile::load_asset(asset_path, cx).unwrap();
+ }
+}
+
fn open_local_settings_file(
workspace: &mut Workspace,
_: &OpenLocalSettings,