Compare commits

..

2 Commits

Author SHA1 Message Date
Smit Barmase
131ed8add1 move include languages in workspace configuration 2025-12-09 10:39:30 +05:30
mafiefa02
1f296334e5 fix(language): Init tw options with includeLanguages instead of
`userLanguages`
2025-12-02 16:44:01 +07:00
41 changed files with 767 additions and 1212 deletions

View File

@@ -84,7 +84,7 @@ jobs:
run: ./script/check-keymaps
shell: bash -euxo pipefail {0}
- name: run_tests::check_style::check_for_typos
uses: crate-ci/typos@2d0ce569feab1f8752f1dde43cc2f2aa53236e06
uses: crate-ci/typos@80c8a4945eec0f6d464eaf9e65ed98ef085283d1
with:
config: ./typos.toml
- name: steps::cargo_fmt

View File

@@ -49,8 +49,7 @@
"ctrl-cmd-f": "zed::ToggleFullScreen",
"ctrl-cmd-z": "edit_prediction::RateCompletions",
"ctrl-cmd-i": "edit_prediction::ToggleMenu",
"ctrl-cmd-l": "lsp_tool::ToggleMenu",
"ctrl-cmd-c": "editor::DisplayCursorNames"
"ctrl-cmd-l": "lsp_tool::ToggleMenu"
}
},
{
@@ -590,7 +589,8 @@
"cmd-.": "editor::ToggleCodeActions",
"cmd-k r": "editor::RevealInFileManager",
"cmd-k p": "editor::CopyPath",
"cmd-\\": "pane::SplitRight"
"cmd-\\": "pane::SplitRight",
"ctrl-cmd-c": "editor::DisplayCursorNames"
}
},
{
@@ -730,8 +730,7 @@
"context": "Workspace && debugger_running",
"use_key_equivalents": true,
"bindings": {
"f5": "zed::NoAction",
"f11": "debugger::StepInto"
"f5": "zed::NoAction"
}
},
{

View File

@@ -36,12 +36,12 @@
"shift-f5": "debugger::Stop",
"ctrl-shift-f5": "debugger::RerunSession",
"f6": "debugger::Pause",
"f10": "debugger::StepOver",
"f7": "debugger::StepOver",
"ctrl-f11": "debugger::StepInto",
"shift-f11": "debugger::StepOut",
"f11": "zed::ToggleFullScreen",
"ctrl-shift-i": "edit_prediction::ToggleMenu",
"shift-alt-l": "lsp_tool::ToggleMenu",
"ctrl-shift-alt-c": "editor::DisplayCursorNames"
"shift-alt-l": "lsp_tool::ToggleMenu"
}
},
{
@@ -117,7 +117,7 @@
"alt-g m": "git::OpenModifiedFiles",
"menu": "editor::OpenContextMenu",
"shift-f10": "editor::OpenContextMenu",
"ctrl-alt-e": "editor::ToggleEditPrediction",
"ctrl-shift-e": "editor::ToggleEditPrediction",
"f9": "editor::ToggleBreakpoint",
"shift-f9": "editor::EditLogBreakpoint"
}
@@ -215,7 +215,7 @@
"context": "ContextEditor > Editor",
"use_key_equivalents": true,
"bindings": {
"ctrl-i": "assistant::Assist",
"ctrl-enter": "assistant::Assist",
"ctrl-s": "workspace::Save",
"ctrl-shift-,": "assistant::InsertIntoEditor",
"shift-enter": "assistant::Split",
@@ -500,7 +500,10 @@
"ctrl-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection
"ctrl-f2": "editor::SelectAllMatches", // Select all occurrences of current word
"ctrl-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand
"ctrl-shift-down": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch
"ctrl-shift-up": ["editor::SelectPrevious", { "replace_newest": false }], // editor.action.addSelectionToPreviousFindMatch
"ctrl-k ctrl-d": ["editor::SelectNext", { "replace_newest": true }], // editor.action.moveSelectionToNextFindMatch / find_under_expand_skip
"ctrl-k ctrl-shift-d": ["editor::SelectPrevious", { "replace_newest": true }], // editor.action.moveSelectionToPreviousFindMatch
"ctrl-k ctrl-i": "editor::Hover",
"ctrl-k ctrl-b": "editor::BlameHover",
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": false }],
@@ -509,8 +512,12 @@
"f2": "editor::Rename",
"f12": "editor::GoToDefinition",
"alt-f12": "editor::GoToDefinitionSplit",
"ctrl-shift-f10": "editor::GoToDefinitionSplit",
"ctrl-f12": "editor::GoToImplementation",
"shift-f12": "editor::GoToTypeDefinition",
"ctrl-alt-f12": "editor::GoToTypeDefinitionSplit",
"shift-alt-f12": "editor::FindAllReferences",
"ctrl-m": "editor::MoveToEnclosingBracket", // from jetbrains
"ctrl-shift-\\": "editor::MoveToEnclosingBracket",
"ctrl-shift-[": "editor::Fold",
"ctrl-shift-]": "editor::UnfoldLines",
@@ -534,6 +541,7 @@
"ctrl-k r": "editor::RevealInFileManager",
"ctrl-k p": "editor::CopyPath",
"ctrl-\\": "pane::SplitRight",
"ctrl-shift-alt-c": "editor::DisplayCursorNames",
"alt-.": "editor::GoToHunk",
"alt-,": "editor::GoToPreviousHunk"
}
@@ -1116,7 +1124,7 @@
"shift-insert": "terminal::Paste",
"ctrl-v": "terminal::Paste",
"ctrl-shift-v": "terminal::Paste",
"ctrl-i": "assistant::InlineAssist",
"ctrl-enter": "assistant::InlineAssist",
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
"alt-.": ["terminal::SendText", "\u001b."],

View File

@@ -96,11 +96,11 @@
"terminal.ansi.bright_white": "#fafafaff",
"terminal.ansi.dim_white": "#575d65ff",
"link_text.hover": "#74ade8ff",
"version_control.added": "#2EA04833",
"version_control.added": "#27a657ff",
"version_control.modified": "#d3b020ff",
"version_control.word_added": "#2EA0483D",
"version_control.word_deleted": "#78081BB3",
"version_control.deleted": "#78081B66",
"version_control.word_added": "#2EA04859",
"version_control.word_deleted": "#78081BCC",
"version_control.deleted": "#e06c76ff",
"version_control.conflict_marker.ours": "#a1c1811a",
"version_control.conflict_marker.theirs": "#74ade81a",
"conflict": "#dec184ff",
@@ -499,11 +499,11 @@
"terminal.ansi.bright_white": "#ffffffff",
"terminal.ansi.dim_white": "#aaaaaaff",
"link_text.hover": "#5c78e2ff",
"version_control.added": "#2EA04833",
"version_control.added": "#27a657ff",
"version_control.modified": "#d3b020ff",
"version_control.word_added": "#2EA0483D",
"version_control.word_deleted": "#F851493D",
"version_control.deleted": "#F8514929",
"version_control.word_added": "#2EA04859",
"version_control.word_deleted": "#F85149CC",
"version_control.deleted": "#e06c76ff",
"conflict": "#a48819ff",
"conflict.background": "#faf2e6ff",
"conflict.border": "#f4e7d1ff",

View File

@@ -524,16 +524,6 @@ impl Room {
self.id
}
pub fn room_id(&self) -> impl Future<Output = Option<String>> + 'static {
let room = self.live_kit.as_ref().map(|lk| lk.room.clone());
async move {
let room = room?;
let sid = room.sid().await;
let name = room.name();
Some(format!("{} (sid: {sid})", name))
}
}
pub fn status(&self) -> RoomStatus {
self.status
}

View File

@@ -206,16 +206,11 @@ pub struct AcceptEditPredictionBody {
pub request_id: String,
}
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RejectEditPredictionsBody {
pub rejections: Vec<EditPredictionRejection>,
}
#[derive(Debug, Clone, Serialize)]
pub struct RejectEditPredictionsBodyRef<'a> {
pub rejections: &'a [EditPredictionRejection],
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EditPredictionRejection {
pub request_id: String,

View File

@@ -37,7 +37,7 @@ use ui::{
};
use util::{ResultExt, TryFutureExt, maybe};
use workspace::{
CopyRoomId, Deafen, LeaveCall, Mute, OpenChannelNotes, ScreenShare, ShareProject, Workspace,
Deafen, LeaveCall, Mute, OpenChannelNotes, ScreenShare, ShareProject, Workspace,
dock::{DockPosition, Panel, PanelEvent},
notifications::{DetachAndPromptErr, NotifyResultExt},
};
@@ -128,32 +128,6 @@ pub fn init(cx: &mut App) {
workspace.register_action(|_, _: &LeaveCall, window, cx| {
CollabPanel::leave_call(window, cx);
});
workspace.register_action(|workspace, _: &CopyRoomId, window, cx| {
use workspace::notifications::{NotificationId, NotifyTaskExt as _};
struct RoomIdCopiedToast;
if let Some(room) = ActiveCall::global(cx).read(cx).room() {
let romo_id_fut = room.read(cx).room_id();
cx.spawn(async move |workspace, cx| {
let room_id = romo_id_fut.await.context("Failed to get livekit room")?;
workspace.update(cx, |workspace, cx| {
cx.write_to_clipboard(ClipboardItem::new_string(room_id));
workspace.show_toast(
workspace::Toast::new(
NotificationId::unique::<RoomIdCopiedToast>(),
"Room ID copied to clipboard",
)
.autohide(),
cx,
);
})
})
.detach_and_notify_err(window, cx);
} else {
workspace.show_error(&"Theres no active call; join one first.", cx);
}
});
workspace.register_action(|workspace, _: &ShareProject, window, cx| {
let project = workspace.project().clone();
println!("{project:?}");

View File

@@ -18,14 +18,14 @@ use gpui::{
use language::{Anchor, Buffer, CharScopeContext, CodeLabel, TextBufferSnapshot, ToOffset};
use menu::{Confirm, SelectNext, SelectPrevious};
use project::{
CompletionDisplayOptions, CompletionResponse,
Completion, CompletionDisplayOptions, CompletionResponse,
debugger::session::{CompletionsQuery, OutputToken, Session},
lsp_store::CompletionDocumentation,
search_history::{SearchHistory, SearchHistoryCursor},
};
use settings::Settings;
use std::fmt::Write;
use std::{ops::Range, rc::Rc, usize};
use std::{cell::RefCell, ops::Range, rc::Rc, usize};
use theme::{Theme, ThemeSettings};
use ui::{ContextMenu, Divider, PopoverMenu, SplitButton, Tooltip, prelude::*};
use util::ResultExt;
@@ -553,6 +553,17 @@ impl CompletionProvider for ConsoleQueryBarCompletionProvider {
}
}
fn apply_additional_edits_for_completion(
&self,
_buffer: Entity<Buffer>,
_completions: Rc<RefCell<Box<[Completion]>>>,
_completion_index: usize,
_push_to_history: bool,
_cx: &mut Context<Editor>,
) -> gpui::Task<anyhow::Result<Option<language::Transaction>>> {
Task::ready(Ok(None))
}
fn is_completion_trigger(
&self,
buffer: &Entity<Buffer>,

View File

@@ -146,8 +146,8 @@ use persistence::DB;
use project::{
BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
ProjectPath, ProjectTransaction, TaskSourceKind,
debugger::{
breakpoint_store::{
Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
@@ -6151,43 +6151,9 @@ impl Editor {
}
let provider = self.completion_provider.as_ref()?;
let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
let command = lsp_store.as_ref().and_then(|lsp_store| {
let CompletionSource::Lsp {
lsp_completion,
server_id,
..
} = &completion.source
else {
return None;
};
let lsp_command = lsp_completion.command.as_ref()?;
let available_commands = lsp_store
.read(cx)
.lsp_server_capabilities
.get(server_id)
.and_then(|server_capabilities| {
server_capabilities
.execute_command_provider
.as_ref()
.map(|options| options.commands.as_slice())
})?;
if available_commands.contains(&lsp_command.command) {
Some(CodeAction {
server_id: *server_id,
range: language::Anchor::MIN..language::Anchor::MIN,
lsp_action: LspAction::Command(lsp_command.clone()),
resolved: false,
})
} else {
None
}
});
drop(completion);
let apply_edits = provider.apply_additional_edits_for_completion(
buffer_handle.clone(),
buffer_handle,
completions_menu.completions.clone(),
candidate_id,
true,
@@ -6201,29 +6167,8 @@ impl Editor {
self.show_signature_help(&ShowSignatureHelp, window, cx);
}
Some(cx.spawn_in(window, async move |editor, cx| {
Some(cx.foreground_executor().spawn(async move {
apply_edits.await?;
if let Some((lsp_store, command)) = lsp_store.zip(command) {
let title = command.lsp_action.title().to_owned();
let project_transaction = lsp_store
.update(cx, |lsp_store, cx| {
lsp_store.apply_code_action(buffer_handle, command, false, cx)
})?
.await
.context("applying post-completion command")?;
if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
Self::open_project_transaction(
&editor,
workspace.downgrade(),
project_transaction,
title,
cx,
)
.await?;
}
}
Ok(())
}))
}

View File

@@ -14755,180 +14755,6 @@ async fn test_completion(cx: &mut TestAppContext) {
apply_additional_edits.await.unwrap();
}
#[gpui::test]
async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/a"),
json!({
"main.rs": "",
}),
)
.await;
let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
let command_calls = Arc::new(AtomicUsize::new(0));
let registered_command = "_the/command";
let closure_command_calls = command_calls.clone();
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
..lsp::CompletionOptions::default()
}),
execute_command_provider: Some(lsp::ExecuteCommandOptions {
commands: vec![registered_command.to_owned()],
..lsp::ExecuteCommandOptions::default()
}),
..lsp::ServerCapabilities::default()
},
initializer: Some(Box::new(move |fake_server| {
fake_server.set_request_handler::<lsp::request::Completion, _, _>(
move |params, _| async move {
Ok(Some(lsp::CompletionResponse::Array(vec![
lsp::CompletionItem {
label: "registered_command".to_owned(),
text_edit: gen_text_edit(&params, ""),
command: Some(lsp::Command {
title: registered_command.to_owned(),
command: "_the/command".to_owned(),
arguments: Some(vec![serde_json::Value::Bool(true)]),
}),
..lsp::CompletionItem::default()
},
lsp::CompletionItem {
label: "unregistered_command".to_owned(),
text_edit: gen_text_edit(&params, ""),
command: Some(lsp::Command {
title: "????????????".to_owned(),
command: "????????????".to_owned(),
arguments: Some(vec![serde_json::Value::Null]),
}),
..lsp::CompletionItem::default()
},
])))
},
);
fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
let command_calls = closure_command_calls.clone();
move |params, _| {
assert_eq!(params.command, registered_command);
let command_calls = command_calls.clone();
async move {
command_calls.fetch_add(1, atomic::Ordering::Release);
Ok(Some(json!(null)))
}
}
});
})),
..FakeLspAdapter::default()
},
);
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let editor = workspace
.update(cx, |workspace, window, cx| {
workspace.open_abs_path(
PathBuf::from(path!("/a/main.rs")),
OpenOptions::default(),
window,
cx,
)
})
.unwrap()
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
let _fake_server = fake_servers.next().await.unwrap();
editor.update_in(cx, |editor, window, cx| {
cx.focus_self(window);
editor.move_to_end(&MoveToEnd, window, cx);
editor.handle_input(".", window, cx);
});
cx.run_until_parked();
editor.update(cx, |editor, _| {
assert!(editor.context_menu_visible());
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
{
let completion_labels = menu
.completions
.borrow()
.iter()
.map(|c| c.label.text.clone())
.collect::<Vec<_>>();
assert_eq!(
completion_labels,
&["registered_command", "unregistered_command",],
);
} else {
panic!("expected completion menu to be open");
}
});
editor
.update_in(cx, |editor, window, cx| {
editor
.confirm_completion(&ConfirmCompletion::default(), window, cx)
.unwrap()
})
.await
.unwrap();
cx.run_until_parked();
assert_eq!(
command_calls.load(atomic::Ordering::Acquire),
1,
"For completion with a registered command, Zed should send a command execution request",
);
editor.update_in(cx, |editor, window, cx| {
cx.focus_self(window);
editor.handle_input(".", window, cx);
});
cx.run_until_parked();
editor.update(cx, |editor, _| {
assert!(editor.context_menu_visible());
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
{
let completion_labels = menu
.completions
.borrow()
.iter()
.map(|c| c.label.text.clone())
.collect::<Vec<_>>();
assert_eq!(
completion_labels,
&["registered_command", "unregistered_command",],
);
} else {
panic!("expected completion menu to be open");
}
});
editor
.update_in(cx, |editor, window, cx| {
editor.context_menu_next(&Default::default(), window, cx);
editor
.confirm_completion(&ConfirmCompletion::default(), window, cx)
.unwrap()
})
.await
.unwrap();
cx.run_until_parked();
assert_eq!(
command_calls.load(atomic::Ordering::Acquire),
1,
"For completion with an unregistered command, Zed should not send a command execution request",
);
}
#[gpui::test]
async fn test_completion_reuse(cx: &mut TestAppContext) {
init_test(cx, |_| {});

View File

@@ -9213,34 +9213,18 @@ impl Element for EditorElement {
continue;
};
let background_color = dbg!(match diff_status.kind {
DiffHunkStatusKind::Added =>
cx.theme().colors().version_control_added,
DiffHunkStatusKind::Deleted =>
cx.theme().colors().version_control_deleted,
DiffHunkStatusKind::Modified => {
debug_panic!("modified diff status for row info");
continue;
}
});
let background_color = match diff_status.kind {
DiffHunkStatusKind::Added =>
cx.theme().colors().version_control_added,
DiffHunkStatusKind::Deleted =>
cx.theme().colors().version_control_deleted,
DiffHunkStatusKind::Modified => {
debug_panic!("modified diff status for row info");
continue;
}
};
// Potential fix with new tokens:
// diff.added = "#2EA048"
// diff.added_word = @diff.added/24%
// diff.added_background = @diff.added/20%
//
//
// Ideal State:
// version_control.added = "#2EA048"
// version_control.added_word = @version_control.added/24%
// version_control.added_background = @version_control.added/20%
// version_control.added_icon = "something"
// let hunk_opacity = if background_color.a < 1.0 {
// background_color.a // Use existing alpha
// } else {
// if is_light { 0.16 } else { 0.20 }
// };
let hunk_opacity = if is_light { 0.16 } else { 0.12 };
let hollow_highlight = LineHighlight {
background: (background_color.opacity(if is_light {
@@ -9259,7 +9243,7 @@ impl Element for EditorElement {
};
let filled_highlight = LineHighlight {
background: solid_background(background_color),
background: solid_background(background_color.opacity(hunk_opacity)),
border: None,
include_gutter: true,
type_id: None,

View File

@@ -232,14 +232,12 @@ impl From<Oid> for usize {
#[derive(Copy, Clone, Debug)]
pub enum RunHook {
PreCommit,
PrePush,
}
impl RunHook {
pub fn as_str(&self) -> &str {
match self {
Self::PreCommit => "pre-commit",
Self::PrePush => "pre-push",
}
}
@@ -250,7 +248,6 @@ impl RunHook {
pub fn from_proto(value: i32) -> Option<Self> {
match value {
0 => Some(Self::PreCommit),
1 => Some(Self::PrePush),
_ => None,
}
}

View File

@@ -748,7 +748,7 @@ impl Size<Length> {
/// assert_eq!(bounds.origin, origin);
/// assert_eq!(bounds.size, size);
/// ```
#[derive(Refineable, Copy, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
#[refineable(Debug)]
#[repr(C)]
pub struct Bounds<T: Clone + Debug + Default + PartialEq> {
@@ -1676,6 +1676,8 @@ impl Bounds<DevicePixels> {
}
}
impl<T: Copy + Clone + Debug + Default + PartialEq> Copy for Bounds<T> {}
/// Represents the edges of a box in a 2D space, such as padding or margin.
///
/// Each field represents the size of the edge on one side of the box: `top`, `right`, `bottom`, and `left`.

View File

@@ -51,7 +51,7 @@ impl WindowsWindowInner {
WM_NCCALCSIZE => self.handle_calc_client_size(handle, wparam, lparam),
WM_DPICHANGED => self.handle_dpi_changed_msg(handle, wparam, lparam),
WM_DISPLAYCHANGE => self.handle_display_change_msg(handle),
WM_NCHITTEST => self.handle_hit_test_msg(handle, lparam),
WM_NCHITTEST => self.handle_hit_test_msg(handle, msg, wparam, lparam),
WM_PAINT => self.handle_paint_msg(handle),
WM_CLOSE => self.handle_close_msg(),
WM_DESTROY => self.handle_destroy_msg(handle),
@@ -116,16 +116,17 @@ impl WindowsWindowInner {
}
fn handle_move_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
let mut lock = self.state.borrow_mut();
let origin = logical_point(
lparam.signed_loword() as f32,
lparam.signed_hiword() as f32,
self.state.scale_factor.get(),
lock.scale_factor,
);
self.state.origin.set(origin);
let size = self.state.logical_size.get();
lock.origin = origin;
let size = lock.logical_size;
let center_x = origin.x.0 + size.width.0 / 2.;
let center_y = origin.y.0 + size.height.0 / 2.;
let monitor_bounds = self.state.display.get().bounds();
let monitor_bounds = lock.display.bounds();
if center_x < monitor_bounds.left().0
|| center_x > monitor_bounds.right().0
|| center_y < monitor_bounds.top().0
@@ -135,42 +136,42 @@ impl WindowsWindowInner {
let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
// minimize the window can trigger this event too, in this case,
// monitor is invalid, we do nothing.
if !monitor.is_invalid() && self.state.display.get().handle != monitor {
if !monitor.is_invalid() && lock.display.handle != monitor {
// we will get the same monitor if we only have one
self.state
.display
.set(WindowsDisplay::new_with_handle(monitor).log_err()?);
lock.display = WindowsDisplay::new_with_handle(monitor).log_err()?;
}
}
if let Some(mut callback) = self.state.callbacks.moved.take() {
if let Some(mut callback) = lock.callbacks.moved.take() {
drop(lock);
callback();
self.state.callbacks.moved.set(Some(callback));
self.state.borrow_mut().callbacks.moved = Some(callback);
}
Some(0)
}
fn handle_get_min_max_info_msg(&self, lparam: LPARAM) -> Option<isize> {
let min_size = self.state.min_size?;
let scale_factor = self.state.scale_factor.get();
let boarder_offset = &self.state.border_offset;
let lock = self.state.borrow();
let min_size = lock.min_size?;
let scale_factor = lock.scale_factor;
let boarder_offset = lock.border_offset;
drop(lock);
unsafe {
let minmax_info = &mut *(lparam.0 as *mut MINMAXINFO);
minmax_info.ptMinTrackSize.x =
min_size.width.scale(scale_factor).0 as i32 + boarder_offset.width_offset.get();
min_size.width.scale(scale_factor).0 as i32 + boarder_offset.width_offset;
minmax_info.ptMinTrackSize.y =
min_size.height.scale(scale_factor).0 as i32 + boarder_offset.height_offset.get();
min_size.height.scale(scale_factor).0 as i32 + boarder_offset.height_offset;
}
Some(0)
}
fn handle_size_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
let mut lock = self.state.borrow_mut();
// Don't resize the renderer when the window is minimized, but record that it was minimized so
// that on restore the swap chain can be recreated via `update_drawable_size_even_if_unchanged`.
if wparam.0 == SIZE_MINIMIZED as usize {
self.state
.restore_from_minimized
.set(self.state.callbacks.request_frame.take());
lock.restore_from_minimized = lock.callbacks.request_frame.take();
return Some(0);
}
@@ -178,16 +179,14 @@ impl WindowsWindowInner {
let height = lparam.hiword().max(1) as i32;
let new_size = size(DevicePixels(width), DevicePixels(height));
let scale_factor = self.state.scale_factor.get();
let scale_factor = lock.scale_factor;
let mut should_resize_renderer = false;
if let Some(restore_from_minimized) = self.state.restore_from_minimized.take() {
self.state
.callbacks
.request_frame
.set(Some(restore_from_minimized));
if lock.restore_from_minimized.is_some() {
lock.callbacks.request_frame = lock.restore_from_minimized.take();
} else {
should_resize_renderer = true;
}
drop(lock);
self.handle_size_change(new_size, scale_factor, should_resize_renderer);
Some(0)
@@ -200,19 +199,17 @@ impl WindowsWindowInner {
should_resize_renderer: bool,
) {
let new_logical_size = device_size.to_pixels(scale_factor);
self.state.logical_size.set(new_logical_size);
if should_resize_renderer
&& let Err(e) = self.state.renderer.borrow_mut().resize(device_size)
{
let mut lock = self.state.borrow_mut();
lock.logical_size = new_logical_size;
if should_resize_renderer && let Err(e) = lock.renderer.resize(device_size) {
log::error!("Failed to resize renderer, invalidating devices: {}", e);
self.state
.invalidate_devices
lock.invalidate_devices
.store(true, std::sync::atomic::Ordering::Release);
}
if let Some(mut callback) = self.state.callbacks.resize.take() {
if let Some(mut callback) = lock.callbacks.resize.take() {
drop(lock);
callback(new_logical_size, scale_factor);
self.state.callbacks.resize.set(Some(callback));
self.state.borrow_mut().callbacks.resize = Some(callback);
}
}
@@ -257,14 +254,17 @@ impl WindowsWindowInner {
}
fn handle_close_msg(&self) -> Option<isize> {
let mut callback = self.state.callbacks.should_close.take()?;
let mut callback = self.state.borrow_mut().callbacks.should_close.take()?;
let should_close = callback();
self.state.callbacks.should_close.set(Some(callback));
self.state.borrow_mut().callbacks.should_close = Some(callback);
if should_close { None } else { Some(0) }
}
fn handle_destroy_msg(&self, handle: HWND) -> Option<isize> {
let callback = { self.state.callbacks.close.take() };
let callback = {
let mut lock = self.state.borrow_mut();
lock.callbacks.close.take()
};
if let Some(callback) = callback {
callback();
}
@@ -283,10 +283,12 @@ impl WindowsWindowInner {
fn handle_mouse_move_msg(&self, handle: HWND, lparam: LPARAM, wparam: WPARAM) -> Option<isize> {
self.start_tracking_mouse(handle, TME_LEAVE);
let Some(mut func) = self.state.callbacks.input.take() else {
let mut lock = self.state.borrow_mut();
let Some(mut func) = lock.callbacks.input.take() else {
return Some(1);
};
let scale_factor = self.state.scale_factor.get();
let scale_factor = lock.scale_factor;
drop(lock);
let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
@@ -308,32 +310,32 @@ impl WindowsWindowInner {
modifiers: current_modifiers(),
});
let handled = !func(input).propagate;
self.state.callbacks.input.set(Some(func));
self.state.borrow_mut().callbacks.input = Some(func);
if handled { Some(0) } else { Some(1) }
}
fn handle_mouse_leave_msg(&self) -> Option<isize> {
self.state.hovered.set(false);
if let Some(mut callback) = self.state.callbacks.hovered_status_change.take() {
let mut lock = self.state.borrow_mut();
lock.hovered = false;
if let Some(mut callback) = lock.callbacks.hovered_status_change.take() {
drop(lock);
callback(false);
self.state
.callbacks
.hovered_status_change
.set(Some(callback));
self.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
}
Some(0)
}
fn handle_syskeyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
let input = handle_key_event(wparam, lparam, &self.state, |keystroke, _| {
let mut lock = self.state.borrow_mut();
let input = handle_key_event(wparam, lparam, &mut lock, |keystroke, _| {
PlatformInput::KeyUp(KeyUpEvent { keystroke })
})?;
let mut func = self.state.callbacks.input.take()?;
let mut func = lock.callbacks.input.take()?;
drop(lock);
func(input);
self.state.callbacks.input.set(Some(func));
self.state.borrow_mut().callbacks.input = Some(func);
// Always return 0 to indicate that the message was handled, so we could properly handle `ModifiersChanged` event.
Some(0)
@@ -342,10 +344,11 @@ impl WindowsWindowInner {
// It's a known bug that you can't trigger `ctrl-shift-0`. See:
// https://superuser.com/questions/1455762/ctrl-shift-number-key-combination-has-stopped-working-for-a-few-numbers
fn handle_keydown_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
let mut lock = self.state.borrow_mut();
let Some(input) = handle_key_event(
wparam,
lparam,
&self.state,
&mut lock,
|keystroke, prefer_character_input| {
PlatformInput::KeyDown(KeyDownEvent {
keystroke,
@@ -356,31 +359,34 @@ impl WindowsWindowInner {
) else {
return Some(1);
};
drop(lock);
let Some(mut func) = self.state.callbacks.input.take() else {
let Some(mut func) = self.state.borrow_mut().callbacks.input.take() else {
return Some(1);
};
let handled = !func(input).propagate;
self.state.callbacks.input.set(Some(func));
self.state.borrow_mut().callbacks.input = Some(func);
if handled { Some(0) } else { Some(1) }
}
fn handle_keyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
let Some(input) = handle_key_event(wparam, lparam, &self.state, |keystroke, _| {
let mut lock = self.state.borrow_mut();
let Some(input) = handle_key_event(wparam, lparam, &mut lock, |keystroke, _| {
PlatformInput::KeyUp(KeyUpEvent { keystroke })
}) else {
return Some(1);
};
let Some(mut func) = self.state.callbacks.input.take() else {
let Some(mut func) = lock.callbacks.input.take() else {
return Some(1);
};
drop(lock);
let handled = !func(input).propagate;
self.state.callbacks.input.set(Some(func));
self.state.borrow_mut().callbacks.input = Some(func);
if handled { Some(0) } else { Some(1) }
}
@@ -401,15 +407,16 @@ impl WindowsWindowInner {
lparam: LPARAM,
) -> Option<isize> {
unsafe { SetCapture(handle) };
let Some(mut func) = self.state.callbacks.input.take() else {
let mut lock = self.state.borrow_mut();
let Some(mut func) = lock.callbacks.input.take() else {
return Some(1);
};
let x = lparam.signed_loword();
let y = lparam.signed_hiword();
let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
let click_count = self.state.click_state.update(button, physical_point);
let scale_factor = self.state.scale_factor.get();
let click_count = lock.click_state.update(button, physical_point);
let scale_factor = lock.scale_factor;
drop(lock);
let input = PlatformInput::MouseDown(MouseDownEvent {
button,
@@ -419,7 +426,7 @@ impl WindowsWindowInner {
first_mouse: false,
});
let handled = !func(input).propagate;
self.state.callbacks.input.set(Some(func));
self.state.borrow_mut().callbacks.input = Some(func);
if handled { Some(0) } else { Some(1) }
}
@@ -431,14 +438,15 @@ impl WindowsWindowInner {
lparam: LPARAM,
) -> Option<isize> {
unsafe { ReleaseCapture().log_err() };
let Some(mut func) = self.state.callbacks.input.take() else {
let mut lock = self.state.borrow_mut();
let Some(mut func) = lock.callbacks.input.take() else {
return Some(1);
};
let x = lparam.signed_loword() as f32;
let y = lparam.signed_hiword() as f32;
let click_count = self.state.click_state.current_count.get();
let scale_factor = self.state.scale_factor.get();
let click_count = lock.click_state.current_count;
let scale_factor = lock.scale_factor;
drop(lock);
let input = PlatformInput::MouseUp(MouseUpEvent {
button,
@@ -447,7 +455,7 @@ impl WindowsWindowInner {
click_count,
});
let handled = !func(input).propagate;
self.state.callbacks.input.set(Some(func));
self.state.borrow_mut().callbacks.input = Some(func);
if handled { Some(0) } else { Some(1) }
}
@@ -474,23 +482,24 @@ impl WindowsWindowInner {
lparam: LPARAM,
) -> Option<isize> {
let modifiers = current_modifiers();
let Some(mut func) = self.state.callbacks.input.take() else {
let mut lock = self.state.borrow_mut();
let Some(mut func) = lock.callbacks.input.take() else {
return Some(1);
};
let scale_factor = self.state.scale_factor.get();
let scale_factor = lock.scale_factor;
let wheel_scroll_amount = match modifiers.shift {
true => self
.system_settings()
.mouse_wheel_settings
.wheel_scroll_chars
.get(),
false => self
.system_settings()
.mouse_wheel_settings
.wheel_scroll_lines
.get(),
true => {
self.system_settings()
.mouse_wheel_settings
.wheel_scroll_chars
}
false => {
self.system_settings()
.mouse_wheel_settings
.wheel_scroll_lines
}
};
drop(lock);
let wheel_distance =
(wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_amount as f32;
@@ -515,7 +524,7 @@ impl WindowsWindowInner {
touch_phase: TouchPhase::Moved,
});
let handled = !func(input).propagate;
self.state.callbacks.input.set(Some(func));
self.state.borrow_mut().callbacks.input = Some(func);
if handled { Some(0) } else { Some(1) }
}
@@ -526,15 +535,16 @@ impl WindowsWindowInner {
wparam: WPARAM,
lparam: LPARAM,
) -> Option<isize> {
let Some(mut func) = self.state.callbacks.input.take() else {
let mut lock = self.state.borrow_mut();
let Some(mut func) = lock.callbacks.input.take() else {
return Some(1);
};
let scale_factor = self.state.scale_factor.get();
let scale_factor = lock.scale_factor;
let wheel_scroll_chars = self
.system_settings()
.mouse_wheel_settings
.wheel_scroll_chars
.get();
.wheel_scroll_chars;
drop(lock);
let wheel_distance =
(-wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_chars as f32;
@@ -553,7 +563,7 @@ impl WindowsWindowInner {
touch_phase: TouchPhase::Moved,
});
let handled = !func(event).propagate;
self.state.callbacks.input.set(Some(func));
self.state.borrow_mut().callbacks.input = Some(func);
if handled { Some(0) } else { Some(1) }
}
@@ -647,11 +657,11 @@ impl WindowsWindowInner {
wparam: WPARAM,
lparam: LPARAM,
) -> Option<isize> {
if !self.hide_title_bar || self.state.is_fullscreen() || wparam.0 == 0 {
if !self.hide_title_bar || self.state.borrow().is_fullscreen() || wparam.0 == 0 {
return None;
}
let is_maximized = self.state.is_maximized();
let is_maximized = self.state.borrow().is_maximized();
let insets = get_client_area_insets(handle, is_maximized, self.windows_version);
// wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
@@ -666,7 +676,7 @@ impl WindowsWindowInner {
// used by Chrome. However, it may result in one row of pixels being obscured
// in our client area. But as Chrome says, "there seems to be no better solution."
if is_maximized
&& let Some(taskbar_position) = self.system_settings().auto_hide_taskbar_position.get()
&& let Some(ref taskbar_position) = self.system_settings().auto_hide_taskbar_position
{
// For the auto-hide taskbar, adjust in by 1 pixel on taskbar edge,
// so the window isn't treated as a "fullscreen app", which would cause
@@ -695,9 +705,11 @@ impl WindowsWindowInner {
let this = self.clone();
self.executor
.spawn(async move {
if let Some(mut func) = this.state.callbacks.active_status_change.take() {
let mut lock = this.state.borrow_mut();
if let Some(mut func) = lock.callbacks.active_status_change.take() {
drop(lock);
func(activated);
this.state.callbacks.active_status_change.set(Some(func));
this.state.borrow_mut().callbacks.active_status_change = Some(func);
}
})
.detach();
@@ -721,11 +733,12 @@ impl WindowsWindowInner {
lparam: LPARAM,
) -> Option<isize> {
let new_dpi = wparam.loword() as f32;
let is_maximized = self.state.is_maximized();
let mut lock = self.state.borrow_mut();
let is_maximized = lock.is_maximized();
let new_scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
self.state.scale_factor.set(new_scale_factor);
self.state.border_offset.update(handle).log_err();
lock.scale_factor = new_scale_factor;
lock.border_offset.update(handle).log_err();
drop(lock);
if is_maximized {
// Get the monitor and its work area at the new DPI
@@ -799,7 +812,7 @@ impl WindowsWindowInner {
// Because WM_DPICHANGED, WM_MOVE, WM_SIZE will come first, window reposition and resize
// are handled there.
// So we only care about if monitor is disconnected.
let previous_monitor = self.state.display.get();
let previous_monitor = self.state.borrow().display;
if WindowsDisplay::is_connected(previous_monitor.handle) {
// we are fine, other display changed
return None;
@@ -817,78 +830,86 @@ impl WindowsWindowInner {
return None;
}
let new_display = WindowsDisplay::new_with_handle(new_monitor).log_err()?;
self.state.display.set(new_display);
self.state.borrow_mut().display = new_display;
Some(0)
}
fn handle_hit_test_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
if !self.is_movable || self.state.is_fullscreen() {
fn handle_hit_test_msg(
&self,
handle: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> Option<isize> {
if !self.is_movable || self.state.borrow().is_fullscreen() {
return None;
}
let callback = self.state.callbacks.hit_test_window_control.take();
let drag_area = if let Some(mut callback) = callback {
let mut lock = self.state.borrow_mut();
if let Some(mut callback) = lock.callbacks.hit_test_window_control.take() {
drop(lock);
let area = callback();
self.state
.callbacks
.hit_test_window_control
.set(Some(callback));
self.state.borrow_mut().callbacks.hit_test_window_control = Some(callback);
if let Some(area) = area {
match area {
return match area {
WindowControlArea::Drag => Some(HTCAPTION as _),
WindowControlArea::Close => return Some(HTCLOSE as _),
WindowControlArea::Max => return Some(HTMAXBUTTON as _),
WindowControlArea::Min => return Some(HTMINBUTTON as _),
}
} else {
None
WindowControlArea::Close => Some(HTCLOSE as _),
WindowControlArea::Max => Some(HTMAXBUTTON as _),
WindowControlArea::Min => Some(HTMINBUTTON as _),
};
}
} else {
None
};
drop(lock);
}
if !self.hide_title_bar {
// If the OS draws the title bar, we don't need to handle hit test messages.
return drag_area;
return None;
}
// default handler for resize areas
let hit = unsafe { DefWindowProcW(handle, msg, wparam, lparam) };
if matches!(
hit.0 as u32,
HTNOWHERE
| HTRIGHT
| HTLEFT
| HTTOPLEFT
| HTTOP
| HTTOPRIGHT
| HTBOTTOMRIGHT
| HTBOTTOM
| HTBOTTOMLEFT
) {
return Some(hit.0);
}
if self.state.borrow().is_fullscreen() {
return Some(HTCLIENT as _);
}
let dpi = unsafe { GetDpiForWindow(handle) };
// We do not use the OS title bar, so the default `DefWindowProcW` will only register a 1px edge for resizes
// We need to calculate the frame thickness ourselves and do the hit test manually.
let frame_y = get_frame_thicknessx(dpi);
let frame_x = get_frame_thicknessy(dpi);
let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
let mut cursor_point = POINT {
x: lparam.signed_loword().into(),
y: lparam.signed_hiword().into(),
};
unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
if !self.state.is_maximized() && 0 <= cursor_point.y && cursor_point.y <= frame_y {
// x-axis actually goes from -frame_x to 0
return Some(if cursor_point.x <= 0 {
HTTOPLEFT
} else {
let mut rect = Default::default();
unsafe { GetWindowRect(handle, &mut rect) }.log_err();
// right and bottom bounds of RECT are exclusive, thus `-1`
let right = rect.right - rect.left - 1;
// the bounds include the padding frames, so accomodate for both of them
if right - 2 * frame_x <= cursor_point.x {
HTTOPRIGHT
} else {
HTTOP
}
} as _);
if !self.state.borrow().is_maximized() && cursor_point.y >= 0 && cursor_point.y <= frame_y {
return Some(HTTOP as _);
}
drag_area
Some(HTCLIENT as _)
}
fn handle_nc_mouse_move_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
self.start_tracking_mouse(handle, TME_LEAVE | TME_NONCLIENT);
let mut func = self.state.callbacks.input.take()?;
let scale_factor = self.state.scale_factor.get();
let mut lock = self.state.borrow_mut();
let mut func = lock.callbacks.input.take()?;
let scale_factor = lock.scale_factor;
drop(lock);
let mut cursor_point = POINT {
x: lparam.signed_loword().into(),
@@ -901,7 +922,7 @@ impl WindowsWindowInner {
modifiers: current_modifiers(),
});
let handled = !func(input).propagate;
self.state.callbacks.input.set(Some(func));
self.state.borrow_mut().callbacks.input = Some(func);
if handled { Some(0) } else { None }
}
@@ -913,15 +934,17 @@ impl WindowsWindowInner {
wparam: WPARAM,
lparam: LPARAM,
) -> Option<isize> {
if let Some(mut func) = self.state.callbacks.input.take() {
let scale_factor = self.state.scale_factor.get();
let mut lock = self.state.borrow_mut();
if let Some(mut func) = lock.callbacks.input.take() {
let scale_factor = lock.scale_factor;
let mut cursor_point = POINT {
x: lparam.signed_loword().into(),
y: lparam.signed_hiword().into(),
};
unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
let click_count = self.state.click_state.update(button, physical_point);
let click_count = lock.click_state.update(button, physical_point);
drop(lock);
let input = PlatformInput::MouseDown(MouseDownEvent {
button,
@@ -932,20 +955,21 @@ impl WindowsWindowInner {
});
let result = func(input);
let handled = !result.propagate || result.default_prevented;
self.state.callbacks.input.set(Some(func));
self.state.borrow_mut().callbacks.input = Some(func);
if handled {
return Some(0);
}
} else {
drop(lock);
};
// Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
if button == MouseButton::Left {
match wparam.0 as u32 {
HTMINBUTTON => self.state.nc_button_pressed.set(Some(HTMINBUTTON)),
HTMAXBUTTON => self.state.nc_button_pressed.set(Some(HTMAXBUTTON)),
HTCLOSE => self.state.nc_button_pressed.set(Some(HTCLOSE)),
HTMINBUTTON => self.state.borrow_mut().nc_button_pressed = Some(HTMINBUTTON),
HTMAXBUTTON => self.state.borrow_mut().nc_button_pressed = Some(HTMAXBUTTON),
HTCLOSE => self.state.borrow_mut().nc_button_pressed = Some(HTCLOSE),
_ => return None,
};
Some(0)
@@ -961,8 +985,10 @@ impl WindowsWindowInner {
wparam: WPARAM,
lparam: LPARAM,
) -> Option<isize> {
if let Some(mut func) = self.state.callbacks.input.take() {
let scale_factor = self.state.scale_factor.get();
let mut lock = self.state.borrow_mut();
if let Some(mut func) = lock.callbacks.input.take() {
let scale_factor = lock.scale_factor;
drop(lock);
let mut cursor_point = POINT {
x: lparam.signed_loword().into(),
@@ -976,15 +1002,16 @@ impl WindowsWindowInner {
click_count: 1,
});
let handled = !func(input).propagate;
self.state.callbacks.input.set(Some(func));
self.state.borrow_mut().callbacks.input = Some(func);
if handled {
return Some(0);
}
} else {
drop(lock);
}
let last_pressed = self.state.nc_button_pressed.take();
let last_pressed = self.state.borrow_mut().nc_button_pressed.take();
if button == MouseButton::Left
&& let Some(last_pressed) = last_pressed
{
@@ -994,7 +1021,7 @@ impl WindowsWindowInner {
true
}
(HTMAXBUTTON, HTMAXBUTTON) => {
if self.state.is_maximized() {
if self.state.borrow().is_maximized() {
unsafe { ShowWindowAsync(handle, SW_NORMAL).ok().log_err() };
} else {
unsafe { ShowWindowAsync(handle, SW_MAXIMIZE).ok().log_err() };
@@ -1019,16 +1046,17 @@ impl WindowsWindowInner {
}
fn handle_cursor_changed(&self, lparam: LPARAM) -> Option<isize> {
let had_cursor = self.state.current_cursor.get().is_some();
let mut state = self.state.borrow_mut();
let had_cursor = state.current_cursor.is_some();
self.state.current_cursor.set(if lparam.0 == 0 {
state.current_cursor = if lparam.0 == 0 {
None
} else {
Some(HCURSOR(lparam.0 as _))
});
};
if had_cursor != self.state.current_cursor.get().is_some() {
unsafe { SetCursor(self.state.current_cursor.get()) };
if had_cursor != state.current_cursor.is_some() {
unsafe { SetCursor(state.current_cursor) };
}
Some(0)
@@ -1051,9 +1079,9 @@ impl WindowsWindowInner {
return None;
}
unsafe {
SetCursor(self.state.current_cursor.get());
SetCursor(self.state.borrow().current_cursor);
};
Some(0)
Some(1)
}
fn handle_system_settings_changed(
@@ -1063,12 +1091,13 @@ impl WindowsWindowInner {
lparam: LPARAM,
) -> Option<isize> {
if wparam.0 != 0 {
let display = self.state.display.get();
self.state.click_state.system_update(wparam.0);
self.state.border_offset.update(handle).log_err();
// system settings may emit a window message which wants to take the refcell self.state, so drop it
self.system_settings().update(display, wparam.0);
let mut lock = self.state.borrow_mut();
let display = lock.display;
lock.click_state.system_update(wparam.0);
lock.border_offset.update(handle).log_err();
// system settings may emit a window message which wants to take the refcell lock, so drop it
drop(lock);
self.system_settings_mut().update(display, wparam.0);
} else {
self.handle_system_theme_changed(handle, lparam)?;
};
@@ -1091,13 +1120,13 @@ impl WindowsWindowInner {
let new_appearance = system_appearance()
.context("unable to get system appearance when handling ImmersiveColorSet")
.log_err()?;
if new_appearance != self.state.appearance.get() {
self.state.appearance.set(new_appearance);
let mut callback = self.state.callbacks.appearance_changed.take()?;
let mut lock = self.state.borrow_mut();
if new_appearance != lock.appearance {
lock.appearance = new_appearance;
let mut callback = lock.callbacks.appearance_changed.take()?;
drop(lock);
callback();
self.state.callbacks.appearance_changed.set(Some(callback));
self.state.borrow_mut().callbacks.appearance_changed = Some(callback);
configure_dwm_dark_mode(handle, new_appearance);
}
}
@@ -1126,14 +1155,10 @@ impl WindowsWindowInner {
}
fn handle_device_lost(&self, lparam: LPARAM) -> Option<isize> {
let mut lock = self.state.borrow_mut();
let devices = lparam.0 as *const DirectXDevices;
let devices = unsafe { &*devices };
if let Err(err) = self
.state
.renderer
.borrow_mut()
.handle_device_lost(&devices)
{
if let Err(err) = lock.renderer.handle_device_lost(&devices) {
panic!("Device lost: {err}");
}
Some(0)
@@ -1141,18 +1166,18 @@ impl WindowsWindowInner {
#[inline]
fn draw_window(&self, handle: HWND, force_render: bool) -> Option<isize> {
let mut request_frame = self.state.callbacks.request_frame.take()?;
let mut request_frame = self.state.borrow_mut().callbacks.request_frame.take()?;
// we are instructing gpui to force render a frame, this will
// re-populate all the gpu textures for us so we can resume drawing in
// case we disabled drawing earlier due to a device loss
self.state.renderer.borrow_mut().mark_drawable();
self.state.borrow_mut().renderer.mark_drawable();
request_frame(RequestFrameOptions {
require_presentation: false,
force_render,
});
self.state.callbacks.request_frame.set(Some(request_frame));
self.state.borrow_mut().callbacks.request_frame = Some(request_frame);
unsafe { ValidateRect(Some(handle), None).ok().log_err() };
Some(0)
@@ -1161,16 +1186,16 @@ impl WindowsWindowInner {
#[inline]
fn parse_char_message(&self, wparam: WPARAM) -> Option<String> {
let code_point = wparam.loword();
let mut lock = self.state.borrow_mut();
// https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-3/#G2630
match code_point {
0xD800..=0xDBFF => {
// High surrogate, wait for low surrogate
self.state.pending_surrogate.set(Some(code_point));
lock.pending_surrogate = Some(code_point);
None
}
0xDC00..=0xDFFF => {
if let Some(high_surrogate) = self.state.pending_surrogate.take() {
if let Some(high_surrogate) = lock.pending_surrogate.take() {
// Low surrogate, combine with pending high surrogate
String::from_utf16(&[high_surrogate, code_point]).ok()
} else {
@@ -1182,7 +1207,7 @@ impl WindowsWindowInner {
}
}
_ => {
self.state.pending_surrogate.set(None);
lock.pending_surrogate = None;
char::from_u32(code_point as u32)
.filter(|c| !c.is_control())
.map(|c| c.to_string())
@@ -1191,8 +1216,9 @@ impl WindowsWindowInner {
}
fn start_tracking_mouse(&self, handle: HWND, flags: TRACKMOUSEEVENT_FLAGS) {
if !self.state.hovered.get() {
self.state.hovered.set(true);
let mut lock = self.state.borrow_mut();
if !lock.hovered {
lock.hovered = true;
unsafe {
TrackMouseEvent(&mut TRACKMOUSEEVENT {
cbSize: std::mem::size_of::<TRACKMOUSEEVENT>() as u32,
@@ -1202,12 +1228,10 @@ impl WindowsWindowInner {
})
.log_err()
};
if let Some(mut callback) = self.state.callbacks.hovered_status_change.take() {
if let Some(mut callback) = lock.callbacks.hovered_status_change.take() {
drop(lock);
callback(true);
self.state
.callbacks
.hovered_status_change
.set(Some(callback));
self.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
}
}
}
@@ -1216,9 +1240,9 @@ impl WindowsWindowInner {
where
F: FnOnce(&mut PlatformInputHandler) -> R,
{
let mut input_handler = self.state.input_handler.take()?;
let mut input_handler = self.state.borrow_mut().input_handler.take()?;
let result = f(&mut input_handler);
self.state.input_handler.set(Some(input_handler));
self.state.borrow_mut().input_handler = Some(input_handler);
Some(result)
}
@@ -1226,11 +1250,12 @@ impl WindowsWindowInner {
where
F: FnOnce(&mut PlatformInputHandler, f32) -> Option<R>,
{
let mut input_handler = self.state.input_handler.take()?;
let scale_factor = self.state.scale_factor.get();
let mut lock = self.state.borrow_mut();
let mut input_handler = lock.input_handler.take()?;
let scale_factor = lock.scale_factor;
drop(lock);
let result = f(&mut input_handler, scale_factor);
self.state.input_handler.set(Some(input_handler));
self.state.borrow_mut().input_handler = Some(input_handler);
result
}
}
@@ -1238,7 +1263,7 @@ impl WindowsWindowInner {
fn handle_key_event<F>(
wparam: WPARAM,
lparam: LPARAM,
state: &WindowsWindowState,
state: &mut WindowsWindowState,
f: F,
) -> Option<PlatformInput>
where
@@ -1251,12 +1276,11 @@ where
VK_SHIFT | VK_CONTROL | VK_MENU | VK_LMENU | VK_RMENU | VK_LWIN | VK_RWIN => {
if state
.last_reported_modifiers
.get()
.is_some_and(|prev_modifiers| prev_modifiers == modifiers)
{
return None;
}
state.last_reported_modifiers.set(Some(modifiers));
state.last_reported_modifiers = Some(modifiers);
Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
modifiers,
capslock: current_capslock(),
@@ -1267,12 +1291,11 @@ where
let capslock = current_capslock();
if state
.last_reported_capslock
.get()
.is_some_and(|prev_capslock| prev_capslock == capslock)
{
return None;
}
state.last_reported_capslock.set(Some(capslock));
state.last_reported_capslock = Some(capslock);
Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
modifiers,
capslock,
@@ -1504,7 +1527,7 @@ fn get_client_area_insets(
// The top inset is calculated using an empirical formula that I derived through various
// tests. Without this, the top 1-2 rows of pixels in our window would be obscured.
let dpi = unsafe { GetDpiForWindow(handle) };
let frame_thickness = get_frame_thicknessx(dpi);
let frame_thickness = get_frame_thickness(dpi);
let top_insets = if is_maximized {
frame_thickness
} else {
@@ -1525,18 +1548,12 @@ fn get_client_area_insets(
// borders on Windows:
// - SM_CXSIZEFRAME: The resize handle.
// - SM_CXPADDEDBORDER: Additional border space that isn't part of the resize handle.
fn get_frame_thicknessx(dpi: u32) -> i32 {
fn get_frame_thickness(dpi: u32) -> i32 {
let resize_frame_thickness = unsafe { GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) };
let padding_thickness = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
resize_frame_thickness + padding_thickness
}
fn get_frame_thicknessy(dpi: u32) -> i32 {
let resize_frame_thickness = unsafe { GetSystemMetricsForDpi(SM_CYSIZEFRAME, dpi) };
let padding_thickness = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
resize_frame_thickness + padding_thickness
}
fn notify_frame_changed(handle: HWND) {
unsafe {
SetWindowPos(

View File

@@ -1,5 +1,5 @@
use std::{
cell::{Cell, RefCell},
cell::RefCell,
ffi::OsStr,
path::{Path, PathBuf},
rc::{Rc, Weak},
@@ -47,7 +47,7 @@ pub(crate) struct WindowsPlatform {
}
struct WindowsPlatformInner {
state: WindowsPlatformState,
state: RefCell<WindowsPlatformState>,
raw_window_handles: std::sync::Weak<RwLock<SmallVec<[SafeHwnd; 4]>>>,
// The below members will never change throughout the entire lifecycle of the app.
validation_number: usize,
@@ -57,22 +57,22 @@ struct WindowsPlatformInner {
pub(crate) struct WindowsPlatformState {
callbacks: PlatformCallbacks,
menus: RefCell<Vec<OwnedMenu>>,
jump_list: RefCell<JumpList>,
menus: Vec<OwnedMenu>,
jump_list: JumpList,
// NOTE: standard cursor handles don't need to close.
pub(crate) current_cursor: Cell<Option<HCURSOR>>,
directx_devices: RefCell<Option<DirectXDevices>>,
pub(crate) current_cursor: Option<HCURSOR>,
directx_devices: Option<DirectXDevices>,
}
#[derive(Default)]
struct PlatformCallbacks {
open_urls: Cell<Option<Box<dyn FnMut(Vec<String>)>>>,
quit: Cell<Option<Box<dyn FnMut()>>>,
reopen: Cell<Option<Box<dyn FnMut()>>>,
app_menu_action: Cell<Option<Box<dyn FnMut(&dyn Action)>>>,
will_open_app_menu: Cell<Option<Box<dyn FnMut()>>>,
validate_app_menu_command: Cell<Option<Box<dyn FnMut(&dyn Action) -> bool>>>,
keyboard_layout_change: Cell<Option<Box<dyn FnMut()>>>,
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
quit: Option<Box<dyn FnMut()>>,
reopen: Option<Box<dyn FnMut()>>,
app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
will_open_app_menu: Option<Box<dyn FnMut()>>,
validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
keyboard_layout_change: Option<Box<dyn FnMut()>>,
}
impl WindowsPlatformState {
@@ -84,10 +84,10 @@ impl WindowsPlatformState {
Self {
callbacks,
jump_list: RefCell::new(jump_list),
current_cursor: Cell::new(current_cursor),
directx_devices: RefCell::new(directx_devices),
menus: RefCell::new(Vec::new()),
jump_list,
current_cursor,
directx_devices,
menus: Vec::new(),
}
}
}
@@ -194,14 +194,14 @@ impl WindowsPlatform {
WindowCreationInfo {
icon: self.icon,
executor: self.foreground_executor.clone(),
current_cursor: self.inner.state.current_cursor.get(),
current_cursor: self.inner.state.borrow().current_cursor,
windows_version: self.windows_version,
drop_target_helper: self.drop_target_helper.clone(),
validation_number: self.inner.validation_number,
main_receiver: self.inner.main_receiver.clone(),
platform_window_handle: self.handle,
disable_direct_composition: self.disable_direct_composition,
directx_devices: self.inner.state.directx_devices.borrow().clone().unwrap(),
directx_devices: self.inner.state.borrow().directx_devices.clone().unwrap(),
invalidate_devices: self.invalidate_devices.clone(),
}
}
@@ -213,8 +213,9 @@ impl WindowsPlatform {
actions.push(dock_menu);
}
});
self.inner.state.jump_list.borrow_mut().dock_menus = actions;
update_jump_list(&self.inner.state.jump_list.borrow()).log_err();
let mut lock = self.inner.state.borrow_mut();
lock.jump_list.dock_menus = actions;
update_jump_list(&lock.jump_list).log_err();
}
fn update_jump_list(
@@ -228,10 +229,12 @@ impl WindowsPlatform {
actions.push(dock_menu);
}
});
let mut jump_list = self.inner.state.jump_list.borrow_mut();
jump_list.dock_menus = actions;
jump_list.recent_workspaces = entries;
update_jump_list(&jump_list).log_err().unwrap_or_default()
let mut lock = self.inner.state.borrow_mut();
lock.jump_list.dock_menus = actions;
lock.jump_list.recent_workspaces = entries;
update_jump_list(&lock.jump_list)
.log_err()
.unwrap_or_default()
}
fn find_current_active_window(&self) -> Option<HWND> {
@@ -247,7 +250,7 @@ impl WindowsPlatform {
}
fn begin_vsync_thread(&self) {
let mut directx_device = self.inner.state.directx_devices.borrow().clone().unwrap();
let mut directx_device = self.inner.state.borrow().directx_devices.clone().unwrap();
let platform_window: SafeHwnd = self.handle.into();
let validation_number = self.inner.validation_number;
let all_windows = Arc::downgrade(&self.raw_window_handles);
@@ -331,9 +334,9 @@ impl Platform for WindowsPlatform {
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
self.inner
.state
.borrow_mut()
.callbacks
.keyboard_layout_change
.set(Some(callback));
.keyboard_layout_change = Some(callback);
}
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
@@ -351,7 +354,7 @@ impl Platform for WindowsPlatform {
}
self.inner
.with_callback(|callbacks| &callbacks.quit, |callback| callback());
.with_callback(|callbacks| &mut callbacks.quit, |callback| callback());
}
fn quit(&self) {
@@ -470,7 +473,7 @@ impl Platform for WindowsPlatform {
}
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
self.inner.state.callbacks.open_urls.set(Some(callback));
self.inner.state.borrow_mut().callbacks.open_urls = Some(callback);
}
fn prompt_for_paths(
@@ -540,19 +543,19 @@ impl Platform for WindowsPlatform {
}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
self.inner.state.callbacks.quit.set(Some(callback));
self.inner.state.borrow_mut().callbacks.quit = Some(callback);
}
fn on_reopen(&self, callback: Box<dyn FnMut()>) {
self.inner.state.callbacks.reopen.set(Some(callback));
self.inner.state.borrow_mut().callbacks.reopen = Some(callback);
}
fn set_menus(&self, menus: Vec<Menu>, _keymap: &Keymap) {
*self.inner.state.menus.borrow_mut() = menus.into_iter().map(|menu| menu.owned()).collect();
self.inner.state.borrow_mut().menus = menus.into_iter().map(|menu| menu.owned()).collect();
}
fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
Some(self.inner.state.menus.borrow().clone())
Some(self.inner.state.borrow().menus.clone())
}
fn set_dock_menu(&self, menus: Vec<MenuItem>, _keymap: &Keymap) {
@@ -560,27 +563,19 @@ impl Platform for WindowsPlatform {
}
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
self.inner
.state
.callbacks
.app_menu_action
.set(Some(callback));
self.inner.state.borrow_mut().callbacks.app_menu_action = Some(callback);
}
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
self.inner
.state
.callbacks
.will_open_app_menu
.set(Some(callback));
self.inner.state.borrow_mut().callbacks.will_open_app_menu = Some(callback);
}
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
self.inner
.state
.borrow_mut()
.callbacks
.validate_app_menu_command
.set(Some(callback));
.validate_app_menu_command = Some(callback);
}
fn app_path(&self) -> Result<PathBuf> {
@@ -594,13 +589,13 @@ impl Platform for WindowsPlatform {
fn set_cursor_style(&self, style: CursorStyle) {
let hcursor = load_cursor(style);
if self.inner.state.current_cursor.get().map(|c| c.0) != hcursor.map(|c| c.0) {
if self.inner.state.borrow_mut().current_cursor.map(|c| c.0) != hcursor.map(|c| c.0) {
self.post_message(
WM_GPUI_CURSOR_STYLE_CHANGED,
WPARAM(0),
LPARAM(hcursor.map_or(0, |c| c.0 as isize)),
);
self.inner.state.current_cursor.set(hcursor);
self.inner.state.borrow_mut().current_cursor = hcursor;
}
}
@@ -726,12 +721,12 @@ impl Platform for WindowsPlatform {
impl WindowsPlatformInner {
fn new(context: &mut PlatformWindowCreateContext) -> Result<Rc<Self>> {
let state = WindowsPlatformState::new(
let state = RefCell::new(WindowsPlatformState::new(
context
.directx_devices
.take()
.context("missing directx devices")?,
);
));
Ok(Rc::new(Self {
state,
raw_window_handles: context.raw_window_handles.clone(),
@@ -751,13 +746,13 @@ impl WindowsPlatformInner {
/// Calls `project` to project to the corresponding callback field, removes it from callbacks, calls `f` with the callback and then puts the callback back.
fn with_callback<T>(
&self,
project: impl Fn(&PlatformCallbacks) -> &Cell<Option<T>>,
project: impl Fn(&mut PlatformCallbacks) -> &mut Option<T>,
f: impl FnOnce(&mut T),
) {
let callback = project(&self.state.callbacks).take();
let callback = project(&mut self.state.borrow_mut().callbacks).take();
if let Some(mut callback) = callback {
f(&mut callback);
project(&self.state.callbacks).set(Some(callback));
*project(&mut self.state.borrow_mut().callbacks) = Some(callback)
}
}
@@ -882,8 +877,8 @@ impl WindowsPlatformInner {
fn handle_dock_action_event(&self, action_idx: usize) -> Option<isize> {
let Some(action) = self
.state
.borrow_mut()
.jump_list
.borrow()
.dock_menus
.get(action_idx)
.map(|dock_menu| dock_menu.action.boxed_clone())
@@ -892,7 +887,7 @@ impl WindowsPlatformInner {
return Some(1);
};
self.with_callback(
|callbacks| &callbacks.app_menu_action,
|callbacks| &mut callbacks.app_menu_action,
|callback| callback(&*action),
);
Some(0)
@@ -900,7 +895,7 @@ impl WindowsPlatformInner {
fn handle_keyboard_layout_change(&self) -> Option<isize> {
self.with_callback(
|callbacks| &callbacks.keyboard_layout_change,
|callbacks| &mut callbacks.keyboard_layout_change,
|callback| callback(),
);
Some(0)
@@ -909,8 +904,9 @@ impl WindowsPlatformInner {
fn handle_device_lost(&self, lparam: LPARAM) -> Option<isize> {
let directx_devices = lparam.0 as *const DirectXDevices;
let directx_devices = unsafe { &*directx_devices };
self.state.directx_devices.borrow_mut().take();
*self.state.directx_devices.borrow_mut() = Some(directx_devices.clone());
let mut lock = self.state.borrow_mut();
lock.directx_devices.take();
lock.directx_devices = Some(directx_devices.clone());
Some(0)
}

View File

@@ -1,7 +1,4 @@
use std::{
cell::Cell,
ffi::{c_uint, c_void},
};
use std::ffi::{c_uint, c_void};
use ::util::ResultExt;
use windows::Win32::UI::{
@@ -18,18 +15,18 @@ use super::WindowsDisplay;
/// Windows settings pulled from SystemParametersInfo
/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow
#[derive(Default, Debug, Clone)]
#[derive(Default, Debug, Clone, Copy)]
pub(crate) struct WindowsSystemSettings {
pub(crate) mouse_wheel_settings: MouseWheelSettings,
pub(crate) auto_hide_taskbar_position: Cell<Option<AutoHideTaskbarPosition>>,
pub(crate) auto_hide_taskbar_position: Option<AutoHideTaskbarPosition>,
}
#[derive(Default, Debug, Clone)]
#[derive(Default, Debug, Clone, Copy)]
pub(crate) struct MouseWheelSettings {
/// SEE: SPI_GETWHEELSCROLLCHARS
pub(crate) wheel_scroll_chars: Cell<u32>,
pub(crate) wheel_scroll_chars: u32,
/// SEE: SPI_GETWHEELSCROLLLINES
pub(crate) wheel_scroll_lines: Cell<u32>,
pub(crate) wheel_scroll_lines: u32,
}
impl WindowsSystemSettings {
@@ -39,13 +36,12 @@ impl WindowsSystemSettings {
settings
}
fn init(&self, display: WindowsDisplay) {
fn init(&mut self, display: WindowsDisplay) {
self.mouse_wheel_settings.update();
self.auto_hide_taskbar_position
.set(AutoHideTaskbarPosition::new(display).log_err().flatten());
self.auto_hide_taskbar_position = AutoHideTaskbarPosition::new(display).log_err().flatten();
}
pub(crate) fn update(&self, display: WindowsDisplay, wparam: usize) {
pub(crate) fn update(&mut self, display: WindowsDisplay, wparam: usize) {
match wparam {
// SPI_SETWORKAREA
47 => self.update_taskbar_position(display),
@@ -55,23 +51,22 @@ impl WindowsSystemSettings {
}
}
fn update_mouse_wheel_settings(&self) {
fn update_mouse_wheel_settings(&mut self) {
self.mouse_wheel_settings.update();
}
fn update_taskbar_position(&self, display: WindowsDisplay) {
self.auto_hide_taskbar_position
.set(AutoHideTaskbarPosition::new(display).log_err().flatten());
fn update_taskbar_position(&mut self, display: WindowsDisplay) {
self.auto_hide_taskbar_position = AutoHideTaskbarPosition::new(display).log_err().flatten();
}
}
impl MouseWheelSettings {
fn update(&self) {
fn update(&mut self) {
self.update_wheel_scroll_chars();
self.update_wheel_scroll_lines();
}
fn update_wheel_scroll_chars(&self) {
fn update_wheel_scroll_chars(&mut self) {
let mut value = c_uint::default();
let result = unsafe {
SystemParametersInfoW(
@@ -82,12 +77,12 @@ impl MouseWheelSettings {
)
};
if result.log_err() != None && self.wheel_scroll_chars.get() != value {
self.wheel_scroll_chars.set(value);
if result.log_err() != None && self.wheel_scroll_chars != value {
self.wheel_scroll_chars = value;
}
}
fn update_wheel_scroll_lines(&self) {
fn update_wheel_scroll_lines(&mut self) {
let mut value = c_uint::default();
let result = unsafe {
SystemParametersInfoW(
@@ -98,8 +93,8 @@ impl MouseWheelSettings {
)
};
if result.log_err() != None && self.wheel_scroll_lines.get() != value {
self.wheel_scroll_lines.set(value);
if result.log_err() != None && self.wheel_scroll_lines != value {
self.wheel_scroll_lines = value;
}
}
}

View File

@@ -1,7 +1,7 @@
#![deny(unsafe_op_in_unsafe_fn)]
use std::{
cell::{Cell, RefCell},
cell::RefCell,
num::NonZeroIsize,
path::PathBuf,
rc::{Rc, Weak},
@@ -30,51 +30,43 @@ use crate::*;
pub(crate) struct WindowsWindow(pub Rc<WindowsWindowInner>);
impl std::ops::Deref for WindowsWindow {
type Target = WindowsWindowInner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct WindowsWindowState {
pub origin: Cell<Point<Pixels>>,
pub logical_size: Cell<Size<Pixels>>,
pub origin: Point<Pixels>,
pub logical_size: Size<Pixels>,
pub min_size: Option<Size<Pixels>>,
pub fullscreen_restore_bounds: Cell<Bounds<Pixels>>,
pub fullscreen_restore_bounds: Bounds<Pixels>,
pub border_offset: WindowBorderOffset,
pub appearance: Cell<WindowAppearance>,
pub scale_factor: Cell<f32>,
pub restore_from_minimized: Cell<Option<Box<dyn FnMut(RequestFrameOptions)>>>,
pub appearance: WindowAppearance,
pub scale_factor: f32,
pub restore_from_minimized: Option<Box<dyn FnMut(RequestFrameOptions)>>,
pub callbacks: Callbacks,
pub input_handler: Cell<Option<PlatformInputHandler>>,
pub pending_surrogate: Cell<Option<u16>>,
pub last_reported_modifiers: Cell<Option<Modifiers>>,
pub last_reported_capslock: Cell<Option<Capslock>>,
pub hovered: Cell<bool>,
pub input_handler: Option<PlatformInputHandler>,
pub pending_surrogate: Option<u16>,
pub last_reported_modifiers: Option<Modifiers>,
pub last_reported_capslock: Option<Capslock>,
pub hovered: bool,
pub renderer: RefCell<DirectXRenderer>,
pub renderer: DirectXRenderer,
pub click_state: ClickState,
pub current_cursor: Cell<Option<HCURSOR>>,
pub nc_button_pressed: Cell<Option<u32>>,
pub current_cursor: Option<HCURSOR>,
pub nc_button_pressed: Option<u32>,
pub display: Cell<WindowsDisplay>,
pub display: WindowsDisplay,
/// Flag to instruct the `VSyncProvider` thread to invalidate the directx devices
/// as resizing them has failed, causing us to have lost at least the render target.
pub invalidate_devices: Arc<AtomicBool>,
fullscreen: Cell<Option<StyleAndBounds>>,
initial_placement: Cell<Option<WindowOpenStatus>>,
fullscreen: Option<StyleAndBounds>,
initial_placement: Option<WindowOpenStatus>,
hwnd: HWND,
}
pub(crate) struct WindowsWindowInner {
hwnd: HWND,
drop_target_helper: IDropTargetHelper,
pub(crate) state: WindowsWindowState,
system_settings: WindowsSystemSettings,
pub(crate) state: RefCell<WindowsWindowState>,
system_settings: RefCell<WindowsSystemSettings>,
pub(crate) handle: AnyWindowHandle,
pub(crate) hide_title_bar: bool,
pub(crate) is_movable: bool,
@@ -129,27 +121,27 @@ impl WindowsWindowState {
let initial_placement = None;
Ok(Self {
origin: Cell::new(origin),
logical_size: Cell::new(logical_size),
fullscreen_restore_bounds: Cell::new(fullscreen_restore_bounds),
origin,
logical_size,
fullscreen_restore_bounds,
border_offset,
appearance: Cell::new(appearance),
scale_factor: Cell::new(scale_factor),
restore_from_minimized: Cell::new(restore_from_minimized),
appearance,
scale_factor,
restore_from_minimized,
min_size,
callbacks,
input_handler: Cell::new(input_handler),
pending_surrogate: Cell::new(pending_surrogate),
last_reported_modifiers: Cell::new(last_reported_modifiers),
last_reported_capslock: Cell::new(last_reported_capslock),
hovered: Cell::new(hovered),
renderer: RefCell::new(renderer),
input_handler,
pending_surrogate,
last_reported_modifiers,
last_reported_capslock,
hovered,
renderer,
click_state,
current_cursor: Cell::new(current_cursor),
nc_button_pressed: Cell::new(nc_button_pressed),
display: Cell::new(display),
fullscreen: Cell::new(fullscreen),
initial_placement: Cell::new(initial_placement),
current_cursor,
nc_button_pressed,
display,
fullscreen,
initial_placement,
hwnd,
invalidate_devices,
})
@@ -157,7 +149,7 @@ impl WindowsWindowState {
#[inline]
pub(crate) fn is_fullscreen(&self) -> bool {
self.fullscreen.get().is_some()
self.fullscreen.is_some()
}
pub(crate) fn is_maximized(&self) -> bool {
@@ -166,8 +158,8 @@ impl WindowsWindowState {
fn bounds(&self) -> Bounds<Pixels> {
Bounds {
origin: self.origin.get(),
size: self.logical_size.get(),
origin: self.origin,
size: self.logical_size,
}
}
@@ -186,8 +178,8 @@ impl WindowsWindowState {
(
calculate_client_rect(
placement.rcNormalPosition,
&self.border_offset,
self.scale_factor.get(),
self.border_offset,
self.scale_factor,
),
placement.showCmd == SW_SHOWMAXIMIZED.0 as u32,
)
@@ -197,7 +189,7 @@ impl WindowsWindowState {
let (bounds, maximized) = self.calculate_window_bounds();
if self.is_fullscreen() {
WindowBounds::Fullscreen(self.fullscreen_restore_bounds.get())
WindowBounds::Fullscreen(self.fullscreen_restore_bounds)
} else if maximized {
WindowBounds::Maximized(bounds)
} else {
@@ -210,13 +202,13 @@ impl WindowsWindowState {
/// Currently, GPUI uses the logical size of the app to handle mouse interactions (such as
/// whether the mouse collides with other elements of GPUI).
fn content_size(&self) -> Size<Pixels> {
self.logical_size.get()
self.logical_size
}
}
impl WindowsWindowInner {
fn new(context: &mut WindowCreateContext, hwnd: HWND, cs: &CREATESTRUCTW) -> Result<Rc<Self>> {
let state = WindowsWindowState::new(
let state = RefCell::new(WindowsWindowState::new(
hwnd,
&context.directx_devices,
cs,
@@ -226,7 +218,7 @@ impl WindowsWindowInner {
context.appearance,
context.disable_direct_composition,
context.invalidate_devices.clone(),
)?;
)?);
Ok(Rc::new(Self {
hwnd,
@@ -240,7 +232,7 @@ impl WindowsWindowInner {
validation_number: context.validation_number,
main_receiver: context.main_receiver.clone(),
platform_window_handle: context.platform_window_handle,
system_settings: WindowsSystemSettings::new(context.display),
system_settings: RefCell::new(WindowsSystemSettings::new(context.display)),
}))
}
@@ -248,17 +240,19 @@ impl WindowsWindowInner {
let this = self.clone();
self.executor
.spawn(async move {
let mut lock = this.state.borrow_mut();
let StyleAndBounds {
style,
x,
y,
cx,
cy,
} = match this.state.fullscreen.take() {
} = match lock.fullscreen.take() {
Some(state) => state,
None => {
let (window_bounds, _) = this.state.calculate_window_bounds();
this.state.fullscreen_restore_bounds.set(window_bounds);
let (window_bounds, _) = lock.calculate_window_bounds();
lock.fullscreen_restore_bounds = window_bounds;
drop(lock);
let style =
WINDOW_STYLE(unsafe { get_window_long(this.hwnd, GWL_STYLE) } as _);
@@ -266,20 +260,22 @@ impl WindowsWindowInner {
unsafe { GetWindowRect(this.hwnd, &mut rc) }
.context("failed to get window rect")
.log_err();
let _ = this.state.fullscreen.set(Some(StyleAndBounds {
lock = this.state.borrow_mut();
let _ = lock.fullscreen.insert(StyleAndBounds {
style,
x: rc.left,
y: rc.top,
cx: rc.right - rc.left,
cy: rc.bottom - rc.top,
}));
});
let style = style
& !(WS_THICKFRAME
| WS_SYSMENU
| WS_MAXIMIZEBOX
| WS_MINIMIZEBOX
| WS_CAPTION);
let physical_bounds = this.state.display.get().physical_bounds();
let physical_bounds = lock.display.physical_bounds();
StyleAndBounds {
style,
x: physical_bounds.left().0,
@@ -289,6 +285,7 @@ impl WindowsWindowInner {
}
}
};
drop(lock);
unsafe { set_window_long(this.hwnd, GWL_STYLE, style.0 as isize) };
unsafe {
SetWindowPos(
@@ -307,7 +304,7 @@ impl WindowsWindowInner {
}
fn set_window_placement(self: &Rc<Self>) -> Result<()> {
let Some(open_status) = self.state.initial_placement.take() else {
let Some(open_status) = self.state.borrow_mut().initial_placement.take() else {
return Ok(());
};
match open_status.state {
@@ -331,23 +328,27 @@ impl WindowsWindowInner {
Ok(())
}
pub(crate) fn system_settings(&self) -> &WindowsSystemSettings {
&self.system_settings
pub(crate) fn system_settings(&self) -> std::cell::Ref<'_, WindowsSystemSettings> {
self.system_settings.borrow()
}
pub(crate) fn system_settings_mut(&self) -> std::cell::RefMut<'_, WindowsSystemSettings> {
self.system_settings.borrow_mut()
}
}
#[derive(Default)]
pub(crate) struct Callbacks {
pub(crate) request_frame: Cell<Option<Box<dyn FnMut(RequestFrameOptions)>>>,
pub(crate) input: Cell<Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>>,
pub(crate) active_status_change: Cell<Option<Box<dyn FnMut(bool)>>>,
pub(crate) hovered_status_change: Cell<Option<Box<dyn FnMut(bool)>>>,
pub(crate) resize: Cell<Option<Box<dyn FnMut(Size<Pixels>, f32)>>>,
pub(crate) moved: Cell<Option<Box<dyn FnMut()>>>,
pub(crate) should_close: Cell<Option<Box<dyn FnMut() -> bool>>>,
pub(crate) close: Cell<Option<Box<dyn FnOnce()>>>,
pub(crate) hit_test_window_control: Cell<Option<Box<dyn FnMut() -> Option<WindowControlArea>>>>,
pub(crate) appearance_changed: Cell<Option<Box<dyn FnMut()>>>,
pub(crate) request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
pub(crate) input: Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>,
pub(crate) active_status_change: Option<Box<dyn FnMut(bool)>>,
pub(crate) hovered_status_change: Option<Box<dyn FnMut(bool)>>,
pub(crate) resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
pub(crate) moved: Option<Box<dyn FnMut()>>,
pub(crate) should_close: Option<Box<dyn FnMut() -> bool>>,
pub(crate) close: Option<Box<dyn FnOnce()>>,
pub(crate) hit_test_window_control: Option<Box<dyn FnMut() -> Option<WindowControlArea>>>,
pub(crate) appearance_changed: Option<Box<dyn FnMut()>>,
}
struct WindowCreateContext {
@@ -475,21 +476,21 @@ impl WindowsWindow {
register_drag_drop(&this)?;
configure_dwm_dark_mode(hwnd, appearance);
this.state.border_offset.update(hwnd)?;
this.state.borrow_mut().border_offset.update(hwnd)?;
let placement = retrieve_window_placement(
hwnd,
display,
params.bounds,
this.state.scale_factor.get(),
&this.state.border_offset,
this.state.borrow().scale_factor,
this.state.borrow().border_offset,
)?;
if params.show {
unsafe { SetWindowPlacement(hwnd, &placement)? };
} else {
this.state.initial_placement.set(Some(WindowOpenStatus {
this.state.borrow_mut().initial_placement = Some(WindowOpenStatus {
placement,
state: WindowOpenState::Windowed,
}));
});
}
Ok(Self(this))
@@ -532,15 +533,15 @@ impl Drop for WindowsWindow {
impl PlatformWindow for WindowsWindow {
fn bounds(&self) -> Bounds<Pixels> {
self.state.bounds()
self.0.state.borrow().bounds()
}
fn is_maximized(&self) -> bool {
self.state.is_maximized()
self.0.state.borrow().is_maximized()
}
fn window_bounds(&self) -> WindowBounds {
self.state.window_bounds()
self.0.state.borrow().window_bounds()
}
/// get the logical size of the app's drawable area.
@@ -548,14 +549,14 @@ impl PlatformWindow for WindowsWindow {
/// Currently, GPUI uses the logical size of the app to handle mouse interactions (such as
/// whether the mouse collides with other elements of GPUI).
fn content_size(&self) -> Size<Pixels> {
self.state.content_size()
self.0.state.borrow().content_size()
}
fn resize(&mut self, size: Size<Pixels>) {
let hwnd = self.0.hwnd;
let bounds =
crate::bounds(self.bounds().origin, size).to_device_pixels(self.scale_factor());
let rect = calculate_window_rect(bounds, &self.state.border_offset);
let rect = calculate_window_rect(bounds, self.0.state.borrow().border_offset);
self.0
.executor
@@ -578,15 +579,15 @@ impl PlatformWindow for WindowsWindow {
}
fn scale_factor(&self) -> f32 {
self.state.scale_factor.get()
self.0.state.borrow().scale_factor
}
fn appearance(&self) -> WindowAppearance {
self.state.appearance.get()
self.0.state.borrow().appearance
}
fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
Some(Rc::new(self.state.display.get()))
Some(Rc::new(self.0.state.borrow().display))
}
fn mouse_position(&self) -> Point<Pixels> {
@@ -611,11 +612,11 @@ impl PlatformWindow for WindowsWindow {
}
fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
self.state.input_handler.set(Some(input_handler));
self.0.state.borrow_mut().input_handler = Some(input_handler);
}
fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
self.state.input_handler.take()
self.0.state.borrow_mut().input_handler.take()
}
fn prompt(
@@ -761,7 +762,7 @@ impl PlatformWindow for WindowsWindow {
}
fn is_hovered(&self) -> bool {
self.state.hovered.get()
self.0.state.borrow().hovered
}
fn set_title(&mut self, title: &str) {
@@ -804,9 +805,8 @@ impl PlatformWindow for WindowsWindow {
unsafe {
if IsWindowVisible(self.0.hwnd).as_bool() {
ShowWindowAsync(self.0.hwnd, SW_MAXIMIZE).ok().log_err();
} else if let Some(mut status) = self.state.initial_placement.take() {
} else if let Some(status) = self.0.state.borrow_mut().initial_placement.as_mut() {
status.state = WindowOpenState::Maximized;
self.state.initial_placement.set(Some(status));
}
}
}
@@ -814,78 +814,61 @@ impl PlatformWindow for WindowsWindow {
fn toggle_fullscreen(&self) {
if unsafe { IsWindowVisible(self.0.hwnd).as_bool() } {
self.0.toggle_fullscreen();
} else if let Some(mut status) = self.state.initial_placement.take() {
} else if let Some(status) = self.0.state.borrow_mut().initial_placement.as_mut() {
status.state = WindowOpenState::Fullscreen;
self.state.initial_placement.set(Some(status));
}
}
fn is_fullscreen(&self) -> bool {
self.state.is_fullscreen()
self.0.state.borrow().is_fullscreen()
}
fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
self.state.callbacks.request_frame.set(Some(callback));
self.0.state.borrow_mut().callbacks.request_frame = Some(callback);
}
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {
self.state.callbacks.input.set(Some(callback));
self.0.state.borrow_mut().callbacks.input = Some(callback);
}
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
self.0
.state
.callbacks
.active_status_change
.set(Some(callback));
self.0.state.borrow_mut().callbacks.active_status_change = Some(callback);
}
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
self.0
.state
.callbacks
.hovered_status_change
.set(Some(callback));
self.0.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.state.callbacks.resize.set(Some(callback));
self.0.state.borrow_mut().callbacks.resize = Some(callback);
}
fn on_moved(&self, callback: Box<dyn FnMut()>) {
self.state.callbacks.moved.set(Some(callback));
self.0.state.borrow_mut().callbacks.moved = Some(callback);
}
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
self.state.callbacks.should_close.set(Some(callback));
self.0.state.borrow_mut().callbacks.should_close = Some(callback);
}
fn on_close(&self, callback: Box<dyn FnOnce()>) {
self.state.callbacks.close.set(Some(callback));
self.0.state.borrow_mut().callbacks.close = Some(callback);
}
fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
self.0
.state
.callbacks
.hit_test_window_control
.set(Some(callback));
self.0.state.borrow_mut().callbacks.hit_test_window_control = Some(callback);
}
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
self.0
.state
.callbacks
.appearance_changed
.set(Some(callback));
self.0.state.borrow_mut().callbacks.appearance_changed = Some(callback);
}
fn draw(&self, scene: &Scene) {
self.state.renderer.borrow_mut().draw(scene).log_err();
self.0.state.borrow_mut().renderer.draw(scene).log_err();
}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
self.state.renderer.borrow().sprite_atlas()
self.0.state.borrow().renderer.sprite_atlas()
}
fn get_raw_handle(&self) -> HWND {
@@ -893,7 +876,7 @@ impl PlatformWindow for WindowsWindow {
}
fn gpu_specs(&self) -> Option<GpuSpecs> {
self.state.renderer.borrow().gpu_specs().log_err()
self.0.state.borrow().renderer.gpu_specs().log_err()
}
fn update_ime_position(&self, _bounds: Bounds<Pixels>) {
@@ -906,9 +889,11 @@ struct WindowsDragDropHandler(pub Rc<WindowsWindowInner>);
impl WindowsDragDropHandler {
fn handle_drag_drop(&self, input: PlatformInput) {
if let Some(mut func) = self.0.state.callbacks.input.take() {
let mut lock = self.0.state.borrow_mut();
if let Some(mut func) = lock.callbacks.input.take() {
drop(lock);
func(input);
self.0.state.callbacks.input.set(Some(func));
self.0.state.borrow_mut().callbacks.input = Some(func);
}
}
}
@@ -952,7 +937,7 @@ impl IDropTarget_Impl for WindowsDragDropHandler_Impl {
ScreenToClient(self.0.hwnd, &mut cursor_position)
.ok()
.log_err();
let scale_factor = self.0.state.scale_factor.get();
let scale_factor = self.0.state.borrow().scale_factor;
let input = PlatformInput::FileDrop(FileDropEvent::Entered {
position: logical_point(
cursor_position.x as f32,
@@ -990,7 +975,7 @@ impl IDropTarget_Impl for WindowsDragDropHandler_Impl {
.ok()
.log_err();
}
let scale_factor = self.0.state.scale_factor.get();
let scale_factor = self.0.state.borrow().scale_factor;
let input = PlatformInput::FileDrop(FileDropEvent::Pending {
position: logical_point(
cursor_position.x as f32,
@@ -1032,7 +1017,7 @@ impl IDropTarget_Impl for WindowsDragDropHandler_Impl {
.ok()
.log_err();
}
let scale_factor = self.0.state.scale_factor.get();
let scale_factor = self.0.state.borrow().scale_factor;
let input = PlatformInput::FileDrop(FileDropEvent::Submit {
position: logical_point(
cursor_position.x as f32,
@@ -1046,15 +1031,15 @@ impl IDropTarget_Impl for WindowsDragDropHandler_Impl {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub(crate) struct ClickState {
button: Cell<MouseButton>,
last_click: Cell<Instant>,
last_position: Cell<Point<DevicePixels>>,
double_click_spatial_tolerance_width: Cell<i32>,
double_click_spatial_tolerance_height: Cell<i32>,
double_click_interval: Cell<Duration>,
pub(crate) current_count: Cell<usize>,
button: MouseButton,
last_click: Instant,
last_position: Point<DevicePixels>,
double_click_spatial_tolerance_width: i32,
double_click_spatial_tolerance_height: i32,
double_click_interval: Duration,
pub(crate) current_count: usize,
}
impl ClickState {
@@ -1064,59 +1049,61 @@ impl ClickState {
let double_click_interval = Duration::from_millis(unsafe { GetDoubleClickTime() } as u64);
ClickState {
button: Cell::new(MouseButton::Left),
last_click: Cell::new(Instant::now()),
last_position: Cell::new(Point::default()),
double_click_spatial_tolerance_width: Cell::new(double_click_spatial_tolerance_width),
double_click_spatial_tolerance_height: Cell::new(double_click_spatial_tolerance_height),
double_click_interval: Cell::new(double_click_interval),
current_count: Cell::new(0),
button: MouseButton::Left,
last_click: Instant::now(),
last_position: Point::default(),
double_click_spatial_tolerance_width,
double_click_spatial_tolerance_height,
double_click_interval,
current_count: 0,
}
}
/// update self and return the needed click count
pub fn update(&self, button: MouseButton, new_position: Point<DevicePixels>) -> usize {
if self.button.get() == button && self.is_double_click(new_position) {
self.current_count.update(|it| it + 1);
pub fn update(&mut self, button: MouseButton, new_position: Point<DevicePixels>) -> usize {
if self.button == button && self.is_double_click(new_position) {
self.current_count += 1;
} else {
self.current_count.set(1);
self.current_count = 1;
}
self.last_click.set(Instant::now());
self.last_position.set(new_position);
self.button.set(button);
self.last_click = Instant::now();
self.last_position = new_position;
self.button = button;
self.current_count.get()
self.current_count
}
pub fn system_update(&self, wparam: usize) {
pub fn system_update(&mut self, wparam: usize) {
match wparam {
// SPI_SETDOUBLECLKWIDTH
29 => self
.double_click_spatial_tolerance_width
.set(unsafe { GetSystemMetrics(SM_CXDOUBLECLK) }),
29 => {
self.double_click_spatial_tolerance_width =
unsafe { GetSystemMetrics(SM_CXDOUBLECLK) }
}
// SPI_SETDOUBLECLKHEIGHT
30 => self
.double_click_spatial_tolerance_height
.set(unsafe { GetSystemMetrics(SM_CYDOUBLECLK) }),
30 => {
self.double_click_spatial_tolerance_height =
unsafe { GetSystemMetrics(SM_CYDOUBLECLK) }
}
// SPI_SETDOUBLECLICKTIME
32 => self
.double_click_interval
.set(Duration::from_millis(unsafe { GetDoubleClickTime() } as u64)),
32 => {
self.double_click_interval =
Duration::from_millis(unsafe { GetDoubleClickTime() } as u64)
}
_ => {}
}
}
#[inline]
fn is_double_click(&self, new_position: Point<DevicePixels>) -> bool {
let diff = self.last_position.get() - new_position;
let diff = self.last_position - new_position;
self.last_click.get().elapsed() < self.double_click_interval.get()
&& diff.x.0.abs() <= self.double_click_spatial_tolerance_width.get()
&& diff.y.0.abs() <= self.double_click_spatial_tolerance_height.get()
self.last_click.elapsed() < self.double_click_interval
&& diff.x.0.abs() <= self.double_click_spatial_tolerance_width
&& diff.y.0.abs() <= self.double_click_spatial_tolerance_height
}
}
#[derive(Copy, Clone)]
struct StyleAndBounds {
style: WINDOW_STYLE,
x: i32,
@@ -1142,14 +1129,14 @@ struct AccentPolicy {
type Color = (u8, u8, u8, u8);
#[derive(Debug, Default, Clone)]
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct WindowBorderOffset {
pub(crate) width_offset: Cell<i32>,
pub(crate) height_offset: Cell<i32>,
pub(crate) width_offset: i32,
pub(crate) height_offset: i32,
}
impl WindowBorderOffset {
pub(crate) fn update(&self, hwnd: HWND) -> anyhow::Result<()> {
pub(crate) fn update(&mut self, hwnd: HWND) -> anyhow::Result<()> {
let window_rect = unsafe {
let mut rect = std::mem::zeroed();
GetWindowRect(hwnd, &mut rect)?;
@@ -1160,21 +1147,19 @@ impl WindowBorderOffset {
GetClientRect(hwnd, &mut rect)?;
rect
};
self.width_offset
.set((window_rect.right - window_rect.left) - (client_rect.right - client_rect.left));
self.height_offset
.set((window_rect.bottom - window_rect.top) - (client_rect.bottom - client_rect.top));
self.width_offset =
(window_rect.right - window_rect.left) - (client_rect.right - client_rect.left);
self.height_offset =
(window_rect.bottom - window_rect.top) - (client_rect.bottom - client_rect.top);
Ok(())
}
}
#[derive(Clone)]
struct WindowOpenStatus {
placement: WINDOWPLACEMENT,
state: WindowOpenState,
}
#[derive(Clone, Copy)]
enum WindowOpenState {
Maximized,
Fullscreen,
@@ -1284,7 +1269,7 @@ fn register_drag_drop(window: &Rc<WindowsWindowInner>) -> Result<()> {
Ok(())
}
fn calculate_window_rect(bounds: Bounds<DevicePixels>, border_offset: &WindowBorderOffset) -> RECT {
fn calculate_window_rect(bounds: Bounds<DevicePixels>, border_offset: WindowBorderOffset) -> RECT {
// NOTE:
// The reason we're not using `AdjustWindowRectEx()` here is
// that the size reported by this function is incorrect.
@@ -1298,10 +1283,10 @@ fn calculate_window_rect(bounds: Bounds<DevicePixels>, border_offset: &WindowBor
right: bounds.right().0,
bottom: bounds.bottom().0,
};
let left_offset = border_offset.width_offset.get() / 2;
let top_offset = border_offset.height_offset.get() / 2;
let right_offset = border_offset.width_offset.get() - left_offset;
let bottom_offset = border_offset.height_offset.get() - top_offset;
let left_offset = border_offset.width_offset / 2;
let top_offset = border_offset.height_offset / 2;
let right_offset = border_offset.width_offset - left_offset;
let bottom_offset = border_offset.height_offset - top_offset;
rect.left -= left_offset;
rect.top -= top_offset;
rect.right += right_offset;
@@ -1311,13 +1296,13 @@ fn calculate_window_rect(bounds: Bounds<DevicePixels>, border_offset: &WindowBor
fn calculate_client_rect(
rect: RECT,
border_offset: &WindowBorderOffset,
border_offset: WindowBorderOffset,
scale_factor: f32,
) -> Bounds<Pixels> {
let left_offset = border_offset.width_offset.get() / 2;
let top_offset = border_offset.height_offset.get() / 2;
let right_offset = border_offset.width_offset.get() - left_offset;
let bottom_offset = border_offset.height_offset.get() - top_offset;
let left_offset = border_offset.width_offset / 2;
let top_offset = border_offset.height_offset / 2;
let right_offset = border_offset.width_offset - left_offset;
let bottom_offset = border_offset.height_offset - top_offset;
let left = rect.left + left_offset;
let top = rect.top + top_offset;
let right = rect.right - right_offset;
@@ -1334,7 +1319,7 @@ fn retrieve_window_placement(
display: WindowsDisplay,
initial_bounds: Bounds<Pixels>,
scale_factor: f32,
border_offset: &WindowBorderOffset,
border_offset: WindowBorderOffset,
) -> Result<WINDOWPLACEMENT> {
let mut placement = WINDOWPLACEMENT {
length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
@@ -1444,9 +1429,7 @@ mod tests {
state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
2
);
state
.last_click
.update(|it| it - Duration::from_millis(700));
state.last_click -= Duration::from_millis(700);
assert_eq!(
state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1

View File

@@ -596,7 +596,7 @@ pub enum HitboxBehavior {
/// ```
///
/// This has effects beyond event handling - any use of hitbox checking, such as hover
/// styles and tooltips. These other behaviors are the main point of this mechanism. An
/// styles and tooltops. These other behaviors are the main point of this mechanism. An
/// alternative might be to not affect mouse event handling - but this would allow
/// inconsistent UI where clicks and moves interact with elements that are not considered to
/// be hovered.
@@ -624,7 +624,7 @@ pub enum HitboxBehavior {
/// desired, then a `cx.stop_propagation()` handler like the one above can be used.
///
/// This has effects beyond event handling - this affects any use of `is_hovered`, such as
/// hover styles and tooltips. These other behaviors are the main point of this mechanism.
/// hover styles and tooltops. These other behaviors are the main point of this mechanism.
/// An alternative might be to not affect mouse event handling - but this would allow
/// inconsistent UI where clicks and moves interact with elements that are not considered to
/// be hovered.

View File

@@ -140,13 +140,7 @@ impl LspAdapter for TailwindLspAdapter {
) -> Result<Option<serde_json::Value>> {
Ok(Some(json!({
"provideFormatter": true,
"userLanguages": {
"html": "html",
"css": "css",
"javascript": "javascript",
"typescript": "typescript",
"typescriptreact": "typescriptreact",
},
})))
}
@@ -167,8 +161,18 @@ impl LspAdapter for TailwindLspAdapter {
tailwind_user_settings["emmetCompletions"] = Value::Bool(true);
}
if tailwind_user_settings.get("includeLanguages").is_none() {
tailwind_user_settings["includeLanguages"] = json!({
"html": "html",
"css": "css",
"javascript": "javascript",
"typescript": "typescript",
"typescriptreact": "typescriptreact",
});
}
Ok(json!({
"tailwindCSS": tailwind_user_settings,
"tailwindCSS": tailwind_user_settings
}))
}

View File

@@ -1,25 +1,3 @@
((comment) @injection.content
(#set! injection.language "comment")
)
; GitHub actions: JavaScript for workflow scripting (inline and block)
(block_mapping
(block_mapping_pair
key: (flow_node) @_uses (#eq? @_uses "uses")
value: (flow_node) @_actions_ghs (#match? @_actions_ghs "^actions/github-script"))
(block_mapping_pair
key: (flow_node) @_with (#eq? @_with "with")
value: (block_node
(block_mapping
(block_mapping_pair
key: (flow_node) @_run (#eq? @_run "script")
value: [
(flow_node (plain_scalar (string_scalar) @injection.content))
(block_node (block_scalar) @injection.content)
]
(#set! injection.language "javascript")
)
)
)
)
)

View File

@@ -98,14 +98,6 @@ impl Room {
self.room.connection_state()
}
pub fn name(&self) -> String {
self.room.name()
}
pub async fn sid(&self) -> String {
self.room.sid().await.to_string()
}
pub async fn publish_local_microphone_track(
&self,
user_name: String,

View File

@@ -714,14 +714,6 @@ impl Room {
self.0.lock().token.clone()
}
pub fn name(&self) -> String {
"test_room".to_string()
}
pub async fn sid(&self) -> String {
"RM_test_session".to_string()
}
pub fn play_remote_audio_track(
&self,
_track: &RemoteAudioTrack,

View File

@@ -667,7 +667,7 @@ impl LanguageServer {
#[allow(deprecated)]
InitializeParams {
process_id: Some(std::process::id()),
process_id: None,
root_path: None,
root_uri: Some(self.root_uri.clone()),
initialization_options: None,

View File

@@ -4648,11 +4648,9 @@ impl Repository {
});
let this = cx.weak_entity();
let rx = self.run_hook(RunHook::PrePush, cx);
self.send_job(
Some(format!("git push {} {} {}", args, remote, branch).into()),
move |git_repo, mut cx| async move {
rx.await??;
match git_repo {
RepositoryState::Local(LocalRepositoryState {
backend,

View File

@@ -9733,7 +9733,7 @@ async fn test_ignored_dirs_events(cx: &mut gpui::TestAppContext) {
("project/target/debug/deps".to_string(), PathChange::Added),
("project/target/debug/deps".to_string(), PathChange::Removed),
],
"Due to `debug` directory being tracked, it should get updates for entries inside it.
"Due to `debug` directory being tracket, it should get updates for entries inside it.
No updates for more nested directories should happen as those are ignored",
);
}

View File

@@ -565,7 +565,6 @@ message GitCreateWorktree {
message RunGitHook {
enum GitHook {
PRE_COMMIT = 0;
PRE_PUSH = 1;
}
uint64 project_id = 1;

View File

@@ -20,6 +20,7 @@ use std::{
time::Instant,
};
use util::{
ResultExt as _,
paths::{PathStyle, RemotePathBuf},
rel_path::RelPath,
shell::ShellKind,
@@ -48,6 +49,7 @@ pub(crate) struct WslRemoteConnection {
shell_kind: ShellKind,
default_system_shell: String,
connection_options: WslConnectionOptions,
can_exec: bool,
}
impl WslRemoteConnection {
@@ -71,6 +73,7 @@ impl WslRemoteConnection {
shell: String::new(),
shell_kind: ShellKind::Posix,
default_system_shell: String::from("/bin/sh"),
can_exec: true,
};
delegate.set_status(Some("Detecting WSL environment"), cx);
this.shell = this
@@ -79,6 +82,8 @@ impl WslRemoteConnection {
.context("failed detecting shell")?;
log::info!("Remote shell discovered: {}", this.shell);
this.shell_kind = ShellKind::new(&this.shell, false);
this.can_exec = this.detect_can_exec().await;
log::info!("Remote can exec: {}", this.can_exec);
this.platform = this
.detect_platform()
.await
@@ -94,9 +99,27 @@ impl WslRemoteConnection {
Ok(this)
}
async fn detect_can_exec(&self) -> bool {
let options = &self.connection_options;
let program = self.shell_kind.prepend_command_prefix("uname");
let args = &["-m"];
let output = wsl_command_impl(options, &program, args, true)
.output()
.await;
if !output.is_ok_and(|output| output.status.success()) {
run_wsl_command_impl(options, &program, args, false)
.await
.context("failed detecting exec status")
.log_err();
false
} else {
true
}
}
async fn detect_platform(&self) -> Result<RemotePlatform> {
let program = self.shell_kind.prepend_command_prefix("uname");
let arch_str = self.run_wsl_command_with_output(&program, &["-m"]).await?;
let arch_str = self.run_wsl_command(&program, &["-m"]).await?;
let arch_str = arch_str.trim().to_string();
let arch = match arch_str.as_str() {
"x86_64" => "x86_64",
@@ -108,25 +131,18 @@ impl WslRemoteConnection {
async fn detect_shell(&self) -> Result<String> {
Ok(self
.run_wsl_command_with_output("sh", &["-c", "echo $SHELL"])
.run_wsl_command("sh", &["-c", "echo $SHELL"])
.await
.inspect_err(|err| log::error!("Failed to detect remote shell: {err}"))
.ok()
.unwrap_or_else(|| "/bin/sh".to_string()))
}
async fn windows_path_to_wsl_path(&self, source: &Path) -> Result<String> {
windows_path_to_wsl_path_impl(&self.connection_options, source).await
windows_path_to_wsl_path_impl(&self.connection_options, source, self.can_exec).await
}
async fn run_wsl_command_with_output(&self, program: &str, args: &[&str]) -> Result<String> {
run_wsl_command_with_output_impl(&self.connection_options, program, args).await
}
async fn run_wsl_command(&self, program: &str, args: &[&str]) -> Result<()> {
run_wsl_command_impl(&self.connection_options, program, args, false)
.await
.map(|_| ())
async fn run_wsl_command(&self, program: &str, args: &[&str]) -> Result<String> {
run_wsl_command_impl(&self.connection_options, program, args, self.can_exec).await
}
async fn ensure_server_binary(
@@ -327,7 +343,7 @@ impl RemoteConnection for WslRemoteConnection {
}
let proxy_process =
match wsl_command_impl(&self.connection_options, "env", &proxy_args, false)
match wsl_command_impl(&self.connection_options, "env", &proxy_args, self.can_exec)
.kill_on_drop(true)
.spawn()
{
@@ -354,14 +370,15 @@ impl RemoteConnection for WslRemoteConnection {
) -> Task<Result<()>> {
cx.background_spawn({
let options = self.connection_options.clone();
let can_exec = self.can_exec;
async move {
let wsl_src = windows_path_to_wsl_path_impl(&options, &src_path).await?;
let wsl_src = windows_path_to_wsl_path_impl(&options, &src_path, can_exec).await?;
run_wsl_command_impl(
&options,
"cp",
&["-r", &wsl_src, &dest_path.to_string()],
true,
can_exec,
)
.await
.map_err(|e| {
@@ -503,26 +520,13 @@ async fn sanitize_path(path: &Path) -> Result<String> {
Ok(sanitized.replace('\\', "/"))
}
async fn run_wsl_command_with_output_impl(
options: &WslConnectionOptions,
program: &str,
args: &[&str],
) -> Result<String> {
match run_wsl_command_impl(options, program, args, true).await {
Ok(res) => Ok(res),
Err(exec_err) => match run_wsl_command_impl(options, program, args, false).await {
Ok(res) => Ok(res),
Err(e) => Err(e.context(exec_err)),
},
}
}
async fn windows_path_to_wsl_path_impl(
options: &WslConnectionOptions,
source: &Path,
exec: bool,
) -> Result<String> {
let source = sanitize_path(source).await?;
run_wsl_command_with_output_impl(options, "wslpath", &["-u", &source]).await
run_wsl_command_impl(options, "wslpath", &["-u", &source], exec).await
}
async fn run_wsl_command_impl(

View File

@@ -1,6 +1,6 @@
/// Trait for recursively merging settings structures.
///
/// When Zed starts it loads settings from `default.json` to initialize
/// When Zed starts it loads settinsg from `default.json` to initialize
/// everything. These may be further refined by loading the user's settings,
/// and any settings profiles; and then further refined by loading any
/// local project settings.

View File

@@ -8,68 +8,37 @@ pub(crate) fn neutral() -> ColorScaleSet {
sand()
}
const LIGHT_ADDED_COLOR: Hsla = Hsla {
const ADDED_COLOR: Hsla = Hsla {
h: 134. / 360.,
s: 0.55,
l: 0.40,
a: 1.0,
};
const LIGHT_WORD_ADDED_COLOR: Hsla = Hsla {
const WORD_ADDED_COLOR: Hsla = Hsla {
h: 134. / 360.,
s: 0.55,
l: 0.40,
a: 0.35,
};
const LIGHT_MODIFIED_COLOR: Hsla = Hsla {
const MODIFIED_COLOR: Hsla = Hsla {
h: 48. / 360.,
s: 0.76,
l: 0.47,
a: 1.0,
};
const LIGHT_REMOVED_COLOR: Hsla = Hsla {
const REMOVED_COLOR: Hsla = Hsla {
h: 350. / 360.,
s: 0.88,
l: 0.25,
a: 1.0,
};
const LIGHT_WORD_DELETED_COLOR: Hsla = Hsla {
const WORD_DELETED_COLOR: Hsla = Hsla {
h: 350. / 360.,
s: 0.88,
l: 0.25,
a: 0.80,
};
pub(crate) const DARK_ADDED_COLOR: Hsla = Hsla {
h: 1.0,
s: 0.55,
l: 0.30,
a: 0.20,
};
pub(crate) const DARK_WORD_ADDED_COLOR: Hsla = Hsla {
h: 135. / 360.,
s: 0.55,
l: 0.30,
a: 0.24,
};
pub(crate) const DARK_MODIFIED_COLOR: Hsla = Hsla {
h: 218. / 360.,
s: 0.66,
l: 0.36,
a: 0.20,
};
pub(crate) const DARK_REMOVED_COLOR: Hsla = Hsla {
h: 356. / 360.,
s: 0.87,
l: 0.35,
a: 0.32,
};
pub(crate) const DARK_WORD_DELETED_COLOR: Hsla = Hsla {
h: 356. / 360.,
s: 0.87,
l: 0.35,
a: 0.50,
};
/// The default colors for the theme.
///
/// Themes that do not specify all colors are refined off of these defaults.
@@ -189,14 +158,14 @@ impl ThemeColors {
terminal_ansi_dim_cyan: cyan().light().step_10(),
terminal_ansi_dim_white: neutral().light().step_11(),
link_text_hover: orange().light().step_10(),
version_control_added: LIGHT_ADDED_COLOR,
version_control_deleted: LIGHT_REMOVED_COLOR,
version_control_modified: LIGHT_MODIFIED_COLOR,
version_control_renamed: LIGHT_MODIFIED_COLOR,
version_control_added: ADDED_COLOR,
version_control_deleted: REMOVED_COLOR,
version_control_modified: MODIFIED_COLOR,
version_control_renamed: MODIFIED_COLOR,
version_control_conflict: orange().light().step_12(),
version_control_ignored: gray().light().step_12(),
version_control_word_added: LIGHT_WORD_ADDED_COLOR,
version_control_word_deleted: LIGHT_WORD_DELETED_COLOR,
version_control_word_added: WORD_ADDED_COLOR,
version_control_word_deleted: WORD_DELETED_COLOR,
version_control_conflict_marker_ours: green().light().step_10().alpha(0.5),
version_control_conflict_marker_theirs: blue().light().step_10().alpha(0.5),
vim_normal_background: system.transparent,
@@ -326,14 +295,14 @@ impl ThemeColors {
terminal_ansi_bright_white: neutral().dark().step_11(),
terminal_ansi_dim_white: neutral().dark().step_10(),
link_text_hover: orange().dark().step_10(),
version_control_added: DARK_ADDED_COLOR,
version_control_deleted: DARK_REMOVED_COLOR,
version_control_modified: DARK_MODIFIED_COLOR,
version_control_renamed: DARK_MODIFIED_COLOR,
version_control_added: ADDED_COLOR,
version_control_deleted: REMOVED_COLOR,
version_control_modified: MODIFIED_COLOR,
version_control_renamed: MODIFIED_COLOR,
version_control_conflict: orange().dark().step_12(),
version_control_ignored: gray().dark().step_12(),
version_control_word_added: DARK_WORD_ADDED_COLOR,
version_control_word_deleted: DARK_WORD_DELETED_COLOR,
version_control_word_added: WORD_ADDED_COLOR,
version_control_word_deleted: WORD_DELETED_COLOR,
version_control_conflict_marker_ours: green().dark().step_10().alpha(0.5),
version_control_conflict_marker_theirs: blue().dark().step_10().alpha(0.5),
vim_normal_background: system.transparent,

View File

@@ -3,8 +3,7 @@ use std::sync::Arc;
use gpui::{FontStyle, FontWeight, HighlightStyle, Hsla, WindowBackgroundAppearance, hsla};
use crate::{
AccentColors, Appearance, DARK_ADDED_COLOR, DARK_MODIFIED_COLOR, DARK_REMOVED_COLOR,
DARK_WORD_ADDED_COLOR, DARK_WORD_DELETED_COLOR, DEFAULT_DARK_THEME, PlayerColors, StatusColors,
AccentColors, Appearance, DEFAULT_DARK_THEME, PlayerColors, StatusColors,
StatusColorsRefinement, SyntaxTheme, SystemColors, Theme, ThemeColors, ThemeColorsRefinement,
ThemeFamily, ThemeStyles, default_color_scales,
};
@@ -71,6 +70,37 @@ pub(crate) fn zed_default_dark() -> Theme {
let teal = hsla(187. / 360., 47. / 100., 55. / 100., 1.0);
let yellow = hsla(39. / 360., 67. / 100., 69. / 100., 1.0);
const ADDED_COLOR: Hsla = Hsla {
h: 134. / 360.,
s: 0.55,
l: 0.40,
a: 1.0,
};
const WORD_ADDED_COLOR: Hsla = Hsla {
h: 134. / 360.,
s: 0.55,
l: 0.40,
a: 0.35,
};
const MODIFIED_COLOR: Hsla = Hsla {
h: 48. / 360.,
s: 0.76,
l: 0.47,
a: 1.0,
};
const REMOVED_COLOR: Hsla = Hsla {
h: 350. / 360.,
s: 0.88,
l: 0.25,
a: 1.0,
};
const WORD_DELETED_COLOR: Hsla = Hsla {
h: 350. / 360.,
s: 0.88,
l: 0.25,
a: 0.80,
};
let player = PlayerColors::dark();
Theme {
id: "one_dark".to_string(),
@@ -207,14 +237,14 @@ pub(crate) fn zed_default_dark() -> Theme {
minimap_thumb_border: hsla(228. / 360., 8. / 100., 25. / 100., 1.),
editor_foreground: hsla(218. / 360., 14. / 100., 71. / 100., 1.),
link_text_hover: blue,
version_control_added: DARK_ADDED_COLOR,
version_control_deleted: DARK_REMOVED_COLOR,
version_control_modified: DARK_MODIFIED_COLOR,
version_control_renamed: DARK_MODIFIED_COLOR,
version_control_added: ADDED_COLOR,
version_control_deleted: REMOVED_COLOR,
version_control_modified: MODIFIED_COLOR,
version_control_renamed: MODIFIED_COLOR,
version_control_conflict: crate::orange().light().step_12(),
version_control_ignored: crate::gray().light().step_12(),
version_control_word_added: DARK_WORD_ADDED_COLOR,
version_control_word_deleted: DARK_WORD_DELETED_COLOR,
version_control_word_added: WORD_ADDED_COLOR,
version_control_word_deleted: WORD_DELETED_COLOR,
version_control_conflict_marker_ours: crate::green().light().step_12().alpha(0.5),
version_control_conflict_marker_theirs: crate::blue().light().step_12().alpha(0.5),

View File

@@ -287,7 +287,6 @@ pub fn theme_colors_refinement(
.panel_background
.as_ref()
.and_then(|color| try_parse_color(color).ok());
ThemeColorsRefinement {
border,
border_variant: this
@@ -714,7 +713,6 @@ pub fn theme_colors_refinement(
.as_ref()
.and_then(|color| try_parse_color(color).ok())
// Fall back to `created`, for backwards compatibility.
// todo!
.or(status_colors.created),
version_control_deleted: this
.version_control_deleted

View File

@@ -247,10 +247,6 @@ impl ThemeFamily {
};
let mut theme_colors_refinement =
theme_colors_refinement(&theme.style.colors, &status_colors_refinement);
// todo! We could pass in refined_theme_colors to apply default version control colors
// instead of falling back to the status colors created ... etc
//
// That would probably break backwards compatibility though
apply_theme_color_defaults(&mut theme_colors_refinement, &refined_player_colors);
refined_theme_colors.refine(&theme_colors_refinement);

View File

@@ -2724,11 +2724,11 @@ impl Pane {
.map(|this| {
if is_active {
let focus_handle = focus_handle.clone();
this.tooltip(move |window, cx| {
this.tooltip(move |_window, cx| {
Tooltip::for_action_in(
end_slot_tooltip_text,
end_slot_action,
&window.focused(cx).unwrap_or_else(|| focus_handle.clone()),
&focus_handle,
cx,
)
})
@@ -3031,14 +3031,7 @@ impl Pane {
.disabled(!self.can_navigate_backward())
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Go Back",
&GoBack,
&window.focused(cx).unwrap_or_else(|| focus_handle.clone()),
cx,
)
}
move |_window, cx| Tooltip::for_action_in("Go Back", &GoBack, &focus_handle, cx)
});
let navigate_forward = IconButton::new("navigate_forward", IconName::ArrowRight)
@@ -3054,13 +3047,8 @@ impl Pane {
.disabled(!self.can_navigate_forward())
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Go Forward",
&GoForward,
&window.focused(cx).unwrap_or_else(|| focus_handle.clone()),
cx,
)
move |_window, cx| {
Tooltip::for_action_in("Go Forward", &GoForward, &focus_handle, cx)
}
});

View File

@@ -7236,9 +7236,7 @@ actions!(
/// Shares the current project with collaborators.
ShareProject,
/// Shares your screen with collaborators.
ScreenShare,
/// Copies the current room name and session id for debugging purposes.
CopyRoomId,
ScreenShare
]
);
actions!(

View File

@@ -54,8 +54,8 @@ fn main() {
}
if cfg!(target_arch = "x86_64") {
println!("cargo::rerun-if-changed=resources\\windows\\bin\\x64\\conpty.dll");
println!("cargo::rerun-if-changed=resources\\windows\\bin\\x64\\OpenConsole.exe");
println!("cargo::rerun-if-changed=\\..\\..\\..\\conpty.dll");
println!("cargo::rerun-if-changed=\\..\\..\\..\\OpenConsole.exe");
let conpty_target = std::env::var("OUT_DIR").unwrap() + "\\..\\..\\..\\conpty.dll";
match std::fs::copy("resources/windows/bin/x64/conpty.dll", &conpty_target) {
Ok(_) => println!("Copied conpty.dll to {conpty_target}"),

View File

@@ -132,8 +132,12 @@ impl EditPredictionProvider for ZetaEditPredictionProvider {
}
fn discard(&mut self, cx: &mut Context<Self>) {
self.zeta.update(cx, |zeta, _cx| {
zeta.reject_current_prediction(EditPredictionRejectReason::Discarded, &self.project);
self.zeta.update(cx, |zeta, cx| {
zeta.reject_current_prediction(
EditPredictionRejectReason::Discarded,
&self.project,
cx,
);
});
}
@@ -169,10 +173,11 @@ impl EditPredictionProvider for ZetaEditPredictionProvider {
let snapshot = buffer.snapshot();
let Some(edits) = prediction.interpolate(&snapshot) else {
self.zeta.update(cx, |zeta, _cx| {
self.zeta.update(cx, |zeta, cx| {
zeta.reject_current_prediction(
EditPredictionRejectReason::InterpolatedEmpty,
&self.project,
cx,
);
});
return None;

View File

@@ -5,7 +5,7 @@ use cloud_llm_client::predict_edits_v3::{self, Event, PromptFormat, Signature};
use cloud_llm_client::{
AcceptEditPredictionBody, EXPIRED_LLM_TOKEN_HEADER_NAME, EditPredictionRejectReason,
EditPredictionRejection, MAX_EDIT_PREDICTION_REJECTIONS_PER_REQUEST,
MINIMUM_REQUIRED_VERSION_HEADER_NAME, PredictEditsRequestTrigger, RejectEditPredictionsBodyRef,
MINIMUM_REQUIRED_VERSION_HEADER_NAME, PredictEditsRequestTrigger, RejectEditPredictionsBody,
ZED_VERSION_HEADER_NAME,
};
use cloud_zeta2_prompt::retrieval_prompt::{SearchToolInput, SearchToolQuery};
@@ -19,10 +19,8 @@ use edit_prediction_context::{
SyntaxIndex, SyntaxIndexState,
};
use feature_flags::{FeatureFlag, FeatureFlagAppExt as _, PredictEditsRateCompletionsFeatureFlag};
use futures::channel::mpsc::UnboundedReceiver;
use futures::channel::{mpsc, oneshot};
use futures::{AsyncReadExt as _, FutureExt as _, StreamExt as _, select_biased};
use gpui::BackgroundExecutor;
use futures::{AsyncReadExt as _, FutureExt as _, StreamExt as _};
use gpui::{
App, AsyncApp, Entity, EntityId, Global, SharedString, Subscription, Task, WeakEntity, actions,
http_client::{self, AsyncBody, Method},
@@ -102,7 +100,6 @@ actions!(
const EVENT_COUNT_MAX: usize = 6;
const CHANGE_GROUPING_LINE_SPAN: u32 = 8;
const ZED_PREDICT_DATA_COLLECTION_CHOICE: &str = "zed_predict_data_collection_choice";
const REJECT_REQUEST_DEBOUNCE: Duration = Duration::from_secs(15);
pub struct SweepFeatureFlag;
@@ -198,7 +195,9 @@ pub struct Zeta {
edit_prediction_model: ZetaEditPredictionModel,
pub sweep_ai: SweepAi,
data_collection_choice: DataCollectionChoice,
reject_predictions_tx: mpsc::UnboundedSender<EditPredictionRejection>,
rejected_predictions: Vec<EditPredictionRejection>,
reject_predictions_tx: mpsc::UnboundedSender<()>,
reject_predictions_debounce_task: Option<Task<()>>,
shown_predictions: VecDeque<EditPrediction>,
rated_predictions: HashSet<EditPredictionId>,
}
@@ -326,8 +325,13 @@ impl ZetaProject {
return;
};
this.update(cx, |this, _cx| {
this.reject_prediction(prediction_id, EditPredictionRejectReason::Canceled, false);
this.update(cx, |this, cx| {
this.reject_prediction(
prediction_id,
EditPredictionRejectReason::Canceled,
false,
cx,
);
})
.ok();
})
@@ -500,24 +504,14 @@ impl Zeta {
let refresh_llm_token_listener = RefreshLlmTokenListener::global(cx);
let data_collection_choice = Self::load_data_collection_choice();
let llm_token = LlmApiToken::default();
let (reject_tx, reject_rx) = mpsc::unbounded();
cx.background_spawn({
let client = client.clone();
let llm_token = llm_token.clone();
let app_version = AppVersion::global(cx);
let background_executor = cx.background_executor().clone();
async move {
Self::handle_rejected_predictions(
reject_rx,
client,
llm_token,
app_version,
background_executor,
)
.await
let (reject_tx, mut reject_rx) = mpsc::unbounded();
cx.spawn(async move |this, cx| {
while let Some(()) = reject_rx.next().await {
this.update(cx, |this, cx| this.flush_rejected_predictions(cx))?
.await
.log_err();
}
anyhow::Ok(())
})
.detach();
@@ -526,7 +520,7 @@ impl Zeta {
client,
user_store,
options: DEFAULT_OPTIONS,
llm_token,
llm_token: LlmApiToken::default(),
_llm_token_subscription: cx.subscribe(
&refresh_llm_token_listener,
|this, _listener, _event, cx| {
@@ -546,6 +540,8 @@ impl Zeta {
edit_prediction_model: ZetaEditPredictionModel::Zeta2,
sweep_ai: SweepAi::new(cx),
data_collection_choice,
rejected_predictions: Vec::new(),
reject_predictions_debounce_task: None,
reject_predictions_tx: reject_tx,
rated_predictions: Default::default(),
shown_predictions: Default::default(),
@@ -905,73 +901,64 @@ impl Zeta {
.detach_and_log_err(cx);
}
async fn handle_rejected_predictions(
rx: UnboundedReceiver<EditPredictionRejection>,
client: Arc<Client>,
llm_token: LlmApiToken,
app_version: Version,
background_executor: BackgroundExecutor,
) {
let mut rx = std::pin::pin!(rx.peekable());
let mut batched = Vec::new();
fn flush_rejected_predictions(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
match self.edit_prediction_model {
ZetaEditPredictionModel::Zeta1 | ZetaEditPredictionModel::Zeta2 => {}
ZetaEditPredictionModel::Sweep => return Task::ready(anyhow::Ok(())),
}
while let Some(rejection) = rx.next().await {
batched.push(rejection);
let client = self.client.clone();
let llm_token = self.llm_token.clone();
let app_version = AppVersion::global(cx);
let last_rejection = self.rejected_predictions.last().cloned();
let Some(last_rejection) = last_rejection else {
return Task::ready(anyhow::Ok(()));
};
if batched.len() < MAX_EDIT_PREDICTION_REJECTIONS_PER_REQUEST / 2 {
select_biased! {
next = rx.as_mut().peek().fuse() => {
if next.is_some() {
continue;
}
}
() = background_executor.timer(REJECT_REQUEST_DEBOUNCE).fuse() => {},
}
}
let body = serde_json::to_string(&RejectEditPredictionsBody {
rejections: self.rejected_predictions.clone(),
})
.ok();
cx.spawn(async move |this, cx| {
let url = client
.http_client()
.build_zed_llm_url("/predict_edits/reject", &[])
.unwrap();
.build_zed_llm_url("/predict_edits/reject", &[])?;
let flush_count = batched
.len()
// in case items have accumulated after failure
.min(MAX_EDIT_PREDICTION_REJECTIONS_PER_REQUEST);
let start = batched.len() - flush_count;
let body = RejectEditPredictionsBodyRef {
rejections: &batched[start..],
};
let result = Self::send_api_request::<()>(
|builder| {
let req = builder
.uri(url.as_ref())
.body(serde_json::to_string(&body)?.into());
anyhow::Ok(req?)
cx.background_spawn(Self::send_api_request::<()>(
move |builder| {
let req = builder.uri(url.as_ref()).body(body.clone().into());
Ok(req?)
},
client.clone(),
llm_token.clone(),
app_version.clone(),
)
.await;
client,
llm_token,
app_version,
))
.await
.context("Failed to reject edit predictions")?;
if result.log_err().is_some() {
batched.drain(start..);
}
}
this.update(cx, |this, _| {
if let Some(ix) = this
.rejected_predictions
.iter()
.position(|rejection| rejection.request_id == last_rejection.request_id)
{
this.rejected_predictions.drain(..ix + 1);
}
})
})
}
fn reject_current_prediction(
&mut self,
reason: EditPredictionRejectReason,
project: &Entity<Project>,
cx: &mut Context<Self>,
) {
if let Some(project_state) = self.projects.get_mut(&project.entity_id()) {
project_state.pending_predictions.clear();
if let Some(prediction) = project_state.current_prediction.take() {
self.reject_prediction(prediction.prediction.id, reason, prediction.was_shown);
self.reject_prediction(prediction.prediction.id, reason, prediction.was_shown, cx);
}
};
}
@@ -997,14 +984,26 @@ impl Zeta {
prediction_id: EditPredictionId,
reason: EditPredictionRejectReason,
was_shown: bool,
cx: &mut Context<Self>,
) {
self.reject_predictions_tx
.unbounded_send(EditPredictionRejection {
request_id: prediction_id.to_string(),
reason,
was_shown,
})
.log_err();
self.rejected_predictions.push(EditPredictionRejection {
request_id: prediction_id.to_string(),
reason,
was_shown,
});
let reached_request_limit =
self.rejected_predictions.len() >= MAX_EDIT_PREDICTION_REJECTIONS_PER_REQUEST / 2;
let reject_tx = self.reject_predictions_tx.clone();
self.reject_predictions_debounce_task = Some(cx.spawn(async move |_this, cx| {
const REJECT_REQUEST_DEBOUNCE: Duration = Duration::from_secs(15);
if !reached_request_limit {
cx.background_executor()
.timer(REJECT_REQUEST_DEBOUNCE)
.await;
}
reject_tx.unbounded_send(()).log_err();
}));
}
fn is_refreshing(&self, project: &Entity<Project>) -> bool {
@@ -1212,6 +1211,7 @@ impl Zeta {
this.reject_current_prediction(
EditPredictionRejectReason::Replaced,
&project,
cx,
);
Some(new_prediction)
@@ -1220,6 +1220,7 @@ impl Zeta {
new_prediction.prediction.id,
EditPredictionRejectReason::CurrentPreferred,
false,
cx,
);
None
}
@@ -1228,7 +1229,7 @@ impl Zeta {
}
}
Err(reject_reason) => {
this.reject_prediction(prediction_result.id, reject_reason, false);
this.reject_prediction(prediction_result.id, reject_reason, false, cx);
None
}
}
@@ -2905,7 +2906,7 @@ fn feature_gate_predict_edits_actions(cx: &mut App) {
#[cfg(test)]
mod tests {
use std::{path::Path, sync::Arc, time::Duration};
use std::{path::Path, sync::Arc};
use client::UserStore;
use clock::FakeSystemClock;
@@ -2932,7 +2933,7 @@ mod tests {
use util::path;
use uuid::Uuid;
use crate::{BufferEditPrediction, EditPredictionId, REJECT_REQUEST_DEBOUNCE, Zeta};
use crate::{BufferEditPrediction, Zeta};
#[gpui::test]
async fn test_current_state(cx: &mut TestAppContext) {
@@ -3034,8 +3035,8 @@ mod tests {
.unwrap();
refresh_task.await.unwrap();
zeta.update(cx, |zeta, _cx| {
zeta.reject_current_prediction(EditPredictionRejectReason::Discarded, &project);
zeta.update(cx, |zeta, cx| {
zeta.reject_current_prediction(EditPredictionRejectReason::Discarded, &project, cx);
});
// Prediction for another file
@@ -3544,17 +3545,14 @@ mod tests {
let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot());
let position = snapshot.anchor_before(language::Point::new(1, 3));
// start two refresh tasks
zeta.update(cx, |zeta, cx| {
// start two refresh tasks
zeta.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx);
zeta.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx);
});
let (_, respond_first) = requests.predict.next().await.unwrap();
zeta.update(cx, |zeta, cx| {
zeta.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx);
});
let (_, respond_second) = requests.predict.next().await.unwrap();
// wait for throttle
@@ -3633,22 +3631,18 @@ mod tests {
let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot());
let position = snapshot.anchor_before(language::Point::new(1, 3));
// start two refresh tasks
zeta.update(cx, |zeta, cx| {
// start two refresh tasks
zeta.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx);
zeta.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx);
});
let (_, respond_first) = requests.predict.next().await.unwrap();
zeta.update(cx, |zeta, cx| {
zeta.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx);
});
let (_, respond_second) = requests.predict.next().await.unwrap();
// wait for throttle, so requests are sent
cx.run_until_parked();
let (_, respond_first) = requests.predict.next().await.unwrap();
let (_, respond_second) = requests.predict.next().await.unwrap();
zeta.update(cx, |zeta, cx| {
// start a third request
zeta.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx);
@@ -3742,118 +3736,6 @@ mod tests {
);
}
#[gpui::test]
async fn test_rejections_flushing(cx: &mut TestAppContext) {
let (zeta, mut requests) = init_test(cx);
zeta.update(cx, |zeta, _cx| {
zeta.reject_prediction(
EditPredictionId("test-1".into()),
EditPredictionRejectReason::Discarded,
false,
);
zeta.reject_prediction(
EditPredictionId("test-2".into()),
EditPredictionRejectReason::Canceled,
true,
);
});
cx.executor().advance_clock(REJECT_REQUEST_DEBOUNCE);
cx.run_until_parked();
let (reject_request, respond_tx) = requests.reject.next().await.unwrap();
respond_tx.send(()).unwrap();
// batched
assert_eq!(reject_request.rejections.len(), 2);
assert_eq!(
reject_request.rejections[0],
EditPredictionRejection {
request_id: "test-1".to_string(),
reason: EditPredictionRejectReason::Discarded,
was_shown: false
}
);
assert_eq!(
reject_request.rejections[1],
EditPredictionRejection {
request_id: "test-2".to_string(),
reason: EditPredictionRejectReason::Canceled,
was_shown: true
}
);
// Reaching batch size limit sends without debounce
zeta.update(cx, |zeta, _cx| {
for i in 0..70 {
zeta.reject_prediction(
EditPredictionId(format!("batch-{}", i).into()),
EditPredictionRejectReason::Discarded,
false,
);
}
});
// First MAX/2 items are sent immediately
cx.run_until_parked();
let (reject_request, respond_tx) = requests.reject.next().await.unwrap();
respond_tx.send(()).unwrap();
assert_eq!(reject_request.rejections.len(), 50);
assert_eq!(reject_request.rejections[0].request_id, "batch-0");
assert_eq!(reject_request.rejections[49].request_id, "batch-49");
// Remaining items are debounced with the next batch
cx.executor().advance_clock(Duration::from_secs(15));
cx.run_until_parked();
let (reject_request, respond_tx) = requests.reject.next().await.unwrap();
respond_tx.send(()).unwrap();
assert_eq!(reject_request.rejections.len(), 20);
assert_eq!(reject_request.rejections[0].request_id, "batch-50");
assert_eq!(reject_request.rejections[19].request_id, "batch-69");
// Request failure
zeta.update(cx, |zeta, _cx| {
zeta.reject_prediction(
EditPredictionId("retry-1".into()),
EditPredictionRejectReason::Discarded,
false,
);
});
cx.executor().advance_clock(REJECT_REQUEST_DEBOUNCE);
cx.run_until_parked();
let (reject_request, _respond_tx) = requests.reject.next().await.unwrap();
assert_eq!(reject_request.rejections.len(), 1);
assert_eq!(reject_request.rejections[0].request_id, "retry-1");
// Simulate failure
drop(_respond_tx);
// Add another rejection
zeta.update(cx, |zeta, _cx| {
zeta.reject_prediction(
EditPredictionId("retry-2".into()),
EditPredictionRejectReason::Discarded,
false,
);
});
cx.executor().advance_clock(REJECT_REQUEST_DEBOUNCE);
cx.run_until_parked();
// Retry should include both the failed item and the new one
let (reject_request, respond_tx) = requests.reject.next().await.unwrap();
respond_tx.send(()).unwrap();
assert_eq!(reject_request.rejections.len(), 2);
assert_eq!(reject_request.rejections[0].request_id, "retry-1");
assert_eq!(reject_request.rejections[1].request_id, "retry-2");
}
// Skipped until we start including diagnostics in prompt
// #[gpui::test]
// async fn test_request_diagnostics(cx: &mut TestAppContext) {

View File

@@ -2,7 +2,7 @@
set -euo pipefail
CARGO_ABOUT_VERSION="0.8.2"
CARGO_ABOUT_VERSION="0.8"
OUTPUT_FILE="${1:-$(pwd)/assets/licenses.md}"
TEMPLATE_FILE="script/licenses/template.md.hbs"
@@ -28,10 +28,10 @@ echo -n "" >"$OUTPUT_FILE"
} >>"$OUTPUT_FILE"
if ! cargo about --version | grep "cargo-about $CARGO_ABOUT_VERSION" &>/dev/null; then
echo "Installing cargo-about@$CARGO_ABOUT_VERSION..."
cargo install "cargo-about@$CARGO_ABOUT_VERSION"
echo "Installing cargo-about@^$CARGO_ABOUT_VERSION..."
cargo install "cargo-about@^$CARGO_ABOUT_VERSION"
else
echo "cargo-about@$CARGO_ABOUT_VERSION is already installed."
echo "cargo-about@^$CARGO_ABOUT_VERSION is already installed."
fi
echo "Generating cargo licenses"

View File

@@ -2,15 +2,15 @@
set -euo pipefail
CARGO_ABOUT_VERSION="0.8.2"
CARGO_ABOUT_VERSION="0.8"
OUTPUT_FILE="${1:-$(pwd)/assets/licenses.csv}"
TEMPLATE_FILE="script/licenses/template.csv.hbs"
if ! cargo about --version | grep "cargo-about $CARGO_ABOUT_VERSION" 2>&1 > /dev/null; then
echo "Installing cargo-about@$CARGO_ABOUT_VERSION..."
cargo install "cargo-about@$CARGO_ABOUT_VERSION"
echo "Installing cargo-about@^$CARGO_ABOUT_VERSION..."
cargo install "cargo-about@^$CARGO_ABOUT_VERSION"
else
echo "cargo-about@$CARGO_ABOUT_VERSION is already installed."
echo "cargo-about@^$CARGO_ABOUT_VERSION is already installed."
fi
echo "Generating cargo licenses"

View File

@@ -1,4 +1,4 @@
$CARGO_ABOUT_VERSION="0.8.2"
$CARGO_ABOUT_VERSION="0.8"
$outputFile=$args[0] ? $args[0] : "$(Get-Location)/assets/licenses.md"
$templateFile="script/licenses/template.md.hbs"
@@ -14,10 +14,10 @@ New-Item -Path "$outputFile" -ItemType File -Value "" -Force
$versionOutput = cargo about --version
if (-not ($versionOutput -match "cargo-about $CARGO_ABOUT_VERSION")) {
Write-Host "Installing cargo-about@$CARGO_ABOUT_VERSION..."
cargo install "cargo-about@$CARGO_ABOUT_VERSION"
Write-Host "Installing cargo-about@^$CARGO_ABOUT_VERSION..."
cargo install "cargo-about@^$CARGO_ABOUT_VERSION"
} else {
Write-Host "cargo-about@$CARGO_ABOUT_VERSION" is already installed
Write-Host "cargo-about@^$CARGO_ABOUT_VERSION" is already installed
}
Write-Host "Generating cargo licenses"

View File

@@ -226,8 +226,8 @@ fn check_style() -> NamedJob {
named::uses(
"crate-ci",
"typos",
"2d0ce569feab1f8752f1dde43cc2f2aa53236e06",
) // v1.40.0
"80c8a4945eec0f6d464eaf9e65ed98ef085283d1",
) // v1.38.1
.with(("config", "./typos.toml"))
}
named::job(