Clipboard handling needed revision in preperation for Windows Support.
This commit is contained in:
parent
03284a694e
commit
17d513c62f
|
|
@ -22,6 +22,7 @@ serde_json = "1"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
zip = { version = "2", default-features = false, features = ["deflate"] }
|
zip = { version = "2", default-features = false, features = ["deflate"] }
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
|
arboard = "3"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cbindgen = "0.29"
|
cbindgen = "0.29"
|
||||||
|
|
|
||||||
|
|
@ -165,21 +165,6 @@ pub struct InlinePressState {
|
||||||
|
|
||||||
const LONG_PRESS_MS: u128 = 300;
|
const LONG_PRESS_MS: u128 = 300;
|
||||||
|
|
||||||
/// Write `s` to the macOS system clipboard via `pbcopy`. Mirrors the
|
|
||||||
/// implementation in `handle.rs::MacClipboard::write` so the editor can copy
|
|
||||||
/// without threading a clipboard handle through update().
|
|
||||||
fn pbcopy(s: &str) {
|
|
||||||
use std::io::Write;
|
|
||||||
if let Ok(mut child) = std::process::Command::new("pbcopy")
|
|
||||||
.stdin(std::process::Stdio::piped())
|
|
||||||
.spawn()
|
|
||||||
{
|
|
||||||
if let Some(stdin) = child.stdin.as_mut() {
|
|
||||||
let _ = stdin.write_all(s.as_bytes());
|
|
||||||
}
|
|
||||||
let _ = child.wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub const ERROR_PREFIX: &str = "⚠ ";
|
pub const ERROR_PREFIX: &str = "⚠ ";
|
||||||
|
|
||||||
const EVAL_DEBOUNCE_MS: u128 = 300;
|
const EVAL_DEBOUNCE_MS: u128 = 300;
|
||||||
|
|
@ -392,6 +377,11 @@ pub struct EditorState {
|
||||||
/// Whether the gutter line numbers cycle through the rainbow palette
|
/// Whether the gutter line numbers cycle through the rainbow palette
|
||||||
/// based on distance from the cursor. Independent of `line_indicator`.
|
/// based on distance from the cursor. Independent of `line_indicator`.
|
||||||
pub gutter_rainbow: bool,
|
pub gutter_rainbow: bool,
|
||||||
|
|
||||||
|
/// Cross-platform clipboard out-channel. Editor logic writes here;
|
||||||
|
/// the shell drains it after each frame via `viewport_take_clipboard`
|
||||||
|
/// and pushes the text to the system clipboard.
|
||||||
|
pub pending_clipboard: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Per-eval table name→id bookkeeping. `keys` is every alias a table is
|
/// Per-eval table name→id bookkeeping. `keys` is every alias a table is
|
||||||
|
|
@ -504,6 +494,7 @@ impl EditorState {
|
||||||
inline_press: None,
|
inline_press: None,
|
||||||
line_indicator: LineIndicator::On,
|
line_indicator: LineIndicator::On,
|
||||||
gutter_rainbow: true,
|
gutter_rainbow: true,
|
||||||
|
pending_clipboard: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3046,15 +3037,14 @@ impl EditorState {
|
||||||
|
|
||||||
/// Copy `{line} → {value}` to clipboard. Used by both long-press (just
|
/// Copy `{line} → {value}` to clipboard. Used by both long-press (just
|
||||||
/// copy) and double-click (copy then insert template).
|
/// copy) and double-click (copy then insert template).
|
||||||
fn copy_inline_result(&self, block_id: crate::selection::BlockId, after_line: usize) {
|
fn copy_inline_result(&mut self, block_id: crate::selection::BlockId, after_line: usize) {
|
||||||
let value = match self.inline_result_value(block_id, after_line) {
|
let value = match self.inline_result_value(block_id, after_line) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
let line = self.read_line_at(block_id, after_line).unwrap_or_default();
|
let line = self.read_line_at(block_id, after_line).unwrap_or_default();
|
||||||
let trimmed = line.trim_end();
|
let trimmed = line.trim_end();
|
||||||
let clip = format!("{trimmed} {RESULT_PREFIX}{value}");
|
self.pending_clipboard = Some(format!("{trimmed} {RESULT_PREFIX}{value}"));
|
||||||
pbcopy(&clip);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Double-click on a result: copy + drop a `let = value` line two lines
|
/// Double-click on a result: copy + drop a `let = value` line two lines
|
||||||
|
|
|
||||||
|
|
@ -7,67 +7,71 @@ use iced_wgpu::core::renderer::Style;
|
||||||
use iced_wgpu::core::time::Instant;
|
use iced_wgpu::core::time::Instant;
|
||||||
use iced_wgpu::core::{clipboard, keyboard, mouse, window, Color, Event, Font, Pixels, Point, Size, Theme};
|
use iced_wgpu::core::{clipboard, keyboard, mouse, window, Color, Event, Font, Pixels, Point, Size, Theme};
|
||||||
use iced_wgpu::Engine;
|
use iced_wgpu::Engine;
|
||||||
use raw_window_handle::{
|
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
||||||
AppKitDisplayHandle, AppKitWindowHandle, RawDisplayHandle, RawWindowHandle,
|
#[cfg(target_os = "macos")]
|
||||||
};
|
use raw_window_handle::{AppKitDisplayHandle, AppKitWindowHandle};
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
use raw_window_handle::{Win32WindowHandle, WindowsDisplayHandle};
|
||||||
|
|
||||||
use crate::editor::{EditorState, Message, RenderMode};
|
use crate::editor::{EditorState, Message, RenderMode};
|
||||||
use crate::palette;
|
use crate::palette;
|
||||||
use crate::table_block::TableMessage;
|
use crate::table_block::TableMessage;
|
||||||
use crate::ViewportHandle;
|
use crate::ViewportHandle;
|
||||||
|
|
||||||
struct MacClipboard;
|
struct AcordClipboard {
|
||||||
|
board: std::cell::RefCell<arboard::Clipboard>,
|
||||||
|
}
|
||||||
|
|
||||||
impl clipboard::Clipboard for MacClipboard {
|
impl clipboard::Clipboard for AcordClipboard {
|
||||||
fn read(&self, _kind: clipboard::Kind) -> Option<String> {
|
fn read(&self, _kind: clipboard::Kind) -> Option<String> {
|
||||||
// `from_utf8_lossy` rather than strict `from_utf8` — some rich-text
|
// arboard uses NSPasteboard on macOS, Win32 on Windows — no subprocess.
|
||||||
// clipboard sources produce stray bytes when pbpaste coerces their
|
// Line-ending normalisation: web pages and cross-platform apps keep
|
||||||
// format to plain text, and a single bad byte would otherwise drop
|
// `\r\n` in the pasteboard; collapse to `\n` so iced's buffer and
|
||||||
// the whole paste.
|
// our gutter line counter agree.
|
||||||
//
|
self.board.borrow_mut()
|
||||||
// Line-ending normalisation: web pages, Discord, and some cross-
|
.get_text()
|
||||||
// platform apps keep `\r\n` in the pasteboard. Iced's buffer and
|
|
||||||
// our own gutter line counter disagree about whether `\r` is its
|
|
||||||
// own row, which drifts the gutter against the cursor on every
|
|
||||||
// paste. Collapse every CR to LF before handing the text upward.
|
|
||||||
std::process::Command::new("pbpaste")
|
|
||||||
.output()
|
|
||||||
.ok()
|
.ok()
|
||||||
.map(|o| {
|
.map(|s| s.replace("\r\n", "\n").replace('\r', "\n"))
|
||||||
let s = String::from_utf8_lossy(&o.stdout).into_owned();
|
|
||||||
s.replace("\r\n", "\n").replace('\r', "\n")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, _kind: clipboard::Kind, contents: String) {
|
fn write(&mut self, _kind: clipboard::Kind, contents: String) {
|
||||||
use std::io::Write;
|
let _ = self.board.borrow_mut().set_text(contents);
|
||||||
if let Ok(mut child) = std::process::Command::new("pbcopy")
|
|
||||||
.stdin(std::process::Stdio::piped())
|
|
||||||
.spawn()
|
|
||||||
{
|
|
||||||
if let Some(stdin) = child.stdin.as_mut() {
|
|
||||||
let _ = stdin.write_all(contents.as_bytes());
|
|
||||||
}
|
|
||||||
let _ = child.wait();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(
|
pub fn create(
|
||||||
nsview: *mut c_void,
|
native_handle: *mut c_void,
|
||||||
width: f32,
|
width: f32,
|
||||||
height: f32,
|
height: f32,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
) -> Option<ViewportHandle> {
|
) -> Option<ViewportHandle> {
|
||||||
let ptr = NonNull::new(nsview)?;
|
let ptr = NonNull::new(native_handle)?;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let backends = wgpu::Backends::METAL;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let backends = wgpu::Backends::DX12;
|
||||||
|
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
|
||||||
|
let backends = wgpu::Backends::VULKAN;
|
||||||
|
|
||||||
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
||||||
backends: wgpu::Backends::METAL,
|
backends,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
let raw_window = RawWindowHandle::AppKit(AppKitWindowHandle::new(ptr));
|
#[cfg(target_os = "macos")]
|
||||||
let raw_display = RawDisplayHandle::AppKit(AppKitDisplayHandle::new());
|
let (raw_window, raw_display) = (
|
||||||
|
RawWindowHandle::AppKit(AppKitWindowHandle::new(ptr)),
|
||||||
|
RawDisplayHandle::AppKit(AppKitDisplayHandle::new()),
|
||||||
|
);
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let (raw_window, raw_display) = {
|
||||||
|
let mut wh = Win32WindowHandle::new(std::num::NonZero::new(ptr.as_ptr() as isize).unwrap());
|
||||||
|
(
|
||||||
|
RawWindowHandle::Win32(wh),
|
||||||
|
RawDisplayHandle::Windows(WindowsDisplayHandle::new()),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let target = wgpu::SurfaceTargetUnsafe::RawHandle {
|
let target = wgpu::SurfaceTargetUnsafe::RawHandle {
|
||||||
raw_display_handle: raw_display,
|
raw_display_handle: raw_display,
|
||||||
|
|
@ -181,7 +185,9 @@ pub fn render(handle: &mut ViewportHandle) {
|
||||||
&mut handle.renderer,
|
&mut handle.renderer,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut clipboard = MacClipboard;
|
let mut clipboard = AcordClipboard {
|
||||||
|
board: std::cell::RefCell::new(arboard::Clipboard::new().unwrap()),
|
||||||
|
};
|
||||||
let mut messages: Vec<Message> = Vec::new();
|
let mut messages: Vec<Message> = Vec::new();
|
||||||
let mut consumed: Vec<usize> = Vec::new();
|
let mut consumed: Vec<usize> = Vec::new();
|
||||||
// Captured during the event scan, applied to `handle.state.mods` AFTER
|
// Captured during the event scan, applied to `handle.state.mods` AFTER
|
||||||
|
|
@ -627,6 +633,13 @@ pub fn render(handle: &mut ViewportHandle) {
|
||||||
handle.state.update(msg);
|
handle.state.update(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drain any clipboard write the editor queued during update/tick.
|
||||||
|
if let Some(text) = handle.state.pending_clipboard.take() {
|
||||||
|
if let Ok(mut board) = arboard::Clipboard::new() {
|
||||||
|
let _ = board.set_text(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handle.state.tick();
|
handle.state.tick();
|
||||||
let pending_focus = handle.state.take_pending_focus();
|
let pending_focus = handle.state.take_pending_focus();
|
||||||
// Drain BEFORE the second `ui` is built — `view()` re-borrows state and
|
// Drain BEFORE the second `ui` is built — `view()` re-borrows state and
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue