Compare commits
10 Commits
actions_js
...
v0.121.2-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34e846b353 | ||
|
|
889a6e2bc3 | ||
|
|
21b755abae | ||
|
|
0bf444d39d | ||
|
|
a745e9b58b | ||
|
|
958fbacc2b | ||
|
|
bb6c06e204 | ||
|
|
2e9f665c77 | ||
|
|
3fdccaacbc | ||
|
|
7dd7ecd07f |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -10261,7 +10261,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.121.0"
|
||||
version = "0.121.2"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"ai",
|
||||
|
||||
@@ -199,9 +199,13 @@ impl AssistantPanel {
|
||||
.update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
|
||||
cx.notify();
|
||||
if self.focus_handle.is_focused(cx) {
|
||||
if let Some(editor) = self.active_editor() {
|
||||
cx.focus_view(editor);
|
||||
} else if let Some(api_key_editor) = self.api_key_editor.as_ref() {
|
||||
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() {
|
||||
cx.focus_view(api_key_editor);
|
||||
}
|
||||
}
|
||||
@@ -777,6 +781,10 @@ 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(
|
||||
@@ -870,7 +878,7 @@ impl AssistantPanel {
|
||||
cx.update(|cx| completion_provider.delete_credentials(cx))?
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.api_key_editor = Some(build_api_key_editor(cx));
|
||||
this.build_api_key_editor(cx);
|
||||
this.focus_handle.focus(cx);
|
||||
cx.notify();
|
||||
})
|
||||
@@ -1136,7 +1144,7 @@ impl AssistantPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
|
||||
fn build_api_key_editor(cx: &mut WindowContext) -> View<Editor> {
|
||||
cx.new_view(|cx| {
|
||||
let mut editor = Editor::single_line(cx);
|
||||
editor.set_placeholder_text("sk-000000000000000000000000000000000000000000000000", cx);
|
||||
@@ -1147,9 +1155,10 @@ fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> 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; 5] = [
|
||||
const INSTRUCTIONS: [&'static str; 6] = [
|
||||
"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:"
|
||||
@@ -1342,7 +1351,9 @@ impl Panel for AssistantPanel {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
load_credentials.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if this.editors.is_empty() {
|
||||
if !this.has_credentials() {
|
||||
this.build_api_key_editor(cx);
|
||||
} else if this.editors.is_empty() {
|
||||
this.new_conversation(cx);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -28,6 +28,9 @@ async fn main() -> Result<()> {
|
||||
Some("version") => {
|
||||
println!("collab v{VERSION}");
|
||||
}
|
||||
Some("migrate") => {
|
||||
run_migrations().await?;
|
||||
}
|
||||
Some("serve") => {
|
||||
let config = envy::from_env::<Config>().expect("error loading config");
|
||||
init_tracing(&config);
|
||||
|
||||
@@ -185,6 +185,14 @@ impl FollowableItem for Editor {
|
||||
|
||||
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
|
||||
let buffer = self.buffer.read(cx);
|
||||
if buffer
|
||||
.as_singleton()
|
||||
.and_then(|buffer| buffer.read(cx).file())
|
||||
.map_or(false, |file| file.is_private())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let scroll_anchor = self.scroll_manager.anchor();
|
||||
let excerpts = buffer
|
||||
.read(cx)
|
||||
|
||||
@@ -3,6 +3,7 @@ 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,
|
||||
@@ -14,8 +15,9 @@ 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::{ffi::c_void, mem, ptr, sync::Arc};
|
||||
use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc};
|
||||
|
||||
#[cfg(not(feature = "runtime_shaders"))]
|
||||
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
|
||||
@@ -25,6 +27,7 @@ const SHADERS_SOURCE_FILE: &'static str =
|
||||
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,
|
||||
@@ -36,7 +39,8 @@ pub(crate) struct MetalRenderer {
|
||||
polychrome_sprites_pipeline_state: metal::RenderPipelineState,
|
||||
surfaces_pipeline_state: metal::RenderPipelineState,
|
||||
unit_vertices: metal::Buffer,
|
||||
instances: metal::Buffer,
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
instance_buffers: Arc<Mutex<Vec<metal::Buffer>>>,
|
||||
sprite_atlas: Arc<MetalAtlas>,
|
||||
core_video_texture_cache: CVMetalTextureCache,
|
||||
}
|
||||
@@ -93,10 +97,6 @@ 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,8 +165,11 @@ 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,
|
||||
@@ -178,9 +181,9 @@ impl MetalRenderer {
|
||||
polychrome_sprites_pipeline_state,
|
||||
surfaces_pipeline_state,
|
||||
unit_vertices,
|
||||
instances,
|
||||
instance_buffers: Arc::default(),
|
||||
sprite_atlas,
|
||||
core_video_texture_cache: unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() },
|
||||
core_video_texture_cache,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,13 +211,22 @@ impl MetalRenderer {
|
||||
);
|
||||
return;
|
||||
};
|
||||
let mut instance_buffer = self.instance_buffers.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_offset, command_buffer)
|
||||
else {
|
||||
let Some(path_tiles) = self.rasterize_paths(
|
||||
scene.paths(),
|
||||
&mut instance_buffer,
|
||||
&mut instance_offset,
|
||||
command_buffer,
|
||||
) else {
|
||||
panic!("failed to rasterize {} paths", scene.paths().len());
|
||||
};
|
||||
|
||||
@@ -243,22 +255,29 @@ 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,
|
||||
@@ -269,6 +288,7 @@ impl MetalRenderer {
|
||||
} => self.draw_monochrome_sprites(
|
||||
texture_id,
|
||||
sprites,
|
||||
&mut instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
@@ -279,12 +299,14 @@ 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,
|
||||
@@ -306,22 +328,32 @@ impl MetalRenderer {
|
||||
|
||||
command_encoder.end_encoding();
|
||||
|
||||
self.instances.did_modify_range(NSRange {
|
||||
instance_buffer.did_modify_range(NSRange {
|
||||
location: 0,
|
||||
length: instance_offset as NSUInteger,
|
||||
});
|
||||
|
||||
let instance_buffers = self.instance_buffers.clone();
|
||||
let instance_buffer = Cell::new(Some(instance_buffer));
|
||||
let block = ConcreteBlock::new(move |_| {
|
||||
if let Some(instance_buffer) = instance_buffer.take() {
|
||||
instance_buffers.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_completed();
|
||||
command_buffer.wait_until_scheduled();
|
||||
drawable.present();
|
||||
}
|
||||
|
||||
fn rasterize_paths(
|
||||
&mut self,
|
||||
paths: &[Path<ScaledPixels>],
|
||||
offset: &mut usize,
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
command_buffer: &metal::CommandBufferRef,
|
||||
) -> Option<HashMap<PathId, AtlasTile>> {
|
||||
let mut tiles = HashMap::default();
|
||||
@@ -347,9 +379,9 @@ impl MetalRenderer {
|
||||
}
|
||||
|
||||
for (texture_id, vertices) in vertices_by_texture_id {
|
||||
align_offset(offset);
|
||||
align_offset(instance_offset);
|
||||
let vertices_bytes_len = mem::size_of_val(vertices.as_slice());
|
||||
let next_offset = *offset + vertices_bytes_len;
|
||||
let next_offset = *instance_offset + vertices_bytes_len;
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return None;
|
||||
}
|
||||
@@ -369,8 +401,8 @@ impl MetalRenderer {
|
||||
command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
|
||||
command_encoder.set_vertex_buffer(
|
||||
PathRasterizationInputIndex::Vertices as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
);
|
||||
let texture_size = Size {
|
||||
width: DevicePixels::from(texture.width()),
|
||||
@@ -382,7 +414,8 @@ impl MetalRenderer {
|
||||
&texture_size as *const Size<DevicePixels> as *const _,
|
||||
);
|
||||
|
||||
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
let buffer_contents =
|
||||
unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) };
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(
|
||||
vertices.as_ptr() as *const u8,
|
||||
@@ -397,7 +430,7 @@ impl MetalRenderer {
|
||||
vertices.len() as u64,
|
||||
);
|
||||
command_encoder.end_encoding();
|
||||
*offset = next_offset;
|
||||
*instance_offset = next_offset;
|
||||
}
|
||||
|
||||
Some(tiles)
|
||||
@@ -406,14 +439,15 @@ impl MetalRenderer {
|
||||
fn draw_shadows(
|
||||
&mut self,
|
||||
shadows: &[Shadow],
|
||||
offset: &mut usize,
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) -> bool {
|
||||
if shadows.is_empty() {
|
||||
return true;
|
||||
}
|
||||
align_offset(offset);
|
||||
align_offset(instance_offset);
|
||||
|
||||
command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state);
|
||||
command_encoder.set_vertex_buffer(
|
||||
@@ -423,13 +457,13 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_vertex_buffer(
|
||||
ShadowInputIndex::Shadows as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
);
|
||||
command_encoder.set_fragment_buffer(
|
||||
ShadowInputIndex::Shadows as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
);
|
||||
|
||||
command_encoder.set_vertex_bytes(
|
||||
@@ -439,9 +473,10 @@ impl MetalRenderer {
|
||||
);
|
||||
|
||||
let shadow_bytes_len = mem::size_of_val(shadows);
|
||||
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
let buffer_contents =
|
||||
unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) };
|
||||
|
||||
let next_offset = *offset + shadow_bytes_len;
|
||||
let next_offset = *instance_offset + shadow_bytes_len;
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return false;
|
||||
}
|
||||
@@ -460,21 +495,22 @@ impl MetalRenderer {
|
||||
6,
|
||||
shadows.len() as u64,
|
||||
);
|
||||
*offset = next_offset;
|
||||
*instance_offset = next_offset;
|
||||
true
|
||||
}
|
||||
|
||||
fn draw_quads(
|
||||
&mut self,
|
||||
quads: &[Quad],
|
||||
offset: &mut usize,
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) -> bool {
|
||||
if quads.is_empty() {
|
||||
return true;
|
||||
}
|
||||
align_offset(offset);
|
||||
align_offset(instance_offset);
|
||||
|
||||
command_encoder.set_render_pipeline_state(&self.quads_pipeline_state);
|
||||
command_encoder.set_vertex_buffer(
|
||||
@@ -484,13 +520,13 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_vertex_buffer(
|
||||
QuadInputIndex::Quads as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
);
|
||||
command_encoder.set_fragment_buffer(
|
||||
QuadInputIndex::Quads as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
);
|
||||
|
||||
command_encoder.set_vertex_bytes(
|
||||
@@ -500,9 +536,10 @@ impl MetalRenderer {
|
||||
);
|
||||
|
||||
let quad_bytes_len = mem::size_of_val(quads);
|
||||
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
let buffer_contents =
|
||||
unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) };
|
||||
|
||||
let next_offset = *offset + quad_bytes_len;
|
||||
let next_offset = *instance_offset + quad_bytes_len;
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return false;
|
||||
}
|
||||
@@ -517,7 +554,7 @@ impl MetalRenderer {
|
||||
6,
|
||||
quads.len() as u64,
|
||||
);
|
||||
*offset = next_offset;
|
||||
*instance_offset = next_offset;
|
||||
true
|
||||
}
|
||||
|
||||
@@ -525,7 +562,8 @@ impl MetalRenderer {
|
||||
&mut self,
|
||||
paths: &[Path<ScaledPixels>],
|
||||
tiles_by_path_id: &HashMap<PathId, AtlasTile>,
|
||||
offset: &mut usize,
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) -> bool {
|
||||
@@ -573,7 +611,7 @@ impl MetalRenderer {
|
||||
if sprites.is_empty() {
|
||||
break;
|
||||
} else {
|
||||
align_offset(offset);
|
||||
align_offset(instance_offset);
|
||||
let texture_id = prev_texture_id.take().unwrap();
|
||||
let texture: metal::Texture = self.sprite_atlas.metal_texture(texture_id);
|
||||
let texture_size = size(
|
||||
@@ -583,8 +621,8 @@ impl MetalRenderer {
|
||||
|
||||
command_encoder.set_vertex_buffer(
|
||||
SpriteInputIndex::Sprites as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
);
|
||||
command_encoder.set_vertex_bytes(
|
||||
SpriteInputIndex::AtlasTextureSize as u64,
|
||||
@@ -593,20 +631,20 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_fragment_buffer(
|
||||
SpriteInputIndex::Sprites as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_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 = *offset + sprite_bytes_len;
|
||||
let next_offset = *instance_offset + sprite_bytes_len;
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return false;
|
||||
}
|
||||
|
||||
let buffer_contents =
|
||||
unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) };
|
||||
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(
|
||||
@@ -622,7 +660,7 @@ impl MetalRenderer {
|
||||
6,
|
||||
sprites.len() as u64,
|
||||
);
|
||||
*offset = next_offset;
|
||||
*instance_offset = next_offset;
|
||||
sprites.clear();
|
||||
}
|
||||
}
|
||||
@@ -632,14 +670,15 @@ impl MetalRenderer {
|
||||
fn draw_underlines(
|
||||
&mut self,
|
||||
underlines: &[Underline],
|
||||
offset: &mut usize,
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) -> bool {
|
||||
if underlines.is_empty() {
|
||||
return true;
|
||||
}
|
||||
align_offset(offset);
|
||||
align_offset(instance_offset);
|
||||
|
||||
command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state);
|
||||
command_encoder.set_vertex_buffer(
|
||||
@@ -649,13 +688,13 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_vertex_buffer(
|
||||
UnderlineInputIndex::Underlines as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
);
|
||||
command_encoder.set_fragment_buffer(
|
||||
UnderlineInputIndex::Underlines as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
);
|
||||
|
||||
command_encoder.set_vertex_bytes(
|
||||
@@ -665,9 +704,10 @@ impl MetalRenderer {
|
||||
);
|
||||
|
||||
let underline_bytes_len = mem::size_of_val(underlines);
|
||||
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
let buffer_contents =
|
||||
unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) };
|
||||
|
||||
let next_offset = *offset + underline_bytes_len;
|
||||
let next_offset = *instance_offset + underline_bytes_len;
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return false;
|
||||
}
|
||||
@@ -686,7 +726,7 @@ impl MetalRenderer {
|
||||
6,
|
||||
underlines.len() as u64,
|
||||
);
|
||||
*offset = next_offset;
|
||||
*instance_offset = next_offset;
|
||||
true
|
||||
}
|
||||
|
||||
@@ -694,14 +734,15 @@ impl MetalRenderer {
|
||||
&mut self,
|
||||
texture_id: AtlasTextureId,
|
||||
sprites: &[MonochromeSprite],
|
||||
offset: &mut usize,
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) -> bool {
|
||||
if sprites.is_empty() {
|
||||
return true;
|
||||
}
|
||||
align_offset(offset);
|
||||
align_offset(instance_offset);
|
||||
|
||||
let texture = self.sprite_atlas.metal_texture(texture_id);
|
||||
let texture_size = size(
|
||||
@@ -716,8 +757,8 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_vertex_buffer(
|
||||
SpriteInputIndex::Sprites as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
);
|
||||
command_encoder.set_vertex_bytes(
|
||||
SpriteInputIndex::ViewportSize as u64,
|
||||
@@ -731,15 +772,16 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_fragment_buffer(
|
||||
SpriteInputIndex::Sprites as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_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 { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
let buffer_contents =
|
||||
unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) };
|
||||
|
||||
let next_offset = *offset + sprite_bytes_len;
|
||||
let next_offset = *instance_offset + sprite_bytes_len;
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return false;
|
||||
}
|
||||
@@ -758,7 +800,7 @@ impl MetalRenderer {
|
||||
6,
|
||||
sprites.len() as u64,
|
||||
);
|
||||
*offset = next_offset;
|
||||
*instance_offset = next_offset;
|
||||
true
|
||||
}
|
||||
|
||||
@@ -766,14 +808,15 @@ impl MetalRenderer {
|
||||
&mut self,
|
||||
texture_id: AtlasTextureId,
|
||||
sprites: &[PolychromeSprite],
|
||||
offset: &mut usize,
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) -> bool {
|
||||
if sprites.is_empty() {
|
||||
return true;
|
||||
}
|
||||
align_offset(offset);
|
||||
align_offset(instance_offset);
|
||||
|
||||
let texture = self.sprite_atlas.metal_texture(texture_id);
|
||||
let texture_size = size(
|
||||
@@ -788,8 +831,8 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_vertex_buffer(
|
||||
SpriteInputIndex::Sprites as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
);
|
||||
command_encoder.set_vertex_bytes(
|
||||
SpriteInputIndex::ViewportSize as u64,
|
||||
@@ -803,15 +846,16 @@ impl MetalRenderer {
|
||||
);
|
||||
command_encoder.set_fragment_buffer(
|
||||
SpriteInputIndex::Sprites as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_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 { (self.instances.contents() as *mut u8).add(*offset) };
|
||||
let buffer_contents =
|
||||
unsafe { (instance_buffer.contents() as *mut u8).add(*instance_offset) };
|
||||
|
||||
let next_offset = *offset + sprite_bytes_len;
|
||||
let next_offset = *instance_offset + sprite_bytes_len;
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return false;
|
||||
}
|
||||
@@ -830,14 +874,15 @@ impl MetalRenderer {
|
||||
6,
|
||||
sprites.len() as u64,
|
||||
);
|
||||
*offset = next_offset;
|
||||
*instance_offset = next_offset;
|
||||
true
|
||||
}
|
||||
|
||||
fn draw_surfaces(
|
||||
&mut self,
|
||||
surfaces: &[Surface],
|
||||
offset: &mut usize,
|
||||
instance_buffer: &mut metal::Buffer,
|
||||
instance_offset: &mut usize,
|
||||
viewport_size: Size<DevicePixels>,
|
||||
command_encoder: &metal::RenderCommandEncoderRef,
|
||||
) -> bool {
|
||||
@@ -889,16 +934,16 @@ impl MetalRenderer {
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
align_offset(offset);
|
||||
let next_offset = *offset + mem::size_of::<Surface>();
|
||||
align_offset(instance_offset);
|
||||
let next_offset = *instance_offset + mem::size_of::<Surface>();
|
||||
if next_offset > INSTANCE_BUFFER_SIZE {
|
||||
return false;
|
||||
}
|
||||
|
||||
command_encoder.set_vertex_buffer(
|
||||
SurfaceInputIndex::Surfaces as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
Some(instance_buffer),
|
||||
*instance_offset as u64,
|
||||
);
|
||||
command_encoder.set_vertex_bytes(
|
||||
SurfaceInputIndex::TextureSize as u64,
|
||||
@@ -915,8 +960,8 @@ impl MetalRenderer {
|
||||
);
|
||||
|
||||
unsafe {
|
||||
let buffer_contents =
|
||||
(self.instances.contents() as *mut u8).add(*offset) as *mut SurfaceBounds;
|
||||
let buffer_contents = (instance_buffer.contents() as *mut u8).add(*instance_offset)
|
||||
as *mut SurfaceBounds;
|
||||
ptr::write(
|
||||
buffer_contents,
|
||||
SurfaceBounds {
|
||||
@@ -927,7 +972,7 @@ impl MetalRenderer {
|
||||
}
|
||||
|
||||
command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6);
|
||||
*offset = next_offset;
|
||||
*instance_offset = next_offset;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@@ -61,6 +61,16 @@ 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 =
|
||||
|
||||
@@ -384,7 +384,7 @@ pub trait File: Send + Sync {
|
||||
/// Converts this file into a protobuf message.
|
||||
fn to_proto(&self) -> rpc::proto::File;
|
||||
|
||||
/// Return whether Zed considers this to be a dotenv file.
|
||||
/// Return whether Zed considers this to be a private file.
|
||||
fn is_private(&self) -> bool;
|
||||
}
|
||||
|
||||
@@ -406,6 +406,11 @@ pub trait LocalFile: File {
|
||||
mtime: SystemTime,
|
||||
cx: &mut AppContext,
|
||||
);
|
||||
|
||||
/// Returns true if the file should not be shared with collaborators.
|
||||
fn is_private(&self, _: &AppContext) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// The auto-indent behavior associated with an editing operation.
|
||||
|
||||
@@ -56,6 +56,7 @@ use postage::watch;
|
||||
use prettier_support::{DefaultPrettier, PrettierInstance};
|
||||
use project_settings::{LspSettings, ProjectSettings};
|
||||
use rand::prelude::*;
|
||||
use rpc::{ErrorCode, ErrorExt};
|
||||
use search::SearchQuery;
|
||||
use serde::Serialize;
|
||||
use settings::{Settings, SettingsStore};
|
||||
@@ -1760,7 +1761,7 @@ impl Project {
|
||||
cx.background_executor().spawn(async move {
|
||||
wait_for_loading_buffer(loading_watch)
|
||||
.await
|
||||
.map_err(|error| anyhow!("{project_path:?} opening failure: {error:#}"))
|
||||
.map_err(|e| e.cloned())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8011,11 +8012,20 @@ impl Project {
|
||||
.update(&mut cx, |this, cx| this.open_buffer_for_symbol(&symbol, cx))?
|
||||
.await?;
|
||||
|
||||
Ok(proto::OpenBufferForSymbolResponse {
|
||||
buffer_id: this.update(&mut cx, |this, cx| {
|
||||
this.create_buffer_for_peer(&buffer, peer_id, cx).into()
|
||||
})?,
|
||||
})
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let is_private = buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.map(|f| f.is_private())
|
||||
.unwrap_or_default();
|
||||
if is_private {
|
||||
Err(anyhow!(ErrorCode::UnsharedItem))
|
||||
} else {
|
||||
Ok(proto::OpenBufferForSymbolResponse {
|
||||
buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
|
||||
})
|
||||
}
|
||||
})?
|
||||
}
|
||||
|
||||
fn symbol_signature(&self, project_path: &ProjectPath) -> [u8; 32] {
|
||||
@@ -8037,11 +8047,7 @@ impl Project {
|
||||
let buffer = this
|
||||
.update(&mut cx, |this, cx| this.open_buffer_by_id(buffer_id, cx))?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
Ok(proto::OpenBufferResponse {
|
||||
buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
|
||||
})
|
||||
})?
|
||||
Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
|
||||
}
|
||||
|
||||
async fn handle_open_buffer_by_path(
|
||||
@@ -8063,10 +8069,28 @@ impl Project {
|
||||
})?;
|
||||
|
||||
let buffer = open_buffer.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
Ok(proto::OpenBufferResponse {
|
||||
buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
|
||||
})
|
||||
Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
|
||||
}
|
||||
|
||||
fn respond_to_open_buffer_request(
|
||||
this: Model<Self>,
|
||||
buffer: Model<Buffer>,
|
||||
peer_id: proto::PeerId,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<proto::OpenBufferResponse> {
|
||||
this.update(cx, |this, cx| {
|
||||
let is_private = buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.map(|f| f.is_private())
|
||||
.unwrap_or_default();
|
||||
if is_private {
|
||||
Err(anyhow!(ErrorCode::UnsharedItem))
|
||||
} else {
|
||||
Ok(proto::OpenBufferResponse {
|
||||
buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
|
||||
})
|
||||
}
|
||||
})?
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ theme = { path = "../theme" }
|
||||
ui = { path = "../ui" }
|
||||
unicase = "2.6"
|
||||
util = { path = "../util" }
|
||||
client = { path = "../client" }
|
||||
workspace = { path = "../workspace", package = "workspace" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod file_associations;
|
||||
mod project_panel_settings;
|
||||
use client::{ErrorCode, ErrorExt};
|
||||
use settings::Settings;
|
||||
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
@@ -35,6 +36,7 @@ use unicase::UniCase;
|
||||
use util::{maybe, ResultExt, TryFutureExt};
|
||||
use workspace::{
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
notifications::DetachAndPromptErr,
|
||||
Workspace,
|
||||
};
|
||||
|
||||
@@ -259,6 +261,7 @@ impl ProjectPanel {
|
||||
} => {
|
||||
if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) {
|
||||
if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) {
|
||||
let file_path = entry.path.clone();
|
||||
workspace
|
||||
.open_path(
|
||||
ProjectPath {
|
||||
@@ -269,7 +272,15 @@ impl ProjectPanel {
|
||||
focus_opened_item,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
.detach_and_prompt_err("Failed to open file", cx, move |e, _| {
|
||||
match e.error_code() {
|
||||
ErrorCode::UnsharedItem => Some(format!(
|
||||
"{} is not shared by the host. This could be because it has been marked as `private`",
|
||||
file_path.display()
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
if !focus_opened_item {
|
||||
if let Some(project_panel) = project_panel.upgrade() {
|
||||
let focus_handle = project_panel.read(cx).focus_handle.clone();
|
||||
|
||||
@@ -216,6 +216,7 @@ enum ErrorCode {
|
||||
BadPublicNesting = 9;
|
||||
CircularNesting = 10;
|
||||
WrongMoveTarget = 11;
|
||||
UnsharedItem = 12;
|
||||
}
|
||||
|
||||
message Test {
|
||||
|
||||
@@ -80,6 +80,8 @@ pub trait ErrorExt {
|
||||
fn error_tag(&self, k: &str) -> Option<&str>;
|
||||
/// to_proto() converts the error into a proto::Error
|
||||
fn to_proto(&self) -> proto::Error;
|
||||
///
|
||||
fn cloned(&self) -> anyhow::Error;
|
||||
}
|
||||
|
||||
impl ErrorExt for anyhow::Error {
|
||||
@@ -106,6 +108,14 @@ impl ErrorExt for anyhow::Error {
|
||||
ErrorCode::Internal.message(format!("{}", self)).to_proto()
|
||||
}
|
||||
}
|
||||
|
||||
fn cloned(&self) -> anyhow::Error {
|
||||
if let Some(rpc_error) = self.downcast_ref::<RpcError>() {
|
||||
rpc_error.cloned()
|
||||
} else {
|
||||
anyhow::anyhow!("{}", self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<proto::ErrorCode> for anyhow::Error {
|
||||
@@ -189,6 +199,10 @@ impl ErrorExt for RpcError {
|
||||
tags: self.tags.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cloned(&self) -> anyhow::Error {
|
||||
self.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RpcError {
|
||||
|
||||
@@ -255,19 +255,23 @@ impl ThemeRegistry {
|
||||
continue;
|
||||
};
|
||||
|
||||
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]);
|
||||
self.load_user_theme(&theme_path, fs.clone())
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
|
||||
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.pre_release == pre_release)
|
||||
.find(|release| !release.assets.is_empty() && release.pre_release == pre_release)
|
||||
.ok_or(anyhow!("Failed to find a release"))
|
||||
}
|
||||
|
||||
@@ -176,11 +176,19 @@ impl Member {
|
||||
return div().into_any();
|
||||
}
|
||||
|
||||
let leader = follower_states.get(pane).and_then(|state| {
|
||||
let follower_state = follower_states.get(pane);
|
||||
|
||||
let leader = follower_state.and_then(|state| {
|
||||
let room = active_call?.read(cx).room()?.read(cx);
|
||||
room.remote_participant_for_peer_id(state.leader_id)
|
||||
});
|
||||
|
||||
let is_in_unshared_view = follower_state.map_or(false, |state| {
|
||||
state.active_view_id.is_some_and(|view_id| {
|
||||
!state.items_by_leader_view_id.contains_key(&view_id)
|
||||
})
|
||||
});
|
||||
|
||||
let mut leader_border = None;
|
||||
let mut leader_status_box = None;
|
||||
let mut leader_join_data = None;
|
||||
@@ -198,7 +206,14 @@ impl Member {
|
||||
project_id: leader_project_id,
|
||||
} => {
|
||||
if Some(leader_project_id) == project.read(cx).remote_id() {
|
||||
None
|
||||
if is_in_unshared_view {
|
||||
Some(Label::new(format!(
|
||||
"{} is in an unshared pane",
|
||||
leader.user.github_login
|
||||
)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
leader_join_data = Some((leader_project_id, leader.user.id));
|
||||
Some(Label::new(format!(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition = "2021"
|
||||
name = "zed"
|
||||
version = "0.121.0"
|
||||
version = "0.121.2"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
preview
|
||||
@@ -11,6 +11,7 @@ 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};
|
||||
@@ -171,35 +172,8 @@ fn main() {
|
||||
);
|
||||
assistant::init(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();
|
||||
load_user_themes_in_background(fs.clone(), cx);
|
||||
watch_themes(fs.clone(), cx);
|
||||
|
||||
cx.spawn(|_| watch_languages(fs.clone(), languages.clone()))
|
||||
.detach();
|
||||
@@ -899,6 +873,81 @@ 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