Compare commits

..

6 Commits

Author SHA1 Message Date
Conrad Irwin
ada0523043 Fix mouse selection
Co-Authored-By: WindSoilder <WindSoilder@outlook.com>
2024-02-02 16:36:51 -07:00
WindSoilder
aeb8535468 Merge branch 'main' into vim_click 2024-02-01 06:59:01 +08:00
Marshall Bowers
a588a7dd4d Fix some typos in comments (#7169)
This PR fixes a couple typos I found in some comments/doc comments.

Release Notes:

- N/A
2024-01-31 16:21:33 -05:00
Conrad Irwin
dcca48482b disallow opening private files (#7165)
- Disallow sharing gitignored files through collab
- Show errors when failing to open files
- Show a warning to followers when view is unshared

/cc @mikaylamaki, let's update this to use your `private_files` config
before merge.


Release Notes:

- Added the ability to prevent sharing private files over collab.

---------

Co-authored-by: Piotr <piotr@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
2024-01-31 12:05:51 -08:00
Joseph T. Lyons
c983c9b6df v0.122.x dev 2024-01-31 14:44:24 -05:00
WindSoilder
a082b93260 single click convert from virual mode to normal mode 2024-01-29 20:41:36 +08:00
28 changed files with 389 additions and 563 deletions

2
Cargo.lock generated
View File

@@ -10261,7 +10261,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.121.4"
version = "0.122.0"
dependencies = [
"activity_indicator",
"ai",

View File

@@ -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",

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,

View File

@@ -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")

View File

@@ -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;
}
}
}

View File

@@ -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

View File

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

View File

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

View File

@@ -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
}

View File

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

View File

@@ -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 =

View File

@@ -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] }
}

View File

@@ -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> {

View File

@@ -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)
}
}

View File

@@ -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,

View File

@@ -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))
}
}

View File

@@ -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.

View File

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

View File

@@ -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 {

View File

@@ -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"))
}

View File

@@ -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)
}
}
})

View File

@@ -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"

View File

@@ -1 +1 @@
stable
dev

View File

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