Compare commits
33 Commits
fix-git-ht
...
v0.121.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc78995fee | ||
|
|
59b26719b0 | ||
|
|
d5a3370d7c | ||
|
|
0a9e34eb1e | ||
|
|
7894f57171 | ||
|
|
d1c4fc38ee | ||
|
|
c6ff5c4c1f | ||
|
|
85dfbe3349 | ||
|
|
d10588d871 | ||
|
|
3b28b37866 | ||
|
|
c544ce0d47 | ||
|
|
a3a7ae455b | ||
|
|
ac85440c82 | ||
|
|
f28a4c7cf9 | ||
|
|
824e7e421a | ||
|
|
5721dec2ec | ||
|
|
9f90ebd509 | ||
|
|
17a529c0f5 | ||
|
|
3a0fb0d322 | ||
|
|
f8bc7fbe0e | ||
|
|
a413729be9 | ||
|
|
441a21ed0b | ||
|
|
234673a1b9 | ||
|
|
34e846b353 | ||
|
|
889a6e2bc3 | ||
|
|
21b755abae | ||
|
|
0bf444d39d | ||
|
|
a745e9b58b | ||
|
|
958fbacc2b | ||
|
|
bb6c06e204 | ||
|
|
2e9f665c77 | ||
|
|
3fdccaacbc | ||
|
|
7dd7ecd07f |
5
.github/actions/check_style/action.yml
vendored
5
.github/actions/check_style/action.yml
vendored
@@ -23,8 +23,3 @@ runs:
|
||||
export SQUAWK_GITHUB_TOKEN=${{ github.token }}
|
||||
. ./script/squawk
|
||||
|
||||
- uses: bufbuild/buf-setup-action@v1
|
||||
- uses: bufbuild/buf-breaking-action@v1
|
||||
with:
|
||||
input: "crates/rpc/proto/"
|
||||
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=main,subdir=crates/rpc/proto/"
|
||||
|
||||
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -48,6 +48,18 @@ jobs:
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
|
||||
- name: Ensure fresh merge
|
||||
run: |
|
||||
git checkout -B temp
|
||||
git merge -q origin/main -m "merge main into temp"
|
||||
|
||||
- uses: bufbuild/buf-setup-action@v1
|
||||
- uses: bufbuild/buf-breaking-action@v1
|
||||
with:
|
||||
input: "crates/rpc/proto/"
|
||||
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=main,subdir=crates/rpc/proto/"
|
||||
|
||||
|
||||
tests:
|
||||
name: Run tests
|
||||
runs-on:
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -103,9 +103,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alacritty_terminal"
|
||||
version = "0.21.0"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35229555d7cc7e83392dfc27c96bec560b1076d756184893296cd60125f4a264"
|
||||
checksum = "cc7ceabf6fc76511f616ca216b51398a2511f19ba9f71bcbd977999edff1b0d1"
|
||||
dependencies = [
|
||||
"base64 0.21.4",
|
||||
"bitflags 2.4.1",
|
||||
@@ -10261,7 +10261,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.121.0"
|
||||
version = "0.121.7"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"ai",
|
||||
|
||||
@@ -415,7 +415,15 @@
|
||||
"cmd-?": "assistant::ToggleFocus",
|
||||
"cmd-alt-s": "workspace::SaveAll",
|
||||
"cmd-k m": "language_selector::Toggle",
|
||||
"escape": "workspace::Unfollow"
|
||||
"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"]
|
||||
}
|
||||
},
|
||||
// Bindings from Sublime Text
|
||||
@@ -441,18 +449,6 @@
|
||||
"ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-k cmd-left": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"cmd-k cmd-right": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"cmd-k cmd-up": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"cmd-k cmd-down": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"cmd-k shift-left": ["workspace::SwapPaneInDirection", "Left"],
|
||||
"cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
|
||||
"cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
|
||||
"cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"]
|
||||
}
|
||||
},
|
||||
// Bindings from Atom
|
||||
{
|
||||
"context": "Pane",
|
||||
|
||||
@@ -199,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);
|
||||
|
||||
@@ -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() == 3);
|
||||
assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -976,7 +976,8 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
|
||||
///Check for the latest copilot language server and download it if we haven't already
|
||||
async fn fetch_latest(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
let release = latest_github_release("zed-industries/copilot", false, http.clone()).await?;
|
||||
let release =
|
||||
latest_github_release("zed-industries/copilot", true, false, http.clone()).await?;
|
||||
|
||||
let version_dir = &*paths::COPILOT_DIR.join(format!("copilot-{}", release.name));
|
||||
|
||||
@@ -997,7 +998,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
let mut response = http
|
||||
.get(url, Default::default(), true)
|
||||
.await
|
||||
.map_err(|err| anyhow!("error downloading copilot release: {}", err))?;
|
||||
.context("error downloading copilot release")?;
|
||||
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
|
||||
let archive = Archive::new(decompressed_bytes);
|
||||
archive.unpack(dist_dir).await?;
|
||||
|
||||
@@ -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,7 +3,7 @@
|
||||
|
||||
use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
|
||||
use crate::{char_kind, CharKind, EditorStyle, ToOffset, ToPoint};
|
||||
use gpui::{px, Pixels, TextSystem};
|
||||
use gpui::{px, Pixels, WindowTextSystem};
|
||||
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<TextSystem>,
|
||||
pub(crate) text_system: Arc<WindowTextSystem>,
|
||||
pub(crate) editor_style: EditorStyle,
|
||||
pub(crate) rem_size: Pixels,
|
||||
pub anchor: Anchor,
|
||||
|
||||
@@ -21,12 +21,21 @@ fn generate_dispatch_bindings() {
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header("src/platform/mac/dispatch.h")
|
||||
.allowlist_var("_dispatch_main_q")
|
||||
.allowlist_var("_dispatch_source_type_data_add")
|
||||
.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")
|
||||
.allowlist_function("dispatch_after_f")
|
||||
.allowlist_function("dispatch_time")
|
||||
.allowlist_function("dispatch_source_merge_data")
|
||||
.allowlist_function("dispatch_source_create")
|
||||
.allowlist_function("dispatch_source_set_event_handler_f")
|
||||
.allowlist_function("dispatch_resume")
|
||||
.allowlist_function("dispatch_suspend")
|
||||
.allowlist_function("dispatch_source_cancel")
|
||||
.allowlist_function("dispatch_set_context")
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
|
||||
.layout_tests(false)
|
||||
.generate()
|
||||
|
||||
@@ -17,8 +17,8 @@ use time::UtcOffset;
|
||||
use crate::{
|
||||
current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
|
||||
AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
|
||||
DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap,
|
||||
Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render,
|
||||
DispatchPhase, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke,
|
||||
LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render,
|
||||
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement,
|
||||
TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId,
|
||||
};
|
||||
@@ -192,7 +192,6 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
|
||||
type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
|
||||
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
|
||||
type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut WindowContext) + 'static>;
|
||||
@@ -212,8 +211,6 @@ pub struct AppContext {
|
||||
pending_updates: usize,
|
||||
pub(crate) actions: Rc<ActionRegistry>,
|
||||
pub(crate) active_drag: Option<AnyDrag>,
|
||||
pub(crate) next_frame_callbacks: FxHashMap<DisplayId, Vec<FrameCallback>>,
|
||||
pub(crate) frame_consumers: FxHashMap<DisplayId, Task<()>>,
|
||||
pub(crate) background_executor: BackgroundExecutor,
|
||||
pub(crate) foreground_executor: ForegroundExecutor,
|
||||
pub(crate) svg_renderer: SvgRenderer,
|
||||
@@ -274,8 +271,6 @@ impl AppContext {
|
||||
flushing_effects: false,
|
||||
pending_updates: 0,
|
||||
active_drag: None,
|
||||
next_frame_callbacks: FxHashMap::default(),
|
||||
frame_consumers: FxHashMap::default(),
|
||||
background_executor: executor,
|
||||
foreground_executor,
|
||||
svg_renderer: SvgRenderer::new(asset_source.clone()),
|
||||
@@ -652,27 +647,20 @@ 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 || window.focus_invalidated).then_some(window.handle)
|
||||
window.dirty.get().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;
|
||||
}
|
||||
@@ -749,7 +737,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 = true;
|
||||
window.dirty.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,16 +62,6 @@ 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);
|
||||
|
||||
@@ -84,7 +74,6 @@ 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)]
|
||||
@@ -116,7 +105,6 @@ impl DispatchTree {
|
||||
keystroke_matchers: FxHashMap::default(),
|
||||
keymap,
|
||||
action_registry,
|
||||
keymatch_mode: KeymatchMode::Sequenced,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +115,6 @@ 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(
|
||||
@@ -335,7 +322,7 @@ impl DispatchTree {
|
||||
.collect()
|
||||
}
|
||||
|
||||
// dispatch_key pushses the next keystroke into any key binding matchers.
|
||||
// dispatch_key pushes 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
|
||||
@@ -364,6 +351,11 @@ 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
|
||||
|
||||
@@ -67,14 +67,6 @@ pub(crate) trait Platform: 'static {
|
||||
options: WindowOptions,
|
||||
) -> Box<dyn PlatformWindow>;
|
||||
|
||||
fn set_display_link_output_callback(
|
||||
&self,
|
||||
display_id: DisplayId,
|
||||
callback: Box<dyn FnMut() + Send>,
|
||||
);
|
||||
fn start_display_link(&self, display_id: DisplayId);
|
||||
fn stop_display_link(&self, display_id: DisplayId);
|
||||
|
||||
fn open_url(&self, url: &str);
|
||||
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
|
||||
fn prompt_for_paths(
|
||||
@@ -175,7 +167,6 @@ 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>;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! an origin at the bottom left of the main display.
|
||||
mod dispatcher;
|
||||
mod display;
|
||||
mod display_linker;
|
||||
mod display_link;
|
||||
mod events;
|
||||
mod metal_atlas;
|
||||
mod metal_renderer;
|
||||
@@ -23,7 +23,7 @@ use std::ops::Range;
|
||||
|
||||
pub(crate) use dispatcher::*;
|
||||
pub(crate) use display::*;
|
||||
pub(crate) use display_linker::*;
|
||||
pub(crate) use display_link::*;
|
||||
pub(crate) use metal_atlas::*;
|
||||
pub(crate) use platform::*;
|
||||
pub(crate) use text_system::*;
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <dispatch/source.h>
|
||||
|
||||
@@ -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_DEFAULT.try_into().unwrap(), 0),
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH.try_into().unwrap(), 0),
|
||||
runnable.into_raw().as_ptr() as *mut c_void,
|
||||
Some(trampoline),
|
||||
);
|
||||
|
||||
@@ -1,93 +1,96 @@
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
mem,
|
||||
sync::{Arc, Weak},
|
||||
use crate::{
|
||||
dispatch_get_main_queue,
|
||||
dispatch_sys::{
|
||||
_dispatch_source_type_data_add, dispatch_resume, dispatch_set_context,
|
||||
dispatch_source_cancel, dispatch_source_create, dispatch_source_merge_data,
|
||||
dispatch_source_set_event_handler_f, dispatch_source_t, dispatch_suspend,
|
||||
},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use core_graphics::display::CGDirectDisplayID;
|
||||
use std::ffi::c_void;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::DisplayId;
|
||||
use collections::HashMap;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
pub(crate) struct MacDisplayLinker {
|
||||
links: HashMap<DisplayId, MacDisplayLink>,
|
||||
pub struct DisplayLink {
|
||||
display_link: sys::DisplayLink,
|
||||
frame_requests: dispatch_source_t,
|
||||
}
|
||||
|
||||
struct MacDisplayLink {
|
||||
system_link: sys::DisplayLink,
|
||||
_output_callback: Arc<OutputCallback>,
|
||||
}
|
||||
|
||||
impl MacDisplayLinker {
|
||||
pub fn new() -> Self {
|
||||
MacDisplayLinker {
|
||||
links: Default::default(),
|
||||
impl DisplayLink {
|
||||
pub fn new(
|
||||
display_id: CGDirectDisplayID,
|
||||
data: *mut c_void,
|
||||
callback: unsafe extern "C" fn(*mut c_void),
|
||||
) -> Result<DisplayLink> {
|
||||
unsafe extern "C" fn display_link_callback(
|
||||
_display_link_out: *mut sys::CVDisplayLink,
|
||||
_current_time: *const sys::CVTimeStamp,
|
||||
_output_time: *const sys::CVTimeStamp,
|
||||
_flags_in: i64,
|
||||
_flags_out: *mut i64,
|
||||
frame_requests: *mut c_void,
|
||||
) -> i32 {
|
||||
let frame_requests = frame_requests as dispatch_source_t;
|
||||
dispatch_source_merge_data(frame_requests, 1);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type OutputCallback = Mutex<Box<dyn FnMut() + Send>>;
|
||||
|
||||
impl MacDisplayLinker {
|
||||
pub fn set_output_callback(
|
||||
&mut self,
|
||||
display_id: DisplayId,
|
||||
output_callback: Box<dyn FnMut() + Send>,
|
||||
) {
|
||||
if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } {
|
||||
let callback = Arc::new(Mutex::new(output_callback));
|
||||
let weak_callback_ptr: *const OutputCallback = Arc::downgrade(&callback).into_raw();
|
||||
unsafe { system_link.set_output_callback(trampoline, weak_callback_ptr as *mut c_void) }
|
||||
|
||||
self.links.insert(
|
||||
display_id,
|
||||
MacDisplayLink {
|
||||
_output_callback: callback,
|
||||
system_link,
|
||||
},
|
||||
unsafe {
|
||||
let frame_requests = dispatch_source_create(
|
||||
&_dispatch_source_type_data_add,
|
||||
0,
|
||||
0,
|
||||
dispatch_get_main_queue(),
|
||||
);
|
||||
} else {
|
||||
log::warn!("DisplayLink could not be obtained for {:?}", display_id);
|
||||
dispatch_set_context(
|
||||
crate::dispatch_sys::dispatch_object_t {
|
||||
_ds: frame_requests,
|
||||
},
|
||||
data,
|
||||
);
|
||||
dispatch_source_set_event_handler_f(frame_requests, Some(callback));
|
||||
|
||||
let display_link = sys::DisplayLink::new(
|
||||
display_id,
|
||||
display_link_callback,
|
||||
frame_requests as *mut c_void,
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
display_link,
|
||||
frame_requests,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self, display_id: DisplayId) {
|
||||
if let Some(link) = self.links.get_mut(&display_id) {
|
||||
unsafe {
|
||||
link.system_link.start();
|
||||
}
|
||||
} else {
|
||||
log::warn!("No DisplayLink callback registered for {:?}", display_id)
|
||||
pub fn start(&mut self) -> Result<()> {
|
||||
unsafe {
|
||||
dispatch_resume(crate::dispatch_sys::dispatch_object_t {
|
||||
_ds: self.frame_requests,
|
||||
});
|
||||
self.display_link.start()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, display_id: DisplayId) {
|
||||
if let Some(link) = self.links.get_mut(&display_id) {
|
||||
unsafe {
|
||||
link.system_link.stop();
|
||||
}
|
||||
} else {
|
||||
log::warn!("No DisplayLink callback registered for {:?}", display_id)
|
||||
pub fn stop(&mut self) -> Result<()> {
|
||||
unsafe {
|
||||
dispatch_suspend(crate::dispatch_sys::dispatch_object_t {
|
||||
_ds: self.frame_requests,
|
||||
});
|
||||
self.display_link.stop()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn trampoline(
|
||||
_display_link_out: *mut sys::CVDisplayLink,
|
||||
current_time: *const sys::CVTimeStamp,
|
||||
output_time: *const sys::CVTimeStamp,
|
||||
_flags_in: i64,
|
||||
_flags_out: *mut i64,
|
||||
user_data: *mut c_void,
|
||||
) -> i32 {
|
||||
if let Some((_current_time, _output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
|
||||
let output_callback: Weak<OutputCallback> =
|
||||
Weak::from_raw(user_data as *mut OutputCallback);
|
||||
if let Some(output_callback) = output_callback.upgrade() {
|
||||
(output_callback.lock())()
|
||||
impl Drop for DisplayLink {
|
||||
fn drop(&mut self) {
|
||||
self.stop().log_err();
|
||||
unsafe {
|
||||
dispatch_source_cancel(self.frame_requests);
|
||||
}
|
||||
mem::forget(output_callback);
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
mod sys {
|
||||
@@ -96,10 +99,12 @@ mod sys {
|
||||
//! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc)
|
||||
#![allow(dead_code, non_upper_case_globals)]
|
||||
|
||||
use anyhow::Result;
|
||||
use core_graphics::display::CGDirectDisplayID;
|
||||
use foreign_types::{foreign_type, ForeignType};
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
fmt::{Debug, Formatter, Result},
|
||||
fmt::{self, Debug, Formatter},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -114,7 +119,7 @@ mod sys {
|
||||
}
|
||||
|
||||
impl Debug for DisplayLink {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> Result {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
formatter
|
||||
.debug_tuple("DisplayLink")
|
||||
.field(&self.as_ptr())
|
||||
@@ -201,19 +206,15 @@ mod sys {
|
||||
pub fn CVDisplayLinkCreateWithActiveCGDisplays(
|
||||
display_link_out: *mut *mut CVDisplayLink,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkCreateWithCGDisplay(
|
||||
pub fn CVDisplayLinkSetCurrentCGDisplay(
|
||||
display_link: &mut DisplayLinkRef,
|
||||
display_id: u32,
|
||||
display_link_out: *mut *mut CVDisplayLink,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkSetOutputCallback(
|
||||
display_link: &mut DisplayLinkRef,
|
||||
callback: CVDisplayLinkOutputCallback,
|
||||
user_info: *mut c_void,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkSetCurrentCGDisplay(
|
||||
display_link: &mut DisplayLinkRef,
|
||||
display_id: u32,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkStart(display_link: &mut DisplayLinkRef) -> i32;
|
||||
pub fn CVDisplayLinkStop(display_link: &mut DisplayLinkRef) -> i32;
|
||||
pub fn CVDisplayLinkRelease(display_link: *mut CVDisplayLink);
|
||||
@@ -221,52 +222,46 @@ mod sys {
|
||||
}
|
||||
|
||||
impl DisplayLink {
|
||||
/// Apple docs: [CVDisplayLinkCreateWithActiveCGDisplays](https://developer.apple.com/documentation/corevideo/1456863-cvdisplaylinkcreatewithactivecgd?language=objc)
|
||||
pub unsafe fn new() -> Option<Self> {
|
||||
let mut display_link: *mut CVDisplayLink = 0 as _;
|
||||
let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
|
||||
if code == 0 {
|
||||
Some(DisplayLink::from_ptr(display_link))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkCreateWithCGDisplay](https://developer.apple.com/documentation/corevideo/1456981-cvdisplaylinkcreatewithcgdisplay?language=objc)
|
||||
pub unsafe fn on_display(display_id: u32) -> Option<Self> {
|
||||
pub unsafe fn new(
|
||||
display_id: CGDirectDisplayID,
|
||||
callback: CVDisplayLinkOutputCallback,
|
||||
user_info: *mut c_void,
|
||||
) -> Result<Self> {
|
||||
let mut display_link: *mut CVDisplayLink = 0 as _;
|
||||
let code = CVDisplayLinkCreateWithCGDisplay(display_id, &mut display_link);
|
||||
if code == 0 {
|
||||
Some(DisplayLink::from_ptr(display_link))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
|
||||
anyhow::ensure!(code == 0, "could not create display link, code: {}", code);
|
||||
|
||||
let mut display_link = DisplayLink::from_ptr(display_link);
|
||||
|
||||
let code = CVDisplayLinkSetOutputCallback(&mut display_link, callback, user_info);
|
||||
anyhow::ensure!(code == 0, "could not set output callback, code: {}", code);
|
||||
|
||||
let code = CVDisplayLinkSetCurrentCGDisplay(&mut display_link, display_id);
|
||||
anyhow::ensure!(
|
||||
code == 0,
|
||||
"could not assign display to display link, code: {}",
|
||||
code
|
||||
);
|
||||
|
||||
Ok(display_link)
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayLinkRef {
|
||||
/// Apple docs: [CVDisplayLinkSetOutputCallback](https://developer.apple.com/documentation/corevideo/1457096-cvdisplaylinksetoutputcallback?language=objc)
|
||||
pub unsafe fn set_output_callback(
|
||||
&mut self,
|
||||
callback: CVDisplayLinkOutputCallback,
|
||||
user_info: *mut c_void,
|
||||
) {
|
||||
assert_eq!(CVDisplayLinkSetOutputCallback(self, callback, user_info), 0);
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkSetCurrentCGDisplay](https://developer.apple.com/documentation/corevideo/1456768-cvdisplaylinksetcurrentcgdisplay?language=objc)
|
||||
pub unsafe fn set_current_display(&mut self, display_id: u32) {
|
||||
assert_eq!(CVDisplayLinkSetCurrentCGDisplay(self, display_id), 0);
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkStart](https://developer.apple.com/documentation/corevideo/1457193-cvdisplaylinkstart?language=objc)
|
||||
pub unsafe fn start(&mut self) {
|
||||
assert_eq!(CVDisplayLinkStart(self), 0);
|
||||
pub unsafe fn start(&mut self) -> Result<()> {
|
||||
let code = CVDisplayLinkStart(self);
|
||||
anyhow::ensure!(code == 0, "could not start display link, code: {}", code);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkStop](https://developer.apple.com/documentation/corevideo/1457281-cvdisplaylinkstop?language=objc)
|
||||
pub unsafe fn stop(&mut self) {
|
||||
assert_eq!(CVDisplayLinkStop(self), 0);
|
||||
pub unsafe fn stop(&mut self) -> Result<()> {
|
||||
let code = CVDisplayLinkStop(self);
|
||||
anyhow::ensure!(code == 0, "could not stop display link, code: {}", code);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,18 +15,21 @@ 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"));
|
||||
#[cfg(feature = "runtime_shaders")]
|
||||
const SHADERS_SOURCE_FILE: &'static str =
|
||||
include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
|
||||
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...)
|
||||
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...)
|
||||
|
||||
pub(crate) struct MetalRenderer {
|
||||
device: metal::Device,
|
||||
layer: metal::MetalLayer,
|
||||
presents_with_transaction: bool,
|
||||
command_queue: CommandQueue,
|
||||
paths_rasterization_pipeline_state: metal::RenderPipelineState,
|
||||
path_sprites_pipeline_state: metal::RenderPipelineState,
|
||||
@@ -36,13 +40,14 @@ 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_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>,
|
||||
sprite_atlas: Arc<MetalAtlas>,
|
||||
core_video_texture_cache: CVMetalTextureCache,
|
||||
}
|
||||
|
||||
impl MetalRenderer {
|
||||
pub fn new(is_opaque: bool) -> Self {
|
||||
pub fn new(instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>) -> Self {
|
||||
let device: metal::Device = if let Some(device) = metal::Device::system_default() {
|
||||
device
|
||||
} else {
|
||||
@@ -53,8 +58,8 @@ impl MetalRenderer {
|
||||
let layer = metal::MetalLayer::new();
|
||||
layer.set_device(&device);
|
||||
layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
|
||||
layer.set_presents_with_transaction(true);
|
||||
layer.set_opaque(is_opaque);
|
||||
layer.set_opaque(true);
|
||||
layer.set_maximum_drawable_count(3);
|
||||
unsafe {
|
||||
let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
|
||||
let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
|
||||
@@ -93,10 +98,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,9 +166,13 @@ 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,
|
||||
presents_with_transaction: false,
|
||||
command_queue,
|
||||
paths_rasterization_pipeline_state,
|
||||
path_sprites_pipeline_state,
|
||||
@@ -178,9 +183,9 @@ impl MetalRenderer {
|
||||
polychrome_sprites_pipeline_state,
|
||||
surfaces_pipeline_state,
|
||||
unit_vertices,
|
||||
instances,
|
||||
instance_buffer_pool,
|
||||
sprite_atlas,
|
||||
core_video_texture_cache: unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() },
|
||||
core_video_texture_cache,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +197,12 @@ impl MetalRenderer {
|
||||
&self.sprite_atlas
|
||||
}
|
||||
|
||||
pub fn set_presents_with_transaction(&mut self, presents_with_transaction: bool) {
|
||||
self.presents_with_transaction = presents_with_transaction;
|
||||
self.layer
|
||||
.set_presents_with_transaction(presents_with_transaction);
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, scene: &Scene) {
|
||||
let layer = self.layer.clone();
|
||||
let viewport_size = layer.drawable_size();
|
||||
@@ -208,14 +219,24 @@ 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_offset, command_buffer)
|
||||
else {
|
||||
panic!("failed to rasterize {} paths", scene.paths().len());
|
||||
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 render_pass_descriptor = metal::RenderPassDescriptor::new();
|
||||
@@ -243,22 +264,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 +297,7 @@ impl MetalRenderer {
|
||||
} => self.draw_monochrome_sprites(
|
||||
texture_id,
|
||||
sprites,
|
||||
&mut instance_buffer,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
@@ -279,12 +308,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,
|
||||
@@ -292,7 +323,7 @@ impl MetalRenderer {
|
||||
};
|
||||
|
||||
if !ok {
|
||||
panic!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",
|
||||
log::error!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",
|
||||
scene.paths.len(),
|
||||
scene.shadows.len(),
|
||||
scene.quads.len(),
|
||||
@@ -300,28 +331,45 @@ impl MetalRenderer {
|
||||
scene.monochrome_sprites.len(),
|
||||
scene.polychrome_sprites.len(),
|
||||
scene.surfaces.len(),
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
command_encoder.end_encoding();
|
||||
|
||||
self.instances.did_modify_range(NSRange {
|
||||
instance_buffer.did_modify_range(NSRange {
|
||||
location: 0,
|
||||
length: instance_offset as NSUInteger,
|
||||
});
|
||||
|
||||
command_buffer.commit();
|
||||
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);
|
||||
|
||||
self.sprite_atlas.clear_textures(AtlasTextureKind::Path);
|
||||
|
||||
command_buffer.wait_until_completed();
|
||||
drawable.present();
|
||||
if self.presents_with_transaction {
|
||||
command_buffer.commit();
|
||||
command_buffer.wait_until_scheduled();
|
||||
drawable.present();
|
||||
} else {
|
||||
command_buffer.present_drawable(drawable);
|
||||
command_buffer.commit();
|
||||
}
|
||||
}
|
||||
|
||||
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 +395,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 +417,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 +430,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 +446,7 @@ impl MetalRenderer {
|
||||
vertices.len() as u64,
|
||||
);
|
||||
command_encoder.end_encoding();
|
||||
*offset = next_offset;
|
||||
*instance_offset = next_offset;
|
||||
}
|
||||
|
||||
Some(tiles)
|
||||
@@ -406,14 +455,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 +473,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 +489,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 +511,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 +536,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 +552,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 +570,7 @@ impl MetalRenderer {
|
||||
6,
|
||||
quads.len() as u64,
|
||||
);
|
||||
*offset = next_offset;
|
||||
*instance_offset = next_offset;
|
||||
true
|
||||
}
|
||||
|
||||
@@ -525,7 +578,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 +627,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 +637,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 +647,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 +676,7 @@ impl MetalRenderer {
|
||||
6,
|
||||
sprites.len() as u64,
|
||||
);
|
||||
*offset = next_offset;
|
||||
*instance_offset = next_offset;
|
||||
sprites.clear();
|
||||
}
|
||||
}
|
||||
@@ -632,14 +686,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 +704,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 +720,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 +742,7 @@ impl MetalRenderer {
|
||||
6,
|
||||
underlines.len() as u64,
|
||||
);
|
||||
*offset = next_offset;
|
||||
*instance_offset = next_offset;
|
||||
true
|
||||
}
|
||||
|
||||
@@ -694,14 +750,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 +773,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 +788,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 +816,7 @@ impl MetalRenderer {
|
||||
6,
|
||||
sprites.len() as u64,
|
||||
);
|
||||
*offset = next_offset;
|
||||
*instance_offset = next_offset;
|
||||
true
|
||||
}
|
||||
|
||||
@@ -766,14 +824,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 +847,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 +862,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 +890,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 +950,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 +976,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 +988,7 @@ impl MetalRenderer {
|
||||
}
|
||||
|
||||
command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6);
|
||||
*offset = next_offset;
|
||||
*instance_offset = next_offset;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::{events::key_to_native, BoolExt};
|
||||
use crate::{
|
||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
||||
ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem,
|
||||
MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
|
||||
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, WindowOptions,
|
||||
ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacTextSystem, MacWindow, Menu,
|
||||
MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem,
|
||||
PlatformWindow, Result, SemanticVersion, Task, WindowOptions,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use block::ConcreteBlock;
|
||||
@@ -145,7 +145,7 @@ pub(crate) struct MacPlatformState {
|
||||
background_executor: BackgroundExecutor,
|
||||
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,
|
||||
@@ -175,7 +175,7 @@ impl MacPlatform {
|
||||
background_executor: BackgroundExecutor::new(dispatcher.clone()),
|
||||
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") },
|
||||
@@ -494,26 +494,13 @@ impl Platform for MacPlatform {
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
) -> Box<dyn PlatformWindow> {
|
||||
Box::new(MacWindow::open(handle, options, self.foreground_executor()))
|
||||
}
|
||||
|
||||
fn set_display_link_output_callback(
|
||||
&self,
|
||||
display_id: DisplayId,
|
||||
callback: Box<dyn FnMut() + Send>,
|
||||
) {
|
||||
self.0
|
||||
.lock()
|
||||
.display_linker
|
||||
.set_output_callback(display_id, callback);
|
||||
}
|
||||
|
||||
fn start_display_link(&self, display_id: DisplayId) {
|
||||
self.0.lock().display_linker.start(display_id);
|
||||
}
|
||||
|
||||
fn stop_display_link(&self, display_id: DisplayId) {
|
||||
self.0.lock().display_linker.stop(display_id);
|
||||
let instance_buffer_pool = self.0.lock().instance_buffer_pool.clone();
|
||||
Box::new(MacWindow::open(
|
||||
handle,
|
||||
options,
|
||||
self.foreground_executor(),
|
||||
instance_buffer_pool,
|
||||
))
|
||||
}
|
||||
|
||||
fn open_url(&self, url: &str) {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use super::{global_bounds_from_ns_rect, ns_string, MacDisplay, MetalRenderer, NSRange};
|
||||
use crate::{
|
||||
global_bounds_to_ns_rect, platform::PlatformInputHandler, point, px, size, AnyWindowHandle,
|
||||
Bounds, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels, KeyDownEvent,
|
||||
Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
|
||||
PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
|
||||
Bounds, DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels,
|
||||
KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
||||
PlatformWindow, Point, PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind,
|
||||
WindowOptions,
|
||||
};
|
||||
use block::ConcreteBlock;
|
||||
use cocoa::{
|
||||
@@ -12,7 +13,7 @@ use cocoa::{
|
||||
CGPoint, NSApplication, NSBackingStoreBuffered, NSEventModifierFlags,
|
||||
NSFilenamesPboardType, NSPasteboard, NSScreen, NSView, NSViewHeightSizable,
|
||||
NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior,
|
||||
NSWindowStyleMask, NSWindowTitleVisibility,
|
||||
NSWindowOcclusionState, NSWindowStyleMask, NSWindowTitleVisibility,
|
||||
},
|
||||
base::{id, nil},
|
||||
foundation::{
|
||||
@@ -20,7 +21,7 @@ use cocoa::{
|
||||
NSSize, NSString, NSUInteger,
|
||||
},
|
||||
};
|
||||
use core_graphics::display::CGRect;
|
||||
use core_graphics::display::{CGDirectDisplayID, CGRect};
|
||||
use ctor::ctor;
|
||||
use foreign_types::ForeignTypeRef;
|
||||
use futures::channel::oneshot;
|
||||
@@ -50,6 +51,7 @@ use std::{
|
||||
sync::{Arc, Weak},
|
||||
time::Duration,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
const WINDOW_STATE_IVAR: &str = "windowState";
|
||||
|
||||
@@ -248,6 +250,10 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C
|
||||
sel!(windowDidResize:),
|
||||
window_did_resize as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(windowDidChangeOcclusionState:),
|
||||
window_did_change_occlusion_state as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(windowWillEnterFullScreen:),
|
||||
window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id),
|
||||
@@ -260,6 +266,10 @@ 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),
|
||||
@@ -320,7 +330,8 @@ struct MacWindowState {
|
||||
handle: AnyWindowHandle,
|
||||
executor: ForegroundExecutor,
|
||||
native_window: id,
|
||||
native_view: NonNull<id>,
|
||||
native_view: NonNull<Object>,
|
||||
display_link: Option<DisplayLink>,
|
||||
renderer: MetalRenderer,
|
||||
kind: WindowKind,
|
||||
request_frame_callback: Option<Box<dyn FnMut()>>,
|
||||
@@ -392,6 +403,21 @@ impl MacWindowState {
|
||||
}
|
||||
}
|
||||
|
||||
fn start_display_link(&mut self) {
|
||||
self.stop_display_link();
|
||||
let display_id = unsafe { display_id_for_screen(self.native_window.screen()) };
|
||||
if let Some(mut display_link) =
|
||||
DisplayLink::new(display_id, self.native_view.as_ptr() as *mut c_void, step).log_err()
|
||||
{
|
||||
display_link.start().log_err();
|
||||
self.display_link = Some(display_link);
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_display_link(&mut self) {
|
||||
self.display_link = None;
|
||||
}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
unsafe {
|
||||
let style_mask = self.native_window.styleMask();
|
||||
@@ -458,6 +484,7 @@ impl MacWindow {
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
executor: ForegroundExecutor,
|
||||
instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>,
|
||||
) -> Self {
|
||||
unsafe {
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
@@ -495,11 +522,8 @@ impl MacWindow {
|
||||
let count: u64 = cocoa::foundation::NSArray::count(screens);
|
||||
for i in 0..count {
|
||||
let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
|
||||
let device_description = NSScreen::deviceDescription(screen);
|
||||
let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
|
||||
let screen_number = device_description.objectForKey_(screen_number_key);
|
||||
let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
|
||||
if screen_number as u32 == display.id().0 {
|
||||
let display_id = display_id_for_screen(screen);
|
||||
if display_id == display.id().0 {
|
||||
target_screen = screen;
|
||||
break;
|
||||
}
|
||||
@@ -521,15 +545,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 window = Self(Arc::new(Mutex::new(MacWindowState {
|
||||
handle,
|
||||
executor,
|
||||
native_window,
|
||||
native_view: NonNull::new_unchecked(native_view as *mut _),
|
||||
renderer: MetalRenderer::new(true),
|
||||
native_view: NonNull::new_unchecked(native_view),
|
||||
display_link: None,
|
||||
renderer: MetalRenderer::new(instance_buffer_pool),
|
||||
kind: options.kind,
|
||||
request_frame_callback: None,
|
||||
event_callback: None,
|
||||
@@ -663,6 +687,7 @@ impl MacWindow {
|
||||
}
|
||||
|
||||
window.0.lock().move_traffic_light();
|
||||
|
||||
pool.drain();
|
||||
|
||||
window
|
||||
@@ -685,8 +710,9 @@ impl MacWindow {
|
||||
|
||||
impl Drop for MacWindow {
|
||||
fn drop(&mut self) {
|
||||
let this = self.0.lock();
|
||||
let mut this = self.0.lock();
|
||||
let window = this.native_window;
|
||||
this.display_link.take();
|
||||
this.executor
|
||||
.spawn(async move {
|
||||
unsafe {
|
||||
@@ -1000,13 +1026,6 @@ 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);
|
||||
@@ -1320,6 +1339,22 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn window_did_change_occlusion_state(this: &Object, _: Sel, _: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let lock = &mut *window_state.lock();
|
||||
unsafe {
|
||||
if lock
|
||||
.native_window
|
||||
.occlusionState()
|
||||
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible)
|
||||
{
|
||||
lock.start_display_link();
|
||||
} else {
|
||||
lock.stop_display_link();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
window_state.as_ref().lock().move_traffic_light();
|
||||
@@ -1353,6 +1388,12 @@ 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();
|
||||
lock.start_display_link();
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -1495,6 +1536,24 @@ extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
|
||||
extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let mut lock = window_state.lock();
|
||||
if let Some(mut callback) = lock.request_frame_callback.take() {
|
||||
lock.renderer.set_presents_with_transaction(true);
|
||||
lock.stop_display_link();
|
||||
drop(lock);
|
||||
callback();
|
||||
|
||||
let mut lock = window_state.lock();
|
||||
lock.request_frame_callback = Some(callback);
|
||||
lock.renderer.set_presents_with_transaction(false);
|
||||
lock.start_display_link();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn step(view: *mut c_void) {
|
||||
let view = view as id;
|
||||
let window_state = unsafe { get_window_state(&*view) };
|
||||
let mut lock = window_state.lock();
|
||||
|
||||
if let Some(mut callback) = lock.request_frame_callback.take() {
|
||||
drop(lock);
|
||||
callback();
|
||||
@@ -1821,3 +1880,11 @@ where
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn display_id_for_screen(screen: id) -> CGDirectDisplayID {
|
||||
let device_description = NSScreen::deviceDescription(screen);
|
||||
let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
|
||||
let screen_number = device_description.objectForKey_(screen_number_key);
|
||||
let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
|
||||
screen_number as CGDirectDisplayID
|
||||
}
|
||||
|
||||
@@ -176,19 +176,7 @@ impl Platform for TestPlatform {
|
||||
Box::new(window)
|
||||
}
|
||||
|
||||
fn set_display_link_output_callback(
|
||||
&self,
|
||||
_display_id: DisplayId,
|
||||
mut callback: Box<dyn FnMut() + Send>,
|
||||
) {
|
||||
callback()
|
||||
}
|
||||
|
||||
fn start_display_link(&self, _display_id: DisplayId) {}
|
||||
|
||||
fn stop_display_link(&self, _display_id: DisplayId) {}
|
||||
|
||||
fn open_url(&self, _url: &str) {
|
||||
fn open_url(&self, _: &str) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
|
||||
@@ -284,8 +284,6 @@ impl PlatformWindow for TestWindow {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn invalidate(&self) {}
|
||||
|
||||
fn draw(&self, _scene: &crate::Scene) {}
|
||||
|
||||
fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
|
||||
|
||||
@@ -15,6 +15,7 @@ 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};
|
||||
@@ -38,9 +39,8 @@ pub struct FontFamilyId(pub usize);
|
||||
|
||||
pub(crate) const SUBPIXEL_VARIANTS: u8 = 4;
|
||||
|
||||
/// The GPUI text layout and rendering sub system.
|
||||
/// The GPUI text 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,7 +53,6 @@ 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(),
|
||||
@@ -234,43 +233,66 @@ impl TextSystem {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_view<R>(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R {
|
||||
self.line_layout_cache.with_view(view_id, f)
|
||||
/// 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(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
});
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
let layout = self
|
||||
.line_layout_cache
|
||||
.layout_line(text, font_size, &font_runs);
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
font_runs.clear();
|
||||
self.font_runs_pool.lock().push(font_runs);
|
||||
/// The GPUI text layout subsystem.
|
||||
#[derive(Deref)]
|
||||
pub struct WindowTextSystem {
|
||||
line_layout_cache: Arc<LineLayoutCache>,
|
||||
#[deref]
|
||||
text_system: Arc<TextSystem>,
|
||||
}
|
||||
|
||||
Ok(layout)
|
||||
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)
|
||||
}
|
||||
|
||||
/// Shape the given line, at the given font_size, for painting to the screen.
|
||||
@@ -429,43 +451,39 @@ impl TextSystem {
|
||||
self.line_layout_cache.finish_frame(reused_views)
|
||||
}
|
||||
|
||||
/// 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(
|
||||
/// 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,
|
||||
params: &RenderGlyphParams,
|
||||
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
|
||||
let raster_bounds = self.raster_bounds(params)?;
|
||||
self.platform_text_system
|
||||
.rasterize_glyph(params, raster_bounds)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ impl Boundary {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{font, TestAppContext, TestDispatcher, TextRun, WrapBoundary};
|
||||
use crate::{font, TestAppContext, TestDispatcher, TextRun, WindowTextSystem, 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 = cx.text_system().clone();
|
||||
let text_system = WindowTextSystem::new(cx.text_system().clone());
|
||||
|
||||
let normal = TextRun {
|
||||
len: 0,
|
||||
|
||||
@@ -2,28 +2,24 @@ 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, 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,
|
||||
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,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::FxHashSet;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
StreamExt,
|
||||
};
|
||||
use futures::channel::oneshot;
|
||||
use parking_lot::RwLock;
|
||||
use slotmap::SlotMap;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
borrow::{Borrow, BorrowMut},
|
||||
cell::RefCell,
|
||||
collections::hash_map::Entry,
|
||||
cell::{Cell, RefCell},
|
||||
fmt::{Debug, Display},
|
||||
future::Future,
|
||||
hash::{Hash, Hasher},
|
||||
@@ -34,7 +30,7 @@ use std::{
|
||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||
Arc,
|
||||
},
|
||||
time::Duration,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use util::{measure, ResultExt};
|
||||
|
||||
@@ -243,6 +239,8 @@ impl<M: FocusableView + EventEmitter<DismissEvent>> ManagedView for M {}
|
||||
/// Emitted by implementers of [`ManagedView`] to indicate the view should be dismissed, such as when a view is presented as a modal.
|
||||
pub struct DismissEvent;
|
||||
|
||||
type FrameCallback = Box<dyn FnOnce(&mut WindowContext)>;
|
||||
|
||||
// Holds the state for a specific window.
|
||||
#[doc(hidden)]
|
||||
pub struct Window {
|
||||
@@ -251,6 +249,7 @@ 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>,
|
||||
@@ -258,6 +257,7 @@ pub struct Window {
|
||||
pub(crate) element_id_stack: GlobalElementId,
|
||||
pub(crate) rendered_frame: Frame,
|
||||
pub(crate) next_frame: Frame,
|
||||
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
|
||||
pub(crate) dirty_views: FxHashSet<EntityId>,
|
||||
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||
focus_listeners: SubscriberSet<(), AnyWindowFocusListener>,
|
||||
@@ -268,17 +268,15 @@ pub struct Window {
|
||||
scale_factor: f32,
|
||||
bounds: WindowBounds,
|
||||
bounds_observers: SubscriberSet<(), AnyObserver>,
|
||||
active: bool,
|
||||
pub(crate) dirty: bool,
|
||||
active: Rc<Cell<bool>>,
|
||||
pub(crate) dirty: Rc<Cell<bool>>,
|
||||
pub(crate) last_input_timestamp: Rc<Cell<Instant>>,
|
||||
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)]
|
||||
@@ -290,10 +288,6 @@ 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()
|
||||
@@ -337,13 +331,47 @@ 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 next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>> = Default::default();
|
||||
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 next_frame_callbacks = next_frame_callbacks.clone();
|
||||
let last_input_timestamp = last_input_timestamp.clone();
|
||||
move || {
|
||||
measure("frame duration", || {
|
||||
handle.update(&mut cx, |_, cx| cx.draw()).log_err();
|
||||
})
|
||||
let next_frame_callbacks = next_frame_callbacks.take();
|
||||
if !next_frame_callbacks.is_empty() {
|
||||
handle
|
||||
.update(&mut cx, |_, cx| {
|
||||
for callback in next_frame_callbacks {
|
||||
callback(cx);
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}));
|
||||
platform_window.on_resize(Box::new({
|
||||
@@ -367,7 +395,7 @@ impl Window {
|
||||
move |active| {
|
||||
handle
|
||||
.update(&mut cx, |_, cx| {
|
||||
cx.window.active = active;
|
||||
cx.window.active.set(active);
|
||||
cx.window
|
||||
.activation_observers
|
||||
.clone()
|
||||
@@ -393,6 +421,7 @@ impl Window {
|
||||
platform_window,
|
||||
display_id,
|
||||
sprite_atlas,
|
||||
text_system,
|
||||
rem_size: px(16.),
|
||||
viewport_size: content_size,
|
||||
layout_engine: Some(TaffyLayoutEngine::new()),
|
||||
@@ -400,6 +429,7 @@ impl Window {
|
||||
element_id_stack: GlobalElementId::default(),
|
||||
rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||
next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||
next_frame_callbacks,
|
||||
dirty_views: FxHashSet::default(),
|
||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||
focus_listeners: SubscriberSet::new(),
|
||||
@@ -410,17 +440,15 @@ impl Window {
|
||||
scale_factor,
|
||||
bounds,
|
||||
bounds_observers: SubscriberSet::new(),
|
||||
active: false,
|
||||
dirty: false,
|
||||
active,
|
||||
dirty,
|
||||
last_input_timestamp,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -472,7 +500,7 @@ impl<'a> WindowContext<'a> {
|
||||
pub fn refresh(&mut self) {
|
||||
if !self.window.drawing {
|
||||
self.window.refreshing = true;
|
||||
self.window.dirty = true;
|
||||
self.window.dirty.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,12 +533,6 @@ impl<'a> WindowContext<'a> {
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.clear_pending_keystrokes();
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
{
|
||||
self.window.focus_invalidated = true;
|
||||
}
|
||||
|
||||
self.refresh();
|
||||
}
|
||||
|
||||
@@ -530,6 +552,11 @@ 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();
|
||||
@@ -639,57 +666,7 @@ impl<'a> WindowContext<'a> {
|
||||
|
||||
/// Schedule the given closure to be run directly after the current frame is rendered.
|
||||
pub fn on_next_frame(&mut self, callback: impl FnOnce(&mut WindowContext) + 'static) {
|
||||
let handle = self.window.handle;
|
||||
let display_id = self.window.display_id;
|
||||
|
||||
let mut frame_consumers = std::mem::take(&mut self.app.frame_consumers);
|
||||
if let Entry::Vacant(e) = frame_consumers.entry(display_id) {
|
||||
let (tx, mut rx) = mpsc::unbounded::<()>();
|
||||
self.platform.set_display_link_output_callback(
|
||||
display_id,
|
||||
Box::new(move || _ = tx.unbounded_send(())),
|
||||
);
|
||||
|
||||
let consumer_task = self.app.spawn(|cx| async move {
|
||||
while rx.next().await.is_some() {
|
||||
cx.update(|cx| {
|
||||
for callback in cx
|
||||
.next_frame_callbacks
|
||||
.get_mut(&display_id)
|
||||
.unwrap()
|
||||
.drain(..)
|
||||
.collect::<SmallVec<[_; 32]>>()
|
||||
{
|
||||
callback(cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
|
||||
// Flush effects, then stop the display link if no new next_frame_callbacks have been added.
|
||||
|
||||
cx.update(|cx| {
|
||||
if cx.next_frame_callbacks.is_empty() {
|
||||
cx.platform.stop_display_link(display_id);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
e.insert(consumer_task);
|
||||
}
|
||||
debug_assert!(self.app.frame_consumers.is_empty());
|
||||
self.app.frame_consumers = frame_consumers;
|
||||
|
||||
if self.next_frame_callbacks.is_empty() {
|
||||
self.platform.start_display_link(display_id);
|
||||
}
|
||||
|
||||
self.next_frame_callbacks
|
||||
.entry(display_id)
|
||||
.or_default()
|
||||
.push(Box::new(move |cx: &mut AppContext| {
|
||||
cx.update_window(handle, |_root_view, cx| callback(cx)).ok();
|
||||
}));
|
||||
RefCell::borrow_mut(&self.window.next_frame_callbacks).push(Box::new(callback));
|
||||
}
|
||||
|
||||
/// Spawn the future returned by the given closure on the application thread pool.
|
||||
@@ -741,7 +718,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
|
||||
self.window.active.get()
|
||||
}
|
||||
|
||||
/// Toggle zoom on the window.
|
||||
@@ -927,16 +904,12 @@ impl<'a> WindowContext<'a> {
|
||||
&self.window.next_frame.z_index_stack
|
||||
}
|
||||
|
||||
/// Draw pixels to the display for this window based on the contents of its scene.
|
||||
/// Produces a new frame and assigns it to `rendered_frame`. To actually show
|
||||
/// the contents of the new [Scene], use [present].
|
||||
pub(crate) fn draw(&mut self) {
|
||||
self.window.dirty = false;
|
||||
self.window.dirty.set(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();
|
||||
@@ -1001,7 +974,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;
|
||||
self.window.next_frame.window_active = self.window.active.get();
|
||||
self.window.root_view = Some(root_view);
|
||||
|
||||
// Set the cursor only if we're the active window.
|
||||
@@ -1070,16 +1043,19 @@ 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`.
|
||||
@@ -1243,21 +1219,12 @@ impl<'a> WindowContext<'a> {
|
||||
.dispatch_path(node_id);
|
||||
|
||||
if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
|
||||
let KeymatchResult {
|
||||
bindings,
|
||||
mut pending,
|
||||
} = self
|
||||
let KeymatchResult { bindings, 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
|
||||
@@ -1272,22 +1239,17 @@ impl<'a> WindowContext<'a> {
|
||||
currently_pending.bindings.push(binding);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
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();
|
||||
}));
|
||||
self.window.pending_input = Some(currently_pending);
|
||||
|
||||
self.propagate_event = false;
|
||||
@@ -1315,8 +1277,21 @@ 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() {
|
||||
@@ -1342,8 +1317,6 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.dispatch_keystroke_observers(event, None);
|
||||
}
|
||||
|
||||
/// Determine whether a potential multi-stroke key binding is in progress on this window.
|
||||
@@ -1380,6 +1353,24 @@ 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);
|
||||
@@ -2023,7 +2014,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
}
|
||||
|
||||
if !self.window.drawing {
|
||||
self.window_cx.window.dirty = true;
|
||||
self.window_cx.window.dirty.set(true);
|
||||
self.window_cx.app.push_effect(Effect::Notify {
|
||||
emitter: self.view.model.entity_id,
|
||||
});
|
||||
@@ -2489,7 +2480,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))
|
||||
.and_then(|window| window.as_ref().map(|window| window.active.get()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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, 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,
|
||||
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,
|
||||
};
|
||||
|
||||
type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut ElementContext) + 'static>;
|
||||
@@ -1143,15 +1143,6 @@ 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.
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3984,7 +3985,7 @@ impl Project {
|
||||
range.end = snapshot.clip_point_utf16(Unclipped(range.end), Bias::Right);
|
||||
if range.start == range.end && range.end.column > 0 {
|
||||
range.start.column -= 1;
|
||||
range.end = snapshot.clip_point_utf16(Unclipped(range.end), Bias::Left);
|
||||
range.start = snapshot.clip_point_utf16(Unclipped(range.start), Bias::Left);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -11,7 +11,7 @@ doctest = false
|
||||
|
||||
|
||||
[dependencies]
|
||||
alacritty_terminal = "0.21"
|
||||
alacritty_terminal = "0.22.0"
|
||||
anyhow.workspace = true
|
||||
db = { path = "../db" }
|
||||
dirs = "4.0.0"
|
||||
|
||||
@@ -364,7 +364,7 @@ impl TerminalBuilder {
|
||||
pty,
|
||||
pty_options.hold,
|
||||
false,
|
||||
);
|
||||
)?;
|
||||
|
||||
//Kick things off
|
||||
let pty_tx = event_loop.channel();
|
||||
|
||||
@@ -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, TextSystem,
|
||||
UnderlineStyle, WeakView, WhiteSpace, WindowContext,
|
||||
Pixels, Point, ShapedLine, StatefulInteractiveElement, Styled, TextRun, TextStyle,
|
||||
UnderlineStyle, WeakView, WhiteSpace, WindowContext, WindowTextSystem,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::CursorShape;
|
||||
@@ -185,7 +185,7 @@ impl TerminalElement {
|
||||
grid: &Vec<IndexedCell>,
|
||||
text_style: &TextStyle,
|
||||
// terminal_theme: &TerminalStyle,
|
||||
text_system: &TextSystem,
|
||||
text_system: &WindowTextSystem,
|
||||
hyperlink: Option<(HighlightStyle, &RangeInclusive<AlacPoint>)>,
|
||||
cx: &WindowContext<'_>,
|
||||
) -> (Vec<LayoutCell>, Vec<LayoutRect>) {
|
||||
@@ -450,6 +450,13 @@ impl TerminalElement {
|
||||
let mut size = bounds.size.clone();
|
||||
size.width -= gutter;
|
||||
|
||||
// https://github.com/zed-industries/zed/issues/2750
|
||||
// if the terminal is one column wide, rendering 🦀
|
||||
// causes alacritty to misbehave.
|
||||
if size.width < cell_width * 2.0 {
|
||||
size.width = cell_width * 2.0;
|
||||
}
|
||||
|
||||
TerminalSize::new(line_height, cell_width, size)
|
||||
};
|
||||
|
||||
@@ -776,7 +783,6 @@ impl Element for TerminalElement {
|
||||
self.interactivity
|
||||
.paint(bounds, bounds.size, state, cx, |_, _, cx| {
|
||||
cx.handle_input(&self.focus, terminal_input_handler);
|
||||
cx.keymatch_mode_immediate();
|
||||
|
||||
cx.on_key_event({
|
||||
let this = self.terminal.clone();
|
||||
|
||||
@@ -255,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 {
|
||||
|
||||
@@ -27,6 +27,7 @@ pub struct GithubReleaseAsset {
|
||||
|
||||
pub async fn latest_github_release(
|
||||
repo_name_with_owner: &str,
|
||||
require_assets: bool,
|
||||
pre_release: bool,
|
||||
http: Arc<dyn HttpClient>,
|
||||
) -> Result<GithubRelease, anyhow::Error> {
|
||||
@@ -68,6 +69,7 @@ pub async fn latest_github_release(
|
||||
|
||||
releases
|
||||
.into_iter()
|
||||
.filter(|release| !require_assets || !release.assets.is_empty())
|
||||
.find(|release| 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.7"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
stable
|
||||
@@ -28,7 +28,8 @@ impl super::LspAdapter for CLspAdapter {
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release = latest_github_release("clangd/clangd", false, delegate.http_client()).await?;
|
||||
let release =
|
||||
latest_github_release("clangd/clangd", true, false, delegate.http_client()).await?;
|
||||
let asset_name = format!("clangd-mac-{}.zip", release.name);
|
||||
let asset = release
|
||||
.assets
|
||||
|
||||
@@ -29,10 +29,6 @@ impl super::LspAdapter for OmniSharpAdapter {
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release =
|
||||
latest_github_release("OmniSharp/omnisharp-roslyn", false, delegate.http_client())
|
||||
.await?;
|
||||
|
||||
let mapped_arch = match ARCH {
|
||||
"aarch64" => Some("arm64"),
|
||||
"x86_64" => Some("x64"),
|
||||
@@ -42,6 +38,13 @@ impl super::LspAdapter for OmniSharpAdapter {
|
||||
match mapped_arch {
|
||||
None => Ok(Box::new(())),
|
||||
Some(arch) => {
|
||||
let release = latest_github_release(
|
||||
"OmniSharp/omnisharp-roslyn",
|
||||
true,
|
||||
false,
|
||||
delegate.http_client(),
|
||||
)
|
||||
.await?;
|
||||
let asset_name = format!("omnisharp-osx-{}-net6.0.tar.gz", arch);
|
||||
let asset = release
|
||||
.assets
|
||||
|
||||
@@ -70,7 +70,8 @@ impl LspAdapter for DenoLspAdapter {
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release = latest_github_release("denoland/deno", false, delegate.http_client()).await?;
|
||||
let release =
|
||||
latest_github_release("denoland/deno", true, false, delegate.http_client()).await?;
|
||||
let asset_name = format!("deno-{}-apple-darwin.zip", consts::ARCH);
|
||||
let asset = release
|
||||
.assets
|
||||
|
||||
@@ -111,19 +111,19 @@ impl LspAdapter for ElixirLspAdapter {
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let http = delegate.http_client();
|
||||
let release = latest_github_release("elixir-lsp/elixir-ls", false, http).await?;
|
||||
let release = latest_github_release("elixir-lsp/elixir-ls", true, false, http).await?;
|
||||
let version_name = release
|
||||
.name
|
||||
.strip_prefix("Release ")
|
||||
.context("Elixir-ls release name does not start with prefix")?
|
||||
.to_owned();
|
||||
|
||||
let asset_name = format!("elixir-ls-{}.zip", &version_name);
|
||||
let asset_name = format!("elixir-ls-{version_name}.zip");
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||
.ok_or_else(|| anyhow!("no asset found matching {asset_name:?}"))?;
|
||||
|
||||
let version = GitHubLspBinaryVersion {
|
||||
name: version_name,
|
||||
@@ -313,20 +313,21 @@ impl LspAdapter for NextLspAdapter {
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release =
|
||||
latest_github_release("elixir-tools/next-ls", false, delegate.http_client()).await?;
|
||||
let version = release.name.clone();
|
||||
let platform = match consts::ARCH {
|
||||
"x86_64" => "darwin_amd64",
|
||||
"aarch64" => "darwin_arm64",
|
||||
other => bail!("Running on unsupported platform: {other}"),
|
||||
};
|
||||
let asset_name = format!("next_ls_{}", platform);
|
||||
let release =
|
||||
latest_github_release("elixir-tools/next-ls", true, false, delegate.http_client())
|
||||
.await?;
|
||||
let version = release.name;
|
||||
let asset_name = format!("next_ls_{platform}");
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||
.with_context(|| format!("no asset found matching {asset_name:?}"))?;
|
||||
let version = GitHubLspBinaryVersion {
|
||||
name: version,
|
||||
url: asset.browser_download_url.clone(),
|
||||
|
||||
@@ -35,7 +35,7 @@ impl LspAdapter for GleamLspAdapter {
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release =
|
||||
latest_github_release("gleam-lang/gleam", false, delegate.http_client()).await?;
|
||||
latest_github_release("gleam-lang/gleam", true, false, delegate.http_client()).await?;
|
||||
|
||||
let asset_name = format!(
|
||||
"gleam-{version}-{arch}-apple-darwin.tar.gz",
|
||||
|
||||
@@ -45,7 +45,8 @@ impl super::LspAdapter for GoLspAdapter {
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release = latest_github_release("golang/tools", false, delegate.http_client()).await?;
|
||||
let release =
|
||||
latest_github_release("golang/tools", false, false, delegate.http_client()).await?;
|
||||
let version: Option<String> = release.name.strip_prefix("gopls/v").map(str::to_string);
|
||||
if version.is_none() {
|
||||
log::warn!(
|
||||
|
||||
@@ -30,15 +30,19 @@ impl super::LspAdapter for LuaLspAdapter {
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release =
|
||||
latest_github_release("LuaLS/lua-language-server", false, delegate.http_client())
|
||||
.await?;
|
||||
let version = release.name.clone();
|
||||
let platform = match consts::ARCH {
|
||||
"x86_64" => "x64",
|
||||
"aarch64" => "arm64",
|
||||
other => bail!("Running on unsupported platform: {other}"),
|
||||
};
|
||||
let release = latest_github_release(
|
||||
"LuaLS/lua-language-server",
|
||||
true,
|
||||
false,
|
||||
delegate.http_client(),
|
||||
)
|
||||
.await?;
|
||||
let version = &release.name;
|
||||
let asset_name = format!("lua-language-server-{version}-darwin-{platform}.tar.gz");
|
||||
let asset = release
|
||||
.assets
|
||||
@@ -46,7 +50,7 @@ impl super::LspAdapter for LuaLspAdapter {
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||
let version = GitHubLspBinaryVersion {
|
||||
name: release.name.clone(),
|
||||
name: release.name,
|
||||
url: asset.browser_download_url.clone(),
|
||||
};
|
||||
Ok(Box::new(version) as Box<_>)
|
||||
|
||||
@@ -31,8 +31,13 @@ impl LspAdapter for RustLspAdapter {
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release =
|
||||
latest_github_release("rust-lang/rust-analyzer", false, delegate.http_client()).await?;
|
||||
let release = latest_github_release(
|
||||
"rust-lang/rust-analyzer",
|
||||
true,
|
||||
false,
|
||||
delegate.http_client(),
|
||||
)
|
||||
.await?;
|
||||
let asset_name = format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH);
|
||||
let asset = release
|
||||
.assets
|
||||
|
||||
@@ -26,7 +26,8 @@ impl LspAdapter for TaploLspAdapter {
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release = latest_github_release("tamasfe/taplo", false, delegate.http_client()).await?;
|
||||
let release =
|
||||
latest_github_release("tamasfe/taplo", true, false, delegate.http_client()).await?;
|
||||
let asset_name = format!("taplo-full-darwin-{arch}.gz", arch = std::env::consts::ARCH);
|
||||
|
||||
let asset = release
|
||||
|
||||
@@ -246,8 +246,13 @@ impl LspAdapter for EsLintLspAdapter {
|
||||
// At the time of writing the latest vscode-eslint release was released in 2020 and requires
|
||||
// special custom LSP protocol extensions be handled to fully initialize. Download the latest
|
||||
// prerelease instead to sidestep this issue
|
||||
let release =
|
||||
latest_github_release("microsoft/vscode-eslint", true, delegate.http_client()).await?;
|
||||
let release = latest_github_release(
|
||||
"microsoft/vscode-eslint",
|
||||
false,
|
||||
false,
|
||||
delegate.http_client(),
|
||||
)
|
||||
.await?;
|
||||
Ok(Box::new(GitHubLspBinaryVersion {
|
||||
name: release.name,
|
||||
url: release.tarball_url,
|
||||
|
||||
@@ -28,8 +28,9 @@ impl LspAdapter for ZlsAdapter {
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release = latest_github_release("zigtools/zls", false, delegate.http_client()).await?;
|
||||
let asset_name = format!("zls-{}-macos.tar.gz", ARCH);
|
||||
let release =
|
||||
latest_github_release("zigtools/zls", true, false, delegate.http_client()).await?;
|
||||
let asset_name = format!("zls-{ARCH}-macos.tar.gz");
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
|
||||
@@ -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