gpui: Improve window.prompt to support ESC with non-English cancel text on macOS (#29538)

Release Notes:

- N/A

----

The before version GPUI used `Cancel` for cancel text, if we use
non-English text (e.g.: "取消" in Chinese), then the press `Esc` to cancel
will not work.

So this PR to change it by use `PromptButton` to instead the `&str`,
then we can use `PromptButton::cancel("取消")` for the `Cancel` button.

Run `cargo run -p gpui --example window` to test.

---

Platform Test:

- [x] macOS
- [x] Windows
- [x] Linux (x11 and Wayland)

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
Jason Lee
2025-05-30 15:26:27 +00:00
committed by GitHub
co-authored by Mikayla Maki Mikayla Maki
parent 1d5d3de85c
commit 047e7eacec
14 changed files with 174 additions and 66 deletions
@@ -29,8 +29,8 @@ use crate::platform::{
use crate::scene::Scene;
use crate::{
AnyWindowHandle, Bounds, Decorations, Globals, GpuSpecs, Modifiers, Output, Pixels,
PlatformDisplay, PlatformInput, Point, PromptLevel, RequestFrameOptions, ResizeEdge,
ScaledPixels, Size, Tiling, WaylandClientStatePtr, WindowAppearance,
PlatformDisplay, PlatformInput, Point, PromptButton, PromptLevel, RequestFrameOptions,
ResizeEdge, ScaledPixels, Size, Tiling, WaylandClientStatePtr, WindowAppearance,
WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations, WindowParams, px,
size,
};
@@ -862,7 +862,7 @@ impl PlatformWindow for WaylandWindow {
_level: PromptLevel,
_msg: &str,
_detail: Option<&str>,
_answers: &[&str],
_answers: &[PromptButton],
) -> Option<Receiver<usize>> {
None
}
+4 -4
View File
@@ -4,9 +4,9 @@ use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
use crate::{
AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs, Modifiers,
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
Point, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size, Tiling,
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind,
WindowParams, X11ClientStatePtr, px, size,
Point, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size,
Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
WindowKind, WindowParams, X11ClientStatePtr, px, size,
};
use blade_graphics as gpu;
@@ -1227,7 +1227,7 @@ impl PlatformWindow for X11Window {
_level: PromptLevel,
_msg: &str,
_detail: Option<&str>,
_answers: &[&str],
_answers: &[PromptButton],
) -> Option<futures::channel::oneshot::Receiver<usize>> {
None
}
+1 -1
View File
@@ -21,7 +21,7 @@ const BACKSPACE_KEY: u16 = 0x7f;
const SPACE_KEY: u16 = b' ' as u16;
const ENTER_KEY: u16 = 0x0d;
const NUMPAD_ENTER_KEY: u16 = 0x03;
const ESCAPE_KEY: u16 = 0x1b;
pub(crate) const ESCAPE_KEY: u16 = 0x1b;
const TAB_KEY: u16 = 0x09;
const SHIFT_TAB_KEY: u16 = 0x19;
+14 -6
View File
@@ -3,8 +3,8 @@ use crate::{
AnyWindowHandle, Bounds, DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor,
KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
PlatformWindow, Point, PromptLevel, RequestFrameOptions, ScaledPixels, Size, Timer,
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowKind, WindowParams,
PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions, ScaledPixels, Size,
Timer, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowKind, WindowParams,
platform::PlatformInputHandler, point, px, size,
};
use block::ConcreteBlock;
@@ -902,7 +902,7 @@ impl PlatformWindow for MacWindow {
level: PromptLevel,
msg: &str,
detail: Option<&str>,
answers: &[&str],
answers: &[PromptButton],
) -> Option<oneshot::Receiver<usize>> {
// macOs applies overrides to modal window buttons after they are added.
// Two most important for this logic are:
@@ -926,7 +926,7 @@ impl PlatformWindow for MacWindow {
.iter()
.enumerate()
.rev()
.find(|(_, label)| **label != "Cancel")
.find(|(_, label)| !label.is_cancel())
.filter(|&(label_index, _)| label_index > 0);
unsafe {
@@ -948,11 +948,19 @@ impl PlatformWindow for MacWindow {
.enumerate()
.filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix))
{
let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer.label())];
let _: () = msg_send![button, setTag: ix as NSInteger];
if answer.is_cancel() {
// Bind Escape Key to Cancel Button
if let Some(key) = std::char::from_u32(super::events::ESCAPE_KEY as u32) {
let _: () =
msg_send![button, setKeyEquivalent: ns_string(&key.to_string())];
}
}
}
if let Some((ix, answer)) = latest_non_cancel_label {
let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer.label())];
let _: () = msg_send![button, setTag: ix as NSInteger];
}
+4 -4
View File
@@ -1,8 +1,8 @@
use crate::{
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels,
ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, PlatformKeyboardLayout,
PlatformTextSystem, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, Size, Task,
TestDisplay, TestWindow, WindowAppearance, WindowParams, size,
PlatformTextSystem, PromptButton, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream,
Size, Task, TestDisplay, TestWindow, WindowAppearance, WindowParams, size,
};
use anyhow::Result;
use collections::VecDeque;
@@ -165,10 +165,10 @@ impl TestPlatform {
&self,
msg: &str,
detail: Option<&str>,
answers: &[&str],
answers: &[PromptButton],
) -> oneshot::Receiver<usize> {
let (tx, rx) = oneshot::channel();
let answers: Vec<String> = answers.iter().map(|&s| s.to_string()).collect();
let answers: Vec<String> = answers.iter().map(|s| s.label().to_string()).collect();
self.background_executor()
.set_waiting_hint(Some(format!("PROMPT: {:?} {:?}", msg, detail)));
self.prompts
+3 -3
View File
@@ -1,8 +1,8 @@
use crate::{
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult, GpuSpecs,
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
Point, RequestFrameOptions, ScaledPixels, Size, TestPlatform, TileId, WindowAppearance,
WindowBackgroundAppearance, WindowBounds, WindowParams,
Point, PromptButton, RequestFrameOptions, ScaledPixels, Size, TestPlatform, TileId,
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowParams,
};
use collections::HashMap;
use parking_lot::Mutex;
@@ -164,7 +164,7 @@ impl PlatformWindow for TestWindow {
_level: crate::PromptLevel,
msg: &str,
detail: Option<&str>,
answers: &[&str],
answers: &[PromptButton],
) -> Option<futures::channel::oneshot::Receiver<usize>> {
Some(
self.0
+5 -5
View File
@@ -608,7 +608,7 @@ impl PlatformWindow for WindowsWindow {
level: PromptLevel,
msg: &str,
detail: Option<&str>,
answers: &[&str],
answers: &[PromptButton],
) -> Option<Receiver<usize>> {
let (done_tx, done_rx) = oneshot::channel();
let msg = msg.to_string();
@@ -616,8 +616,8 @@ impl PlatformWindow for WindowsWindow {
Some(info) => Some(info.to_string()),
None => None,
};
let answers = answers.iter().map(|s| s.to_string()).collect::<Vec<_>>();
let handle = self.0.hwnd;
let answers = answers.to_vec();
self.0
.executor
.spawn(async move {
@@ -653,9 +653,9 @@ impl PlatformWindow for WindowsWindow {
let mut button_id_map = Vec::with_capacity(answers.len());
let mut buttons = Vec::new();
let mut btn_encoded = Vec::new();
for (index, btn_string) in answers.iter().enumerate() {
let encoded = HSTRING::from(btn_string);
let button_id = if btn_string == "Cancel" {
for (index, btn) in answers.iter().enumerate() {
let encoded = HSTRING::from(btn.label().as_ref());
let button_id = if btn.is_cancel() {
IDCANCEL.0
} else {
index as i32 - 100