Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ada0523043 | ||
|
|
aeb8535468 | ||
|
|
a588a7dd4d | ||
|
|
dcca48482b | ||
|
|
c983c9b6df | ||
|
|
a082b93260 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -10261,7 +10261,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.121.4"
|
||||
version = "0.122.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"ai",
|
||||
|
||||
@@ -415,15 +415,7 @@
|
||||
"cmd-?": "assistant::ToggleFocus",
|
||||
"cmd-alt-s": "workspace::SaveAll",
|
||||
"cmd-k m": "language_selector::Toggle",
|
||||
"escape": "workspace::Unfollow",
|
||||
"cmd-k cmd-left": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"cmd-k cmd-right": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"cmd-k cmd-up": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"cmd-k cmd-down": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"cmd-k shift-left": ["workspace::SwapPaneInDirection", "Left"],
|
||||
"cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
|
||||
"cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
|
||||
"cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"]
|
||||
"escape": "workspace::Unfollow"
|
||||
}
|
||||
},
|
||||
// Bindings from Sublime Text
|
||||
@@ -449,6 +441,18 @@
|
||||
"ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-k cmd-left": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"cmd-k cmd-right": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"cmd-k cmd-up": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"cmd-k cmd-down": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"cmd-k shift-left": ["workspace::SwapPaneInDirection", "Left"],
|
||||
"cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
|
||||
"cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
|
||||
"cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"]
|
||||
}
|
||||
},
|
||||
// Bindings from Atom
|
||||
{
|
||||
"context": "Pane",
|
||||
|
||||
@@ -199,13 +199,9 @@ impl AssistantPanel {
|
||||
.update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
|
||||
cx.notify();
|
||||
if self.focus_handle.is_focused(cx) {
|
||||
if self.has_credentials() {
|
||||
if let Some(editor) = self.active_editor() {
|
||||
cx.focus_view(editor);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(api_key_editor) = self.api_key_editor.as_ref() {
|
||||
if let Some(editor) = self.active_editor() {
|
||||
cx.focus_view(editor);
|
||||
} else if let Some(api_key_editor) = self.api_key_editor.as_ref() {
|
||||
cx.focus_view(api_key_editor);
|
||||
}
|
||||
}
|
||||
@@ -781,10 +777,6 @@ impl AssistantPanel {
|
||||
});
|
||||
}
|
||||
|
||||
fn build_api_key_editor(&mut self, cx: &mut WindowContext<'_>) {
|
||||
self.api_key_editor = Some(build_api_key_editor(cx));
|
||||
}
|
||||
|
||||
fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> View<ConversationEditor> {
|
||||
let editor = cx.new_view(|cx| {
|
||||
ConversationEditor::new(
|
||||
@@ -878,7 +870,7 @@ impl AssistantPanel {
|
||||
cx.update(|cx| completion_provider.delete_credentials(cx))?
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.build_api_key_editor(cx);
|
||||
this.api_key_editor = Some(build_api_key_editor(cx));
|
||||
this.focus_handle.focus(cx);
|
||||
cx.notify();
|
||||
})
|
||||
@@ -1144,7 +1136,7 @@ impl AssistantPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_api_key_editor(cx: &mut WindowContext) -> View<Editor> {
|
||||
fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
|
||||
cx.new_view(|cx| {
|
||||
let mut editor = Editor::single_line(cx);
|
||||
editor.set_placeholder_text("sk-000000000000000000000000000000000000000000000000", cx);
|
||||
@@ -1155,10 +1147,9 @@ fn build_api_key_editor(cx: &mut WindowContext) -> View<Editor> {
|
||||
impl Render for AssistantPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
if let Some(api_key_editor) = self.api_key_editor.clone() {
|
||||
const INSTRUCTIONS: [&'static str; 6] = [
|
||||
const INSTRUCTIONS: [&'static str; 5] = [
|
||||
"To use the assistant panel or inline assistant, you need to add your OpenAI API key.",
|
||||
" - You can create an API key at: platform.openai.com/api-keys",
|
||||
" - Make sure your OpenAI account has credits",
|
||||
" - Having a subscription for another service like GitHub Copilot won't work.",
|
||||
" ",
|
||||
"Paste your OpenAI API key and press Enter to use the assistant:"
|
||||
@@ -1351,9 +1342,7 @@ impl Panel for AssistantPanel {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
load_credentials.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if !this.has_credentials() {
|
||||
this.build_api_key_editor(cx);
|
||||
} else if this.editors.is_empty() {
|
||||
if this.editors.is_empty() {
|
||||
this.new_conversation(cx);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5967,6 +5967,6 @@ async fn test_cmd_k_left(cx: &mut TestAppContext) {
|
||||
cx.executor().advance_clock(Duration::from_secs(2));
|
||||
cx.simulate_keystrokes("left");
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
|
||||
assert!(workspace.items(cx).collect::<Vec<_>>().len() == 3);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ impl Clone for Command {
|
||||
|
||||
/// Hit count for each command in the palette.
|
||||
/// We only account for commands triggered directly via command palette and not by e.g. keystrokes because
|
||||
/// if an user already knows a keystroke for a command, they are unlikely to use a command palette to look for it.
|
||||
/// if a user already knows a keystroke for a command, they are unlikely to use a command palette to look for it.
|
||||
#[derive(Default)]
|
||||
struct HitCounts(HashMap<String, usize>);
|
||||
|
||||
|
||||
@@ -2310,7 +2310,7 @@ impl Editor {
|
||||
let mut bracket_pair = None;
|
||||
let mut is_bracket_pair_start = false;
|
||||
if !text.is_empty() {
|
||||
// `text` can be empty when an user is using IME (e.g. Chinese Wubi Simplified)
|
||||
// `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
|
||||
// and they are removing the character that triggered IME popup.
|
||||
for (pair, enabled) in scope.brackets() {
|
||||
if enabled && pair.close && pair.start.ends_with(text.as_ref()) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
|
||||
use crate::{char_kind, CharKind, EditorStyle, ToOffset, ToPoint};
|
||||
use gpui::{px, Pixels, WindowTextSystem};
|
||||
use gpui::{px, Pixels, TextSystem};
|
||||
use language::Point;
|
||||
|
||||
use std::{ops::Range, sync::Arc};
|
||||
@@ -22,7 +22,7 @@ pub enum FindRange {
|
||||
/// TextLayoutDetails encompasses everything we need to move vertically
|
||||
/// taking into account variable width characters.
|
||||
pub struct TextLayoutDetails {
|
||||
pub(crate) text_system: Arc<WindowTextSystem>,
|
||||
pub(crate) text_system: Arc<TextSystem>,
|
||||
pub(crate) editor_style: EditorStyle,
|
||||
pub(crate) rem_size: Pixels,
|
||||
pub anchor: Anchor,
|
||||
|
||||
@@ -22,7 +22,6 @@ fn generate_dispatch_bindings() {
|
||||
.header("src/platform/mac/dispatch.h")
|
||||
.allowlist_var("_dispatch_main_q")
|
||||
.allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
|
||||
.allowlist_var("DISPATCH_QUEUE_PRIORITY_HIGH")
|
||||
.allowlist_var("DISPATCH_TIME_NOW")
|
||||
.allowlist_function("dispatch_get_global_queue")
|
||||
.allowlist_function("dispatch_async_f")
|
||||
|
||||
@@ -652,20 +652,27 @@ impl AppContext {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for window in self.windows.values() {
|
||||
if let Some(window) = window.as_ref() {
|
||||
if window.dirty {
|
||||
window.platform_window.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
for window in self
|
||||
.windows
|
||||
.values()
|
||||
.filter_map(|window| {
|
||||
let window = window.as_ref()?;
|
||||
window.dirty.get().then_some(window.handle)
|
||||
(window.dirty || window.focus_invalidated).then_some(window.handle)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
self.update_window(window, |_, cx| cx.draw()).unwrap();
|
||||
}
|
||||
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
if self.pending_effects.is_empty() {
|
||||
break;
|
||||
}
|
||||
@@ -742,7 +749,7 @@ impl AppContext {
|
||||
fn apply_refresh_effect(&mut self) {
|
||||
for window in self.windows.values_mut() {
|
||||
if let Some(window) = window.as_mut() {
|
||||
window.dirty.set(true);
|
||||
window.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,16 @@ use std::{
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// KeymatchMode controls how keybindings are resolved in the case of conflicting pending keystrokes.
|
||||
/// When `Sequenced`, gpui will wait for 1s for sequences to complete.
|
||||
/// When `Immediate`, gpui will immediately resolve the keybinding.
|
||||
#[derive(Default, PartialEq)]
|
||||
pub enum KeymatchMode {
|
||||
#[default]
|
||||
Sequenced,
|
||||
Immediate,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
pub(crate) struct DispatchNodeId(usize);
|
||||
|
||||
@@ -74,6 +84,7 @@ pub(crate) struct DispatchTree {
|
||||
keystroke_matchers: FxHashMap<SmallVec<[KeyContext; 4]>, KeystrokeMatcher>,
|
||||
keymap: Rc<RefCell<Keymap>>,
|
||||
action_registry: Rc<ActionRegistry>,
|
||||
pub(crate) keymatch_mode: KeymatchMode,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -105,6 +116,7 @@ impl DispatchTree {
|
||||
keystroke_matchers: FxHashMap::default(),
|
||||
keymap,
|
||||
action_registry,
|
||||
keymatch_mode: KeymatchMode::Sequenced,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +127,7 @@ impl DispatchTree {
|
||||
self.focusable_node_ids.clear();
|
||||
self.view_node_ids.clear();
|
||||
self.keystroke_matchers.clear();
|
||||
self.keymatch_mode = KeymatchMode::Sequenced;
|
||||
}
|
||||
|
||||
pub fn push_node(
|
||||
@@ -322,7 +335,7 @@ impl DispatchTree {
|
||||
.collect()
|
||||
}
|
||||
|
||||
// dispatch_key pushes the next keystroke into any key binding matchers.
|
||||
// dispatch_key pushses the next keystroke into any key binding matchers.
|
||||
// any matching bindings are returned in the order that they should be dispatched:
|
||||
// * First by length of binding (so if you have a binding for "b" and "ab", the "ab" binding fires first)
|
||||
// * Secondly by depth in the tree (so if Editor has a binding for "b" and workspace a
|
||||
@@ -351,11 +364,6 @@ impl DispatchTree {
|
||||
.or_insert_with(|| KeystrokeMatcher::new(self.keymap.clone()));
|
||||
|
||||
let result = keystroke_matcher.match_keystroke(keystroke, &context_stack);
|
||||
if result.pending && !pending && !bindings.is_empty() {
|
||||
context_stack.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
pending = result.pending || pending;
|
||||
for new_binding in result.bindings {
|
||||
match bindings
|
||||
|
||||
@@ -175,6 +175,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
fn on_close(&self, callback: Box<dyn FnOnce()>);
|
||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
||||
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
|
||||
fn invalidate(&self);
|
||||
fn draw(&self, scene: &Scene);
|
||||
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||
|
||||
@@ -51,7 +51,7 @@ impl PlatformDispatcher for MacDispatcher {
|
||||
fn dispatch(&self, runnable: Runnable, _: Option<TaskLabel>) {
|
||||
unsafe {
|
||||
dispatch_async_f(
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH.try_into().unwrap(), 0),
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0),
|
||||
runnable.into_raw().as_ptr() as *mut c_void,
|
||||
Some(trampoline),
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::{
|
||||
Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
|
||||
Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline,
|
||||
};
|
||||
use block::ConcreteBlock;
|
||||
use cocoa::{
|
||||
base::{NO, YES},
|
||||
foundation::NSUInteger,
|
||||
@@ -15,19 +14,17 @@ use foreign_types::ForeignType;
|
||||
use media::core_video::CVMetalTextureCache;
|
||||
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||
use objc::{self, msg_send, sel, sel_impl};
|
||||
use parking_lot::Mutex;
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc};
|
||||
use std::{ffi::c_void, mem, ptr, sync::Arc};
|
||||
|
||||
#[cfg(not(feature = "runtime_shaders"))]
|
||||
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
|
||||
#[cfg(feature = "runtime_shaders")]
|
||||
const SHADERS_SOURCE_FILE: &'static str =
|
||||
include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
|
||||
const INSTANCE_BUFFER_SIZE: usize = 2 * 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value (maybe even we could adjust dynamically...)
|
||||
const INSTANCE_BUFFER_SIZE: usize = 32 * 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value (maybe even we could adjust dynamically...)
|
||||
|
||||
pub(crate) struct MetalRenderer {
|
||||
device: metal::Device,
|
||||
layer: metal::MetalLayer,
|
||||
command_queue: CommandQueue,
|
||||
paths_rasterization_pipeline_state: metal::RenderPipelineState,
|
||||
@@ -39,14 +36,13 @@ pub(crate) struct MetalRenderer {
|
||||
polychrome_sprites_pipeline_state: metal::RenderPipelineState,
|
||||
surfaces_pipeline_state: metal::RenderPipelineState,
|
||||
unit_vertices: metal::Buffer,
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>,
|
||||
instances: metal::Buffer,
|
||||
sprite_atlas: Arc<MetalAtlas>,
|
||||
core_video_texture_cache: CVMetalTextureCache,
|
||||
}
|
||||
|
||||
impl MetalRenderer {
|
||||
pub fn new(instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>) -> Self {
|
||||
pub fn new(is_opaque: bool) -> Self {
|
||||
let device: metal::Device = if let Some(device) = metal::Device::system_default() {
|
||||
device
|
||||
} else {
|
||||
@@ -58,7 +54,7 @@ impl MetalRenderer {
|
||||
layer.set_device(&device);
|
||||
layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
|
||||
layer.set_presents_with_transaction(true);
|
||||
layer.set_opaque(true);
|
||||
layer.set_opaque(is_opaque);
|
||||
unsafe {
|
||||
let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
|
||||
let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
|
||||
@@ -97,6 +93,10 @@ impl MetalRenderer {
|
||||
mem::size_of_val(&unit_vertices) as u64,
|
||||
MTLResourceOptions::StorageModeManaged,
|
||||
);
|
||||
let instances = device.new_buffer(
|
||||
INSTANCE_BUFFER_SIZE as u64,
|
||||
MTLResourceOptions::StorageModeManaged,
|
||||
);
|
||||
|
||||
let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state(
|
||||
&device,
|
||||
@@ -165,11 +165,8 @@ impl MetalRenderer {
|
||||
|
||||
let command_queue = device.new_command_queue();
|
||||
let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
|
||||
let core_video_texture_cache =
|
||||
unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() };
|
||||
|
||||
Self {
|
||||
device,
|
||||
layer,
|
||||
command_queue,
|
||||
paths_rasterization_pipeline_state,
|
||||
@@ -181,9 +178,9 @@ impl MetalRenderer {
|
||||
polychrome_sprites_pipeline_state,
|
||||
surfaces_pipeline_state,
|
||||
unit_vertices,
|
||||
instance_buffer_pool,
|
||||
instances,
|
||||
sprite_atlas,
|
||||
core_video_texture_cache,
|
||||
core_video_texture_cache: unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,24 +208,14 @@ impl MetalRenderer {
|
||||
);
|
||||
return;
|
||||
};
|
||||
let mut instance_buffer = self.instance_buffer_pool.lock().pop().unwrap_or_else(|| {
|
||||
self.device.new_buffer(
|
||||
INSTANCE_BUFFER_SIZE as u64,
|
||||
MTLResourceOptions::StorageModeManaged,
|
||||
)
|
||||
});
|
||||
let command_queue = self.command_queue.clone();
|
||||
let command_buffer = command_queue.new_command_buffer();
|
||||
let mut instance_offset = 0;
|
||||
|
||||
let Some(path_tiles) = self.rasterize_paths(
|
||||
scene.paths(),
|
||||
&mut instance_buffer,
|
||||
&mut instance_offset,
|
||||
command_buffer,
|
||||
) else {
|
||||
log::error!("failed to rasterize {} paths", scene.paths().len());
|
||||
return;
|
||||
let Some(path_tiles) =
|
||||
self.rasterize_paths(scene.paths(), &mut instance_offset, command_buffer)
|
||||
else {
|
||||
panic!("failed to rasterize {} paths", scene.paths().len());
|
||||
};
|
||||
|
||||
let render_pass_descriptor = metal::RenderPassDescriptor::new();
|
||||
@@ -256,29 +243,22 @@ impl MetalRenderer {
|
||||
let ok = match batch {
|
||||
PrimitiveBatch::Shadows(shadows) => self.draw_shadows(
|
||||
shadows,
|
||||
&mut instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
),
|
||||
PrimitiveBatch::Quads(quads) => self.draw_quads(
|
||||
quads,
|
||||
&mut instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
),
|
||||
PrimitiveBatch::Quads(quads) => {
|
||||
self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder)
|
||||
}
|
||||
PrimitiveBatch::Paths(paths) => self.draw_paths(
|
||||
paths,
|
||||
&path_tiles,
|
||||
&mut instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
),
|
||||
PrimitiveBatch::Underlines(underlines) => self.draw_underlines(
|
||||
underlines,
|
||||
&mut instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
@@ -289,7 +269,6 @@ impl MetalRenderer {
|
||||
} => self.draw_monochrome_sprites(
|
||||
texture_id,
|
||||
sprites,
|
||||
&mut instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
@@ -300,14 +279,12 @@ impl MetalRenderer {
|
||||
} => self.draw_polychrome_sprites(
|
||||
texture_id,
|
||||
sprites,
|
||||
&mut instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
),
|
||||
PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(
|
||||
surfaces,
|
||||
&mut instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
@@ -315,7 +292,7 @@ impl MetalRenderer {
|
||||
};
|
||||
|
||||
if !ok {
|
||||
log::error!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",
|
||||
panic!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",
|
||||
scene.paths.len(),
|
||||
scene.shadows.len(),
|
||||
scene.quads.len(),
|
||||
@@ -323,39 +300,28 @@ impl MetalRenderer {
|
||||
scene.monochrome_sprites.len(),
|
||||
scene.polychrome_sprites.len(),
|
||||
scene.surfaces.len(),
|
||||
);
|
||||
break;
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
command_encoder.end_encoding();
|
||||
|
||||
instance_buffer.did_modify_range(NSRange {
|
||||
self.instances.did_modify_range(NSRange {
|
||||
location: 0,
|
||||
length: instance_offset as NSUInteger,
|
||||
});
|
||||
|
||||
let instance_buffer_pool = self.instance_buffer_pool.clone();
|
||||
let instance_buffer = Cell::new(Some(instance_buffer));
|
||||
let block = ConcreteBlock::new(move |_| {
|
||||
if let Some(instance_buffer) = instance_buffer.take() {
|
||||
instance_buffer_pool.lock().push(instance_buffer);
|
||||
}
|
||||
});
|
||||
let block = block.copy();
|
||||
command_buffer.add_completed_handler(&block);
|
||||
command_buffer.commit();
|
||||
self.sprite_atlas.clear_textures(AtlasTextureKind::Path);
|
||||
|
||||
command_buffer.wait_until_scheduled();
|
||||
command_buffer.wait_until_completed();
|
||||
drawable.present();
|
||||
}
|
||||
|
||||
fn rasterize_paths(
|
||||
&mut self,
|
||||
paths: &[Path<ScaledPixels>],
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
offset: &mut usize,
|
||||
command_buffer: &metal::CommandBufferRef,
|
||||
) -> Option<HashMap<PathId, AtlasTile>> {
|
||||
let mut tiles = HashMap::default();
|
||||
@@ -381,9 +347,9 @@ impl MetalRenderer {
|
||||
}
|
||||
|
||||
for (texture_id, vertices) in vertices_by_texture_id {
|
||||
align_offset(instance_offset);
|
||||
align_offset(offset);
|
||||
let vertices_bytes_len = mem::size_of_val(vertices.as_slice());
|
||||
let next_offset = *instance_offset + vertices_bytes_len;
|
||||
let next_offset = *offset + vertices_bytes_len;
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return None;
|
||||
}
|
||||
@@ -403,8 +369,8 @@ impl MetalRenderer {
|
||||
command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
|
||||
command_encoder.set_vertex_buffer(
|
||||
PathRasterizationInputIndex::Vertices as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
let texture_size = Size {
|
||||
width: DevicePixels::from(texture.width()),
|
||||
@@ -416,8 +382,7 @@ impl MetalRenderer {
|
||||
&texture_size as *const Size<DevicePixels> as *const _,
|
||||
);
|
||||
|
||||
let buffer_contents =
|
||||
unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) };
|
||||
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(
|
||||
vertices.as_ptr() as *const u8,
|
||||
@@ -432,7 +397,7 @@ impl MetalRenderer {
|
||||
vertices.len() as u64,
|
||||
);
|
||||
command_encoder.end_encoding();
|
||||
*instance_offset = next_offset;
|
||||
*offset = next_offset;
|
||||
}
|
||||
|
||||
Some(tiles)
|
||||
@@ -441,15 +406,14 @@ impl MetalRenderer {
|
||||
fn draw_shadows(
|
||||
&mut self,
|
||||
shadows: &[Shadow],
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) -> bool {
|
||||
if shadows.is_empty() {
|
||||
return true;
|
||||
}
|
||||
align_offset(instance_offset);
|
||||
align_offset(offset);
|
||||
|
||||
command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state);
|
||||
command_encoder.set_vertex_buffer(
|
||||
@@ -459,13 +423,13 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_vertex_buffer(
|
||||
ShadowInputIndex::Shadows as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
command_encoder.set_fragment_buffer(
|
||||
ShadowInputIndex::Shadows as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
|
||||
command_encoder.set_vertex_bytes(
|
||||
@@ -475,10 +439,9 @@ impl MetalRenderer {
|
||||
);
|
||||
|
||||
let shadow_bytes_len = mem::size_of_val(shadows);
|
||||
let buffer_contents =
|
||||
unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) };
|
||||
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
|
||||
let next_offset = *instance_offset + shadow_bytes_len;
|
||||
let next_offset = *offset + shadow_bytes_len;
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return false;
|
||||
}
|
||||
@@ -497,22 +460,21 @@ impl MetalRenderer {
|
||||
6,
|
||||
shadows.len() as u64,
|
||||
);
|
||||
*instance_offset = next_offset;
|
||||
*offset = next_offset;
|
||||
true
|
||||
}
|
||||
|
||||
fn draw_quads(
|
||||
&mut self,
|
||||
quads: &[Quad],
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) -> bool {
|
||||
if quads.is_empty() {
|
||||
return true;
|
||||
}
|
||||
align_offset(instance_offset);
|
||||
align_offset(offset);
|
||||
|
||||
command_encoder.set_render_pipeline_state(&self.quads_pipeline_state);
|
||||
command_encoder.set_vertex_buffer(
|
||||
@@ -522,13 +484,13 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_vertex_buffer(
|
||||
QuadInputIndex::Quads as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
command_encoder.set_fragment_buffer(
|
||||
QuadInputIndex::Quads as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
|
||||
command_encoder.set_vertex_bytes(
|
||||
@@ -538,10 +500,9 @@ impl MetalRenderer {
|
||||
);
|
||||
|
||||
let quad_bytes_len = mem::size_of_val(quads);
|
||||
let buffer_contents =
|
||||
unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) };
|
||||
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
|
||||
let next_offset = *instance_offset + quad_bytes_len;
|
||||
let next_offset = *offset + quad_bytes_len;
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return false;
|
||||
}
|
||||
@@ -556,7 +517,7 @@ impl MetalRenderer {
|
||||
6,
|
||||
quads.len() as u64,
|
||||
);
|
||||
*instance_offset = next_offset;
|
||||
*offset = next_offset;
|
||||
true
|
||||
}
|
||||
|
||||
@@ -564,8 +525,7 @@ impl MetalRenderer {
|
||||
&mut self,
|
||||
paths: &[Path<ScaledPixels>],
|
||||
tiles_by_path_id: &HashMap<PathId, AtlasTile>,
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) -> bool {
|
||||
@@ -613,7 +573,7 @@ impl MetalRenderer {
|
||||
if sprites.is_empty() {
|
||||
break;
|
||||
} else {
|
||||
align_offset(instance_offset);
|
||||
align_offset(offset);
|
||||
let texture_id = prev_texture_id.take().unwrap();
|
||||
let texture: metal::Texture = self.sprite_atlas.metal_texture(texture_id);
|
||||
let texture_size = size(
|
||||
@@ -623,8 +583,8 @@ impl MetalRenderer {
|
||||
|
||||
command_encoder.set_vertex_buffer(
|
||||
SpriteInputIndex::Sprites as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
command_encoder.set_vertex_bytes(
|
||||
SpriteInputIndex::AtlasTextureSize as u64,
|
||||
@@ -633,20 +593,20 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_fragment_buffer(
|
||||
SpriteInputIndex::Sprites as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
command_encoder
|
||||
.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
|
||||
|
||||
let sprite_bytes_len = mem::size_of_val(sprites.as_slice());
|
||||
let next_offset = *instance_offset + sprite_bytes_len;
|
||||
let next_offset = *offset + sprite_bytes_len;
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return false;
|
||||
}
|
||||
|
||||
let buffer_contents =
|
||||
unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) };
|
||||
unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(
|
||||
@@ -662,7 +622,7 @@ impl MetalRenderer {
|
||||
6,
|
||||
sprites.len() as u64,
|
||||
);
|
||||
*instance_offset = next_offset;
|
||||
*offset = next_offset;
|
||||
sprites.clear();
|
||||
}
|
||||
}
|
||||
@@ -672,15 +632,14 @@ impl MetalRenderer {
|
||||
fn draw_underlines(
|
||||
&mut self,
|
||||
underlines: &[Underline],
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) -> bool {
|
||||
if underlines.is_empty() {
|
||||
return true;
|
||||
}
|
||||
align_offset(instance_offset);
|
||||
align_offset(offset);
|
||||
|
||||
command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state);
|
||||
command_encoder.set_vertex_buffer(
|
||||
@@ -690,13 +649,13 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_vertex_buffer(
|
||||
UnderlineInputIndex::Underlines as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
command_encoder.set_fragment_buffer(
|
||||
UnderlineInputIndex::Underlines as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
|
||||
command_encoder.set_vertex_bytes(
|
||||
@@ -706,10 +665,9 @@ impl MetalRenderer {
|
||||
);
|
||||
|
||||
let underline_bytes_len = mem::size_of_val(underlines);
|
||||
let buffer_contents =
|
||||
unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) };
|
||||
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
|
||||
let next_offset = *instance_offset + underline_bytes_len;
|
||||
let next_offset = *offset + underline_bytes_len;
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return false;
|
||||
}
|
||||
@@ -728,7 +686,7 @@ impl MetalRenderer {
|
||||
6,
|
||||
underlines.len() as u64,
|
||||
);
|
||||
*instance_offset = next_offset;
|
||||
*offset = next_offset;
|
||||
true
|
||||
}
|
||||
|
||||
@@ -736,15 +694,14 @@ impl MetalRenderer {
|
||||
&mut self,
|
||||
texture_id: AtlasTextureId,
|
||||
sprites: &[MonochromeSprite],
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) -> bool {
|
||||
if sprites.is_empty() {
|
||||
return true;
|
||||
}
|
||||
align_offset(instance_offset);
|
||||
align_offset(offset);
|
||||
|
||||
let texture = self.sprite_atlas.metal_texture(texture_id);
|
||||
let texture_size = size(
|
||||
@@ -759,8 +716,8 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_vertex_buffer(
|
||||
SpriteInputIndex::Sprites as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
command_encoder.set_vertex_bytes(
|
||||
SpriteInputIndex::ViewportSize as u64,
|
||||
@@ -774,16 +731,15 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_fragment_buffer(
|
||||
SpriteInputIndex::Sprites as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
|
||||
|
||||
let sprite_bytes_len = mem::size_of_val(sprites);
|
||||
let buffer_contents =
|
||||
unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) };
|
||||
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
|
||||
let next_offset = *instance_offset + sprite_bytes_len;
|
||||
let next_offset = *offset + sprite_bytes_len;
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return false;
|
||||
}
|
||||
@@ -802,7 +758,7 @@ impl MetalRenderer {
|
||||
6,
|
||||
sprites.len() as u64,
|
||||
);
|
||||
*instance_offset = next_offset;
|
||||
*offset = next_offset;
|
||||
true
|
||||
}
|
||||
|
||||
@@ -810,15 +766,14 @@ impl MetalRenderer {
|
||||
&mut self,
|
||||
texture_id: AtlasTextureId,
|
||||
sprites: &[PolychromeSprite],
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) -> bool {
|
||||
if sprites.is_empty() {
|
||||
return true;
|
||||
}
|
||||
align_offset(instance_offset);
|
||||
align_offset(offset);
|
||||
|
||||
let texture = self.sprite_atlas.metal_texture(texture_id);
|
||||
let texture_size = size(
|
||||
@@ -833,8 +788,8 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_vertex_buffer(
|
||||
SpriteInputIndex::Sprites as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
command_encoder.set_vertex_bytes(
|
||||
SpriteInputIndex::ViewportSize as u64,
|
||||
@@ -848,16 +803,15 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_fragment_buffer(
|
||||
SpriteInputIndex::Sprites as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
|
||||
|
||||
let sprite_bytes_len = mem::size_of_val(sprites);
|
||||
let buffer_contents =
|
||||
unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) };
|
||||
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
|
||||
let next_offset = *instance_offset + sprite_bytes_len;
|
||||
let next_offset = *offset + sprite_bytes_len;
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return false;
|
||||
}
|
||||
@@ -876,15 +830,14 @@ impl MetalRenderer {
|
||||
6,
|
||||
sprites.len() as u64,
|
||||
);
|
||||
*instance_offset = next_offset;
|
||||
*offset = next_offset;
|
||||
true
|
||||
}
|
||||
|
||||
fn draw_surfaces(
|
||||
&mut self,
|
||||
surfaces: &[Surface],
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) -> bool {
|
||||
@@ -936,16 +889,16 @@ impl MetalRenderer {
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
align_offset(instance_offset);
|
||||
let next_offset = *instance_offset + mem::size_of::<Surface>();
|
||||
align_offset(offset);
|
||||
let next_offset = *offset + mem::size_of::<Surface>();
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return false;
|
||||
}
|
||||
|
||||
command_encoder.set_vertex_buffer(
|
||||
SurfaceInputIndex::Surfaces as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
command_encoder.set_vertex_bytes(
|
||||
SurfaceInputIndex::TextureSize as u64,
|
||||
@@ -962,8 +915,8 @@ impl MetalRenderer {
|
||||
);
|
||||
|
||||
unsafe {
|
||||
let buffer_contents = (instance_buffer.contents() as *mut u8).add(*instance_offset)
|
||||
as *mut SurfaceBounds;
|
||||
let buffer_contents =
|
||||
(self.instances.contents() as *mut u8).add(*offset) as *mut SurfaceBounds;
|
||||
ptr::write(
|
||||
buffer_contents,
|
||||
SurfaceBounds {
|
||||
@@ -974,7 +927,7 @@ impl MetalRenderer {
|
||||
}
|
||||
|
||||
command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6);
|
||||
*instance_offset = next_offset;
|
||||
*offset = next_offset;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@@ -146,7 +146,6 @@ pub(crate) struct MacPlatformState {
|
||||
foreground_executor: ForegroundExecutor,
|
||||
text_system: Arc<MacTextSystem>,
|
||||
display_linker: MacDisplayLinker,
|
||||
instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>,
|
||||
pasteboard: id,
|
||||
text_hash_pasteboard_type: id,
|
||||
metadata_pasteboard_type: id,
|
||||
@@ -177,7 +176,6 @@ impl MacPlatform {
|
||||
foreground_executor: ForegroundExecutor::new(dispatcher),
|
||||
text_system: Arc::new(MacTextSystem::new()),
|
||||
display_linker: MacDisplayLinker::new(),
|
||||
instance_buffer_pool: Arc::default(),
|
||||
pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
|
||||
text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
|
||||
metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
|
||||
@@ -496,13 +494,7 @@ impl Platform for MacPlatform {
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
) -> Box<dyn PlatformWindow> {
|
||||
let instance_buffer_pool = self.0.lock().instance_buffer_pool.clone();
|
||||
Box::new(MacWindow::open(
|
||||
handle,
|
||||
options,
|
||||
self.foreground_executor(),
|
||||
instance_buffer_pool,
|
||||
))
|
||||
Box::new(MacWindow::open(handle, options, self.foreground_executor()))
|
||||
}
|
||||
|
||||
fn set_display_link_output_callback(
|
||||
|
||||
@@ -61,16 +61,6 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
|
||||
constant Quad *quads
|
||||
[[buffer(QuadInputIndex_Quads)]]) {
|
||||
Quad quad = quads[input.quad_id];
|
||||
|
||||
// Fast path when the quad is not rounded and doesn't have any border.
|
||||
if (quad.corner_radii.top_left == 0. && quad.corner_radii.bottom_left == 0. &&
|
||||
quad.corner_radii.top_right == 0. &&
|
||||
quad.corner_radii.bottom_right == 0. && quad.border_widths.top == 0. &&
|
||||
quad.border_widths.left == 0. && quad.border_widths.right == 0. &&
|
||||
quad.border_widths.bottom == 0.) {
|
||||
return input.background_color;
|
||||
}
|
||||
|
||||
float2 half_size =
|
||||
float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
|
||||
float2 center =
|
||||
|
||||
@@ -16,8 +16,8 @@ use cocoa::{
|
||||
},
|
||||
base::{id, nil},
|
||||
foundation::{
|
||||
NSArray, NSAutoreleasePool, NSDefaultRunLoopMode, NSDictionary, NSFastEnumeration,
|
||||
NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger,
|
||||
NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSPoint, NSRect,
|
||||
NSSize, NSString, NSUInteger,
|
||||
},
|
||||
};
|
||||
use core_graphics::display::CGRect;
|
||||
@@ -168,7 +168,6 @@ unsafe fn build_classes() {
|
||||
sel!(displayLayer:),
|
||||
display_layer as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(sel!(step:), step as extern "C" fn(&Object, Sel, id));
|
||||
|
||||
decl.add_protocol(Protocol::get("NSTextInputClient").unwrap());
|
||||
decl.add_method(
|
||||
@@ -261,10 +260,6 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C
|
||||
sel!(windowDidMove:),
|
||||
window_did_move as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(windowDidChangeScreen:),
|
||||
window_did_change_screen as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(windowDidBecomeKey:),
|
||||
window_did_change_key_status as extern "C" fn(&Object, Sel, id),
|
||||
@@ -325,8 +320,7 @@ struct MacWindowState {
|
||||
handle: AnyWindowHandle,
|
||||
executor: ForegroundExecutor,
|
||||
native_window: id,
|
||||
native_view: NonNull<Object>,
|
||||
display_link: id,
|
||||
native_view: NonNull<id>,
|
||||
renderer: MetalRenderer,
|
||||
kind: WindowKind,
|
||||
request_frame_callback: Option<Box<dyn FnMut()>>,
|
||||
@@ -464,7 +458,6 @@ impl MacWindow {
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
executor: ForegroundExecutor,
|
||||
instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>,
|
||||
) -> Self {
|
||||
unsafe {
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
@@ -528,17 +521,15 @@ impl MacWindow {
|
||||
|
||||
let native_view: id = msg_send![VIEW_CLASS, alloc];
|
||||
let native_view = NSView::init(native_view);
|
||||
assert!(!native_view.is_null());
|
||||
|
||||
let display_link = start_display_link(native_window.screen(), native_view);
|
||||
assert!(!native_view.is_null());
|
||||
|
||||
let window = Self(Arc::new(Mutex::new(MacWindowState {
|
||||
handle,
|
||||
executor,
|
||||
native_window,
|
||||
native_view: NonNull::new_unchecked(native_view),
|
||||
display_link,
|
||||
renderer: MetalRenderer::new(instance_buffer_pool),
|
||||
native_view: NonNull::new_unchecked(native_view as *mut _),
|
||||
renderer: MetalRenderer::new(true),
|
||||
kind: options.kind,
|
||||
request_frame_callback: None,
|
||||
event_callback: None,
|
||||
@@ -672,7 +663,6 @@ impl MacWindow {
|
||||
}
|
||||
|
||||
window.0.lock().move_traffic_light();
|
||||
|
||||
pool.drain();
|
||||
|
||||
window
|
||||
@@ -693,19 +683,10 @@ impl MacWindow {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn start_display_link(native_screen: id, native_view: id) -> id {
|
||||
let display_link: id =
|
||||
msg_send![native_screen, displayLinkWithTarget: native_view selector: sel!(step:)];
|
||||
let main_run_loop: id = msg_send![class!(NSRunLoop), mainRunLoop];
|
||||
let _: () = msg_send![display_link, addToRunLoop: main_run_loop forMode: NSDefaultRunLoopMode];
|
||||
display_link
|
||||
}
|
||||
|
||||
impl Drop for MacWindow {
|
||||
fn drop(&mut self) {
|
||||
let mut this = self.0.lock();
|
||||
let this = self.0.lock();
|
||||
let window = this.native_window;
|
||||
this.display_link = nil;
|
||||
this.executor
|
||||
.spawn(async move {
|
||||
unsafe {
|
||||
@@ -1019,6 +1000,13 @@ impl PlatformWindow for MacWindow {
|
||||
}
|
||||
}
|
||||
|
||||
fn invalidate(&self) {
|
||||
let this = self.0.lock();
|
||||
unsafe {
|
||||
let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES];
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&self, scene: &crate::Scene) {
|
||||
let mut this = self.0.lock();
|
||||
this.renderer.draw(scene);
|
||||
@@ -1365,19 +1353,6 @@ extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
unsafe {
|
||||
let screen = lock.native_window.screen();
|
||||
if screen == nil {
|
||||
lock.display_link = nil;
|
||||
} else {
|
||||
lock.display_link = start_display_link(screen, lock.native_view.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let lock = window_state.lock();
|
||||
@@ -1527,23 +1502,6 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn step(this: &Object, _: Sel, display_link: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let mut lock = window_state.lock();
|
||||
|
||||
if lock.display_link == display_link {
|
||||
if let Some(mut callback) = lock.request_frame_callback.take() {
|
||||
drop(lock);
|
||||
callback();
|
||||
window_state.lock().request_frame_callback = Some(callback);
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
let _: () = msg_send![display_link, invalidate];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
|
||||
unsafe { msg_send![class!(NSArray), array] }
|
||||
}
|
||||
|
||||
@@ -284,6 +284,8 @@ impl PlatformWindow for TestWindow {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn invalidate(&self) {}
|
||||
|
||||
fn draw(&self, _scene: &crate::Scene) {}
|
||||
|
||||
fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
|
||||
|
||||
@@ -15,7 +15,6 @@ use crate::{
|
||||
use anyhow::anyhow;
|
||||
use collections::{BTreeSet, FxHashMap, FxHashSet};
|
||||
use core::fmt;
|
||||
use derive_more::Deref;
|
||||
use itertools::Itertools;
|
||||
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
@@ -39,8 +38,9 @@ pub struct FontFamilyId(pub usize);
|
||||
|
||||
pub(crate) const SUBPIXEL_VARIANTS: u8 = 4;
|
||||
|
||||
/// The GPUI text rendering sub system.
|
||||
/// The GPUI text layout and rendering sub system.
|
||||
pub struct TextSystem {
|
||||
line_layout_cache: Arc<LineLayoutCache>,
|
||||
platform_text_system: Arc<dyn PlatformTextSystem>,
|
||||
font_ids_by_font: RwLock<FxHashMap<Font, Result<FontId>>>,
|
||||
font_metrics: RwLock<FxHashMap<FontId, FontMetrics>>,
|
||||
@@ -53,6 +53,7 @@ pub struct TextSystem {
|
||||
impl TextSystem {
|
||||
pub(crate) fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
|
||||
TextSystem {
|
||||
line_layout_cache: Arc::new(LineLayoutCache::new(platform_text_system.clone())),
|
||||
platform_text_system,
|
||||
font_metrics: RwLock::default(),
|
||||
raster_bounds: RwLock::default(),
|
||||
@@ -233,68 +234,45 @@ impl TextSystem {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a handle to a line wrapper, for the given font and font size.
|
||||
pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
|
||||
let lock = &mut self.wrapper_pool.lock();
|
||||
let font_id = self.resolve_font(&font);
|
||||
let wrappers = lock
|
||||
.entry(FontIdWithSize { font_id, font_size })
|
||||
.or_default();
|
||||
let wrapper = wrappers.pop().unwrap_or_else(|| {
|
||||
LineWrapper::new(font_id, font_size, self.platform_text_system.clone())
|
||||
});
|
||||
|
||||
LineWrapperHandle {
|
||||
wrapper: Some(wrapper),
|
||||
text_system: self.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the rasterized size and location of a specific, rendered glyph.
|
||||
pub(crate) fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
||||
let raster_bounds = self.raster_bounds.upgradable_read();
|
||||
if let Some(bounds) = raster_bounds.get(params) {
|
||||
Ok(*bounds)
|
||||
} else {
|
||||
let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds);
|
||||
let bounds = self.platform_text_system.glyph_raster_bounds(params)?;
|
||||
raster_bounds.insert(params.clone(), bounds);
|
||||
Ok(bounds)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn rasterize_glyph(
|
||||
&self,
|
||||
params: &RenderGlyphParams,
|
||||
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
|
||||
let raster_bounds = self.raster_bounds(params)?;
|
||||
self.platform_text_system
|
||||
.rasterize_glyph(params, raster_bounds)
|
||||
}
|
||||
}
|
||||
|
||||
/// The GPUI text layout subsystem.
|
||||
#[derive(Deref)]
|
||||
pub struct WindowTextSystem {
|
||||
line_layout_cache: Arc<LineLayoutCache>,
|
||||
#[deref]
|
||||
text_system: Arc<TextSystem>,
|
||||
}
|
||||
|
||||
impl WindowTextSystem {
|
||||
pub(crate) fn new(text_system: Arc<TextSystem>) -> Self {
|
||||
Self {
|
||||
line_layout_cache: Arc::new(LineLayoutCache::new(
|
||||
text_system.platform_text_system.clone(),
|
||||
)),
|
||||
text_system,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_view<R>(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R {
|
||||
self.line_layout_cache.with_view(view_id, f)
|
||||
}
|
||||
|
||||
/// Layout the given line of text, at the given font_size.
|
||||
/// Subsets of the line can be styled independently with the `runs` parameter.
|
||||
/// Generally, you should prefer to use `TextLayout::shape_line` instead, which
|
||||
/// can be painted directly.
|
||||
pub fn layout_line(
|
||||
&self,
|
||||
text: &str,
|
||||
font_size: Pixels,
|
||||
runs: &[TextRun],
|
||||
) -> 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.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;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
font_runs.push(FontRun {
|
||||
len: run.len,
|
||||
font_id,
|
||||
});
|
||||
}
|
||||
|
||||
let layout = self
|
||||
.line_layout_cache
|
||||
.layout_line(text, font_size, &font_runs);
|
||||
|
||||
font_runs.clear();
|
||||
self.font_runs_pool.lock().push(font_runs);
|
||||
|
||||
Ok(layout)
|
||||
}
|
||||
|
||||
/// Shape the given line, at the given font_size, for painting to the screen.
|
||||
/// Subsets of the line can be styled independently with the `runs` parameter.
|
||||
///
|
||||
@@ -451,39 +429,43 @@ impl WindowTextSystem {
|
||||
self.line_layout_cache.finish_frame(reused_views)
|
||||
}
|
||||
|
||||
/// Layout the given line of text, at the given font_size.
|
||||
/// Subsets of the line can be styled independently with the `runs` parameter.
|
||||
/// Generally, you should prefer to use `TextLayout::shape_line` instead, which
|
||||
/// can be painted directly.
|
||||
pub fn layout_line(
|
||||
&self,
|
||||
text: &str,
|
||||
font_size: Pixels,
|
||||
runs: &[TextRun],
|
||||
) -> 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.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;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
font_runs.push(FontRun {
|
||||
len: run.len,
|
||||
font_id,
|
||||
});
|
||||
/// Returns a handle to a line wrapper, for the given font and font size.
|
||||
pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
|
||||
let lock = &mut self.wrapper_pool.lock();
|
||||
let font_id = self.resolve_font(&font);
|
||||
let wrappers = lock
|
||||
.entry(FontIdWithSize { font_id, font_size })
|
||||
.or_default();
|
||||
let wrapper = wrappers.pop().unwrap_or_else(|| {
|
||||
LineWrapper::new(font_id, font_size, self.platform_text_system.clone())
|
||||
});
|
||||
|
||||
LineWrapperHandle {
|
||||
wrapper: Some(wrapper),
|
||||
text_system: self.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
let layout = self
|
||||
.line_layout_cache
|
||||
.layout_line(text, font_size, &font_runs);
|
||||
/// Get the rasterized size and location of a specific, rendered glyph.
|
||||
pub(crate) fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
||||
let raster_bounds = self.raster_bounds.upgradable_read();
|
||||
if let Some(bounds) = raster_bounds.get(params) {
|
||||
Ok(*bounds)
|
||||
} else {
|
||||
let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds);
|
||||
let bounds = self.platform_text_system.glyph_raster_bounds(params)?;
|
||||
raster_bounds.insert(params.clone(), bounds);
|
||||
Ok(bounds)
|
||||
}
|
||||
}
|
||||
|
||||
font_runs.clear();
|
||||
self.font_runs_pool.lock().push(font_runs);
|
||||
|
||||
Ok(layout)
|
||||
pub(crate) fn rasterize_glyph(
|
||||
&self,
|
||||
params: &RenderGlyphParams,
|
||||
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
|
||||
let raster_bounds = self.raster_bounds(params)?;
|
||||
self.platform_text_system
|
||||
.rasterize_glyph(params, raster_bounds)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ impl Boundary {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{font, TestAppContext, TestDispatcher, TextRun, WindowTextSystem, WrapBoundary};
|
||||
use crate::{font, TestAppContext, TestDispatcher, TextRun, WrapBoundary};
|
||||
use rand::prelude::*;
|
||||
|
||||
#[test]
|
||||
@@ -218,7 +218,7 @@ mod tests {
|
||||
#[crate::test]
|
||||
fn test_wrap_shaped_line(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let text_system = WindowTextSystem::new(cx.text_system().clone());
|
||||
let text_system = cx.text_system().clone();
|
||||
|
||||
let normal = TextRun {
|
||||
len: 0,
|
||||
|
||||
@@ -2,12 +2,12 @@ use crate::{
|
||||
px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext,
|
||||
AvailableSpace, Bounds, Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId,
|
||||
DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten,
|
||||
Global, GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchResult,
|
||||
Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent,
|
||||
MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
|
||||
PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription,
|
||||
TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowBounds, WindowOptions,
|
||||
WindowTextSystem,
|
||||
Global, GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchMode,
|
||||
KeymatchResult, Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton,
|
||||
MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
||||
PlatformWindow, Point, PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet,
|
||||
Subscription, TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowBounds,
|
||||
WindowOptions,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::FxHashSet;
|
||||
@@ -22,7 +22,7 @@ use smallvec::SmallVec;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
borrow::{Borrow, BorrowMut},
|
||||
cell::{Cell, RefCell},
|
||||
cell::RefCell,
|
||||
collections::hash_map::Entry,
|
||||
fmt::{Debug, Display},
|
||||
future::Future,
|
||||
@@ -34,7 +34,7 @@ use std::{
|
||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||
Arc,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
time::Duration,
|
||||
};
|
||||
use util::{measure, ResultExt};
|
||||
|
||||
@@ -251,7 +251,6 @@ pub struct Window {
|
||||
pub(crate) platform_window: Box<dyn PlatformWindow>,
|
||||
display_id: DisplayId,
|
||||
sprite_atlas: Arc<dyn PlatformAtlas>,
|
||||
text_system: Arc<WindowTextSystem>,
|
||||
pub(crate) rem_size: Pixels,
|
||||
pub(crate) viewport_size: Size<Pixels>,
|
||||
layout_engine: Option<TaffyLayoutEngine>,
|
||||
@@ -269,15 +268,17 @@ pub struct Window {
|
||||
scale_factor: f32,
|
||||
bounds: WindowBounds,
|
||||
bounds_observers: SubscriberSet<(), AnyObserver>,
|
||||
active: Rc<Cell<bool>>,
|
||||
pub(crate) dirty: Rc<Cell<bool>>,
|
||||
pub(crate) last_input_timestamp: Rc<Cell<Instant>>,
|
||||
active: bool,
|
||||
pub(crate) dirty: bool,
|
||||
pub(crate) refreshing: bool,
|
||||
pub(crate) drawing: bool,
|
||||
activation_observers: SubscriberSet<(), AnyObserver>,
|
||||
pub(crate) focus: Option<FocusId>,
|
||||
focus_enabled: bool,
|
||||
pending_input: Option<PendingInput>,
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) focus_invalidated: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
@@ -289,6 +290,10 @@ struct PendingInput {
|
||||
}
|
||||
|
||||
impl PendingInput {
|
||||
fn is_noop(&self) -> bool {
|
||||
self.bindings.is_empty() && (self.keystrokes.iter().all(|k| k.ime_key.is_none()))
|
||||
}
|
||||
|
||||
fn input(&self) -> String {
|
||||
self.keystrokes
|
||||
.iter()
|
||||
@@ -332,34 +337,13 @@ impl Window {
|
||||
let content_size = platform_window.content_size();
|
||||
let scale_factor = platform_window.scale_factor();
|
||||
let bounds = platform_window.bounds();
|
||||
let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone()));
|
||||
let dirty = Rc::new(Cell::new(true));
|
||||
let active = Rc::new(Cell::new(false));
|
||||
let last_input_timestamp = Rc::new(Cell::new(Instant::now()));
|
||||
|
||||
platform_window.on_request_frame(Box::new({
|
||||
let mut cx = cx.to_async();
|
||||
let dirty = dirty.clone();
|
||||
let active = active.clone();
|
||||
let last_input_timestamp = last_input_timestamp.clone();
|
||||
move || {
|
||||
if dirty.get() {
|
||||
measure("frame duration", || {
|
||||
handle
|
||||
.update(&mut cx, |_, cx| {
|
||||
cx.draw();
|
||||
cx.present();
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
}
|
||||
// Keep presenting the current scene for 1 extra second since the
|
||||
// last input to prevent the display from underclocking the refresh rate.
|
||||
else if active.get()
|
||||
&& last_input_timestamp.get().elapsed() < Duration::from_secs(1)
|
||||
{
|
||||
handle.update(&mut cx, |_, cx| cx.present()).log_err();
|
||||
}
|
||||
measure("frame duration", || {
|
||||
handle.update(&mut cx, |_, cx| cx.draw()).log_err();
|
||||
})
|
||||
}
|
||||
}));
|
||||
platform_window.on_resize(Box::new({
|
||||
@@ -383,7 +367,7 @@ impl Window {
|
||||
move |active| {
|
||||
handle
|
||||
.update(&mut cx, |_, cx| {
|
||||
cx.window.active.set(active);
|
||||
cx.window.active = active;
|
||||
cx.window
|
||||
.activation_observers
|
||||
.clone()
|
||||
@@ -409,7 +393,6 @@ impl Window {
|
||||
platform_window,
|
||||
display_id,
|
||||
sprite_atlas,
|
||||
text_system,
|
||||
rem_size: px(16.),
|
||||
viewport_size: content_size,
|
||||
layout_engine: Some(TaffyLayoutEngine::new()),
|
||||
@@ -427,15 +410,17 @@ impl Window {
|
||||
scale_factor,
|
||||
bounds,
|
||||
bounds_observers: SubscriberSet::new(),
|
||||
active,
|
||||
dirty,
|
||||
last_input_timestamp,
|
||||
active: false,
|
||||
dirty: false,
|
||||
refreshing: false,
|
||||
drawing: false,
|
||||
activation_observers: SubscriberSet::new(),
|
||||
focus: None,
|
||||
focus_enabled: true,
|
||||
pending_input: None,
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
focus_invalidated: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -487,7 +472,7 @@ impl<'a> WindowContext<'a> {
|
||||
pub fn refresh(&mut self) {
|
||||
if !self.window.drawing {
|
||||
self.window.refreshing = true;
|
||||
self.window.dirty.set(true);
|
||||
self.window.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,6 +505,12 @@ impl<'a> WindowContext<'a> {
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.clear_pending_keystrokes();
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
{
|
||||
self.window.focus_invalidated = true;
|
||||
}
|
||||
|
||||
self.refresh();
|
||||
}
|
||||
|
||||
@@ -539,11 +530,6 @@ impl<'a> WindowContext<'a> {
|
||||
self.window.focus_enabled = false;
|
||||
}
|
||||
|
||||
/// Accessor for the text system.
|
||||
pub fn text_system(&self) -> &Arc<WindowTextSystem> {
|
||||
&self.window.text_system
|
||||
}
|
||||
|
||||
/// Dispatch the given action on the currently focused element.
|
||||
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
|
||||
let focus_handle = self.focused();
|
||||
@@ -755,7 +741,7 @@ impl<'a> WindowContext<'a> {
|
||||
|
||||
/// Returns whether this window is focused by the operating system (receiving key events).
|
||||
pub fn is_window_active(&self) -> bool {
|
||||
self.window.active.get()
|
||||
self.window.active
|
||||
}
|
||||
|
||||
/// Toggle zoom on the window.
|
||||
@@ -941,12 +927,16 @@ impl<'a> WindowContext<'a> {
|
||||
&self.window.next_frame.z_index_stack
|
||||
}
|
||||
|
||||
/// Produces a new frame and assigns it to `rendered_frame`. To actually show
|
||||
/// the contents of the new [Scene], use [present].
|
||||
/// Draw pixels to the display for this window based on the contents of its scene.
|
||||
pub(crate) fn draw(&mut self) {
|
||||
self.window.dirty.set(false);
|
||||
self.window.dirty = false;
|
||||
self.window.drawing = true;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
{
|
||||
self.window.focus_invalidated = false;
|
||||
}
|
||||
|
||||
if let Some(requested_handler) = self.window.rendered_frame.requested_input_handler.as_mut()
|
||||
{
|
||||
let input_handler = self.window.platform_window.take_input_handler();
|
||||
@@ -1011,7 +1001,7 @@ impl<'a> WindowContext<'a> {
|
||||
self.window.focus,
|
||||
);
|
||||
self.window.next_frame.focus = self.window.focus;
|
||||
self.window.next_frame.window_active = self.window.active.get();
|
||||
self.window.next_frame.window_active = self.window.active;
|
||||
self.window.root_view = Some(root_view);
|
||||
|
||||
// Set the cursor only if we're the active window.
|
||||
@@ -1080,19 +1070,16 @@ impl<'a> WindowContext<'a> {
|
||||
.clone()
|
||||
.retain(&(), |listener| listener(&event, self));
|
||||
}
|
||||
|
||||
self.window
|
||||
.platform_window
|
||||
.draw(&self.window.rendered_frame.scene);
|
||||
self.window.refreshing = false;
|
||||
self.window.drawing = false;
|
||||
}
|
||||
|
||||
fn present(&self) {
|
||||
self.window
|
||||
.platform_window
|
||||
.draw(&self.window.rendered_frame.scene);
|
||||
}
|
||||
|
||||
/// Dispatch a mouse or keyboard event on the window.
|
||||
pub fn dispatch_event(&mut self, event: PlatformInput) -> bool {
|
||||
self.window.last_input_timestamp.set(Instant::now());
|
||||
// Handlers may set this to false by calling `stop_propagation`.
|
||||
self.app.propagate_event = true;
|
||||
// Handlers may set this to true by calling `prevent_default`.
|
||||
@@ -1256,12 +1243,21 @@ impl<'a> WindowContext<'a> {
|
||||
.dispatch_path(node_id);
|
||||
|
||||
if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
|
||||
let KeymatchResult { bindings, pending } = self
|
||||
let KeymatchResult {
|
||||
bindings,
|
||||
mut pending,
|
||||
} = self
|
||||
.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.dispatch_key(&key_down_event.keystroke, &dispatch_path);
|
||||
|
||||
if self.window.rendered_frame.dispatch_tree.keymatch_mode == KeymatchMode::Immediate
|
||||
&& !bindings.is_empty()
|
||||
{
|
||||
pending = false;
|
||||
}
|
||||
|
||||
if pending {
|
||||
let mut currently_pending = self.window.pending_input.take().unwrap_or_default();
|
||||
if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus
|
||||
@@ -1276,17 +1272,22 @@ impl<'a> WindowContext<'a> {
|
||||
currently_pending.bindings.push(binding);
|
||||
}
|
||||
|
||||
currently_pending.timer = Some(self.spawn(|mut cx| async move {
|
||||
cx.background_executor.timer(Duration::from_secs(1)).await;
|
||||
cx.update(move |cx| {
|
||||
cx.clear_pending_keystrokes();
|
||||
let Some(currently_pending) = cx.window.pending_input.take() else {
|
||||
return;
|
||||
};
|
||||
cx.replay_pending_input(currently_pending)
|
||||
})
|
||||
.log_err();
|
||||
}));
|
||||
// for vim compatibility, we also should check "is input handler enabled"
|
||||
if !currently_pending.is_noop() {
|
||||
currently_pending.timer = Some(self.spawn(|mut cx| async move {
|
||||
cx.background_executor.timer(Duration::from_secs(1)).await;
|
||||
cx.update(move |cx| {
|
||||
cx.clear_pending_keystrokes();
|
||||
let Some(currently_pending) = cx.window.pending_input.take() else {
|
||||
return;
|
||||
};
|
||||
cx.replay_pending_input(currently_pending)
|
||||
})
|
||||
.log_err();
|
||||
}));
|
||||
} else {
|
||||
currently_pending.timer = None;
|
||||
}
|
||||
self.window.pending_input = Some(currently_pending);
|
||||
|
||||
self.propagate_event = false;
|
||||
@@ -1314,21 +1315,8 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
self.dispatch_key_down_up_event(event, &dispatch_path);
|
||||
if !self.propagate_event {
|
||||
return;
|
||||
}
|
||||
|
||||
self.dispatch_keystroke_observers(event, None);
|
||||
}
|
||||
|
||||
fn dispatch_key_down_up_event(
|
||||
&mut self,
|
||||
event: &dyn Any,
|
||||
dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
|
||||
) {
|
||||
// Capture phase
|
||||
for node_id in dispatch_path {
|
||||
for node_id in &dispatch_path {
|
||||
let node = self.window.rendered_frame.dispatch_tree.node(*node_id);
|
||||
|
||||
for key_listener in node.key_listeners.clone() {
|
||||
@@ -1354,6 +1342,8 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.dispatch_keystroke_observers(event, None);
|
||||
}
|
||||
|
||||
/// Determine whether a potential multi-stroke key binding is in progress on this window.
|
||||
@@ -1390,24 +1380,6 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let dispatch_path = self
|
||||
.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.dispatch_path(node_id);
|
||||
|
||||
for keystroke in currently_pending.keystrokes {
|
||||
let event = KeyDownEvent {
|
||||
keystroke,
|
||||
is_held: false,
|
||||
};
|
||||
|
||||
self.dispatch_key_down_up_event(&event, &dispatch_path);
|
||||
if !self.propagate_event {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if !input.is_empty() {
|
||||
if let Some(mut input_handler) = self.window.platform_window.take_input_handler() {
|
||||
input_handler.flush_pending_input(&input, self);
|
||||
@@ -2051,7 +2023,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
}
|
||||
|
||||
if !self.window.drawing {
|
||||
self.window_cx.window.dirty.set(true);
|
||||
self.window_cx.window.dirty = true;
|
||||
self.window_cx.app.push_effect(Effect::Notify {
|
||||
emitter: self.view.model.entity_id,
|
||||
});
|
||||
@@ -2517,7 +2489,7 @@ impl<V: 'static + Render> WindowHandle<V> {
|
||||
pub fn is_active(&self, cx: &AppContext) -> Option<bool> {
|
||||
cx.windows
|
||||
.get(self.id)
|
||||
.and_then(|window| window.as_ref().map(|window| window.active.get()))
|
||||
.and_then(|window| window.as_ref().map(|window| window.active))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,11 +31,11 @@ use crate::{
|
||||
prelude::*, size, AnyTooltip, AppContext, AvailableSpace, Bounds, BoxShadow, ContentMask,
|
||||
Corners, CursorStyle, DevicePixels, DispatchPhase, DispatchTree, ElementId, ElementStateBox,
|
||||
EntityId, FocusHandle, FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData,
|
||||
InputHandler, IsZero, KeyContext, KeyEvent, LayoutId, MonochromeSprite, MouseEvent, PaintQuad,
|
||||
Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, RenderGlyphParams,
|
||||
RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, StackingContext,
|
||||
StackingOrder, Style, Surface, TextStyleRefinement, Underline, UnderlineStyle, Window,
|
||||
WindowContext, SUBPIXEL_VARIANTS,
|
||||
InputHandler, IsZero, KeyContext, KeyEvent, KeymatchMode, LayoutId, MonochromeSprite,
|
||||
MouseEvent, PaintQuad, Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad,
|
||||
RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size,
|
||||
StackingContext, StackingOrder, Style, Surface, TextStyleRefinement, Underline, UnderlineStyle,
|
||||
Window, WindowContext, SUBPIXEL_VARIANTS,
|
||||
};
|
||||
|
||||
type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut ElementContext) + 'static>;
|
||||
@@ -1143,6 +1143,15 @@ impl<'a> ElementContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// keymatch mode immediate instructs GPUI to prefer shorter action bindings.
|
||||
/// In the case that you have a keybinding of `"cmd-k": "terminal::Clear"` and
|
||||
/// `"cmd-k left": "workspace::MoveLeft"`, GPUI will by default wait for 1s after
|
||||
/// you type cmd-k to see if you're going to type left.
|
||||
/// This is problematic in the terminal
|
||||
pub fn keymatch_mode_immediate(&mut self) {
|
||||
self.window.next_frame.dispatch_tree.keymatch_mode = KeymatchMode::Immediate;
|
||||
}
|
||||
|
||||
/// Register a mouse event listener on the window for the next frame. The type of event
|
||||
/// is determined by the first parameter of the given listener. When the next frame is rendered
|
||||
/// the listener will be cleared.
|
||||
|
||||
@@ -4,8 +4,8 @@ use gpui::{
|
||||
ElementContext, ElementId, FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hsla,
|
||||
InputHandler, InteractiveBounds, InteractiveElement, InteractiveElementState, Interactivity,
|
||||
IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, MouseMoveEvent,
|
||||
Pixels, Point, ShapedLine, StatefulInteractiveElement, Styled, TextRun, TextStyle,
|
||||
UnderlineStyle, WeakView, WhiteSpace, WindowContext, WindowTextSystem,
|
||||
Pixels, Point, ShapedLine, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem,
|
||||
UnderlineStyle, WeakView, WhiteSpace, WindowContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::CursorShape;
|
||||
@@ -185,7 +185,7 @@ impl TerminalElement {
|
||||
grid: &Vec<IndexedCell>,
|
||||
text_style: &TextStyle,
|
||||
// terminal_theme: &TerminalStyle,
|
||||
text_system: &WindowTextSystem,
|
||||
text_system: &TextSystem,
|
||||
hyperlink: Option<(HighlightStyle, &RangeInclusive<AlacPoint>)>,
|
||||
cx: &WindowContext<'_>,
|
||||
) -> (Vec<LayoutCell>, Vec<LayoutRect>) {
|
||||
@@ -776,6 +776,7 @@ impl Element for TerminalElement {
|
||||
self.interactivity
|
||||
.paint(bounds, bounds.size, state, cx, |_, _, cx| {
|
||||
cx.handle_input(&self.focus, terminal_input_handler);
|
||||
cx.keymatch_mode_immediate();
|
||||
|
||||
cx.on_key_event({
|
||||
let this = self.terminal.clone();
|
||||
|
||||
@@ -255,23 +255,19 @@ impl ThemeRegistry {
|
||||
continue;
|
||||
};
|
||||
|
||||
self.load_user_theme(&theme_path, fs.clone())
|
||||
.await
|
||||
.log_err();
|
||||
let Some(reader) = fs.open_sync(&theme_path).await.log_err() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(theme) = serde_json_lenient::from_reader(reader).log_err() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
self.insert_user_theme_families([theme]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Loads the user theme from the specified path and adds it to the registry.
|
||||
pub async fn load_user_theme(&self, theme_path: &Path, fs: Arc<dyn Fs>) -> Result<()> {
|
||||
let reader = fs.open_sync(&theme_path).await?;
|
||||
let theme = serde_json_lenient::from_reader(reader)?;
|
||||
|
||||
self.insert_user_theme_families([theme]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ThemeRegistry {
|
||||
|
||||
@@ -68,6 +68,6 @@ pub async fn latest_github_release(
|
||||
|
||||
releases
|
||||
.into_iter()
|
||||
.find(|release| !release.assets.is_empty() && release.pre_release == pre_release)
|
||||
.find(|release| release.pre_release == pre_release)
|
||||
.ok_or(anyhow!("Failed to find a release"))
|
||||
}
|
||||
|
||||
@@ -204,7 +204,8 @@ impl Vim {
|
||||
let editor = editor.read(cx);
|
||||
if editor.leader_peer_id().is_none() {
|
||||
let newest = editor.selections.newest::<usize>(cx);
|
||||
local_selections_changed(newest, cx);
|
||||
let is_multicursor = editor.selections.count() > 1;
|
||||
local_selections_changed(newest, is_multicursor, cx);
|
||||
}
|
||||
}
|
||||
EditorEvent::InputIgnored { text } => {
|
||||
@@ -626,13 +627,24 @@ impl Settings for VimModeSetting {
|
||||
}
|
||||
}
|
||||
|
||||
fn local_selections_changed(newest: Selection<usize>, cx: &mut WindowContext) {
|
||||
fn local_selections_changed(
|
||||
newest: Selection<usize>,
|
||||
is_multicursor: bool,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
if vim.enabled && vim.state().mode == Mode::Normal && !newest.is_empty() {
|
||||
if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
|
||||
vim.switch_mode(Mode::VisualBlock, false, cx);
|
||||
} else {
|
||||
vim.switch_mode(Mode::Visual, false, cx)
|
||||
if vim.enabled {
|
||||
if vim.state().mode == Mode::Normal && !newest.is_empty() {
|
||||
if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
|
||||
vim.switch_mode(Mode::VisualBlock, false, cx);
|
||||
} else {
|
||||
vim.switch_mode(Mode::Visual, false, cx)
|
||||
}
|
||||
} else if newest.is_empty()
|
||||
&& !is_multicursor
|
||||
&& [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&vim.state().mode)
|
||||
{
|
||||
vim.switch_mode(Mode::Normal, true, cx)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition = "2021"
|
||||
name = "zed"
|
||||
version = "0.121.4"
|
||||
version = "0.122.0"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
stable
|
||||
dev
|
||||
@@ -11,7 +11,6 @@ use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::Editor;
|
||||
use env_logger::Builder;
|
||||
use fs::RealFs;
|
||||
use fsevent::StreamFlags;
|
||||
use futures::StreamExt;
|
||||
use gpui::{App, AppContext, AsyncAppContext, Context, SemanticVersion, Task};
|
||||
use isahc::{prelude::Configurable, Request};
|
||||
@@ -172,8 +171,35 @@ fn main() {
|
||||
);
|
||||
assistant::init(cx);
|
||||
|
||||
load_user_themes_in_background(fs.clone(), cx);
|
||||
watch_themes(fs.clone(), cx);
|
||||
// TODO: Should we be loading the themes in a different spot?
|
||||
cx.spawn({
|
||||
let fs = fs.clone();
|
||||
|cx| async move {
|
||||
if let Some(theme_registry) =
|
||||
cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
|
||||
{
|
||||
if let Some(()) = theme_registry
|
||||
.load_user_themes(&paths::THEMES_DIR.clone(), fs)
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
cx.update(|cx| {
|
||||
let mut theme_settings = ThemeSettings::get_global(cx).clone();
|
||||
|
||||
if let Some(requested_theme) = theme_settings.requested_theme.clone() {
|
||||
if let Some(_theme) =
|
||||
theme_settings.switch_theme(&requested_theme, cx)
|
||||
{
|
||||
ThemeSettings::override_global(theme_settings, cx);
|
||||
}
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.spawn(|_| watch_languages(fs.clone(), languages.clone()))
|
||||
.detach();
|
||||
@@ -873,81 +899,6 @@ fn load_embedded_fonts(cx: &AppContext) {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Spawns a background task to load the user themes from the themes directory.
|
||||
fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
|
||||
cx.spawn({
|
||||
let fs = fs.clone();
|
||||
|cx| async move {
|
||||
if let Some(theme_registry) =
|
||||
cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
|
||||
{
|
||||
if let Some(()) = theme_registry
|
||||
.load_user_themes(&paths::THEMES_DIR.clone(), fs)
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
cx.update(|cx| {
|
||||
let mut theme_settings = ThemeSettings::get_global(cx).clone();
|
||||
|
||||
if let Some(requested_theme) = theme_settings.requested_theme.clone() {
|
||||
if let Some(_theme) = theme_settings.switch_theme(&requested_theme, cx)
|
||||
{
|
||||
ThemeSettings::override_global(theme_settings, cx);
|
||||
}
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Spawns a background task to watch the themes directory for changes.
|
||||
fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
|
||||
cx.spawn(|cx| async move {
|
||||
let mut events = fs
|
||||
.watch(&paths::THEMES_DIR.clone(), Duration::from_millis(100))
|
||||
.await;
|
||||
|
||||
while let Some(events) = events.next().await {
|
||||
for event in events {
|
||||
if event.flags.contains(StreamFlags::ITEM_REMOVED) {
|
||||
// Theme was removed, don't need to reload.
|
||||
// We may want to remove the theme from the registry, in this case.
|
||||
} else {
|
||||
if let Some(theme_registry) =
|
||||
cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
|
||||
{
|
||||
if let Some(()) = theme_registry
|
||||
.load_user_theme(&event.path, fs.clone())
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
cx.update(|cx| {
|
||||
let mut theme_settings = ThemeSettings::get_global(cx).clone();
|
||||
|
||||
if let Some(requested_theme) =
|
||||
theme_settings.requested_theme.clone()
|
||||
{
|
||||
if let Some(_theme) =
|
||||
theme_settings.switch_theme(&requested_theme, cx)
|
||||
{
|
||||
ThemeSettings::override_global(theme_settings, cx);
|
||||
}
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
|
||||
async fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>) {
|
||||
let reload_debounce = Duration::from_millis(250);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user