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"
|
||||
zip = { version = "2", default-features = false, features = ["deflate"] }
|
||||
base64 = "0.22"
|
||||
arboard = "3"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.29"
|
||||
|
|
|
|||
|
|
@ -165,21 +165,6 @@ pub struct InlinePressState {
|
|||
|
||||
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 = "⚠ ";
|
||||
|
||||
const EVAL_DEBOUNCE_MS: u128 = 300;
|
||||
|
|
@ -392,6 +377,11 @@ pub struct EditorState {
|
|||
/// Whether the gutter line numbers cycle through the rainbow palette
|
||||
/// based on distance from the cursor. Independent of `line_indicator`.
|
||||
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
|
||||
|
|
@ -504,6 +494,7 @@ impl EditorState {
|
|||
inline_press: None,
|
||||
line_indicator: LineIndicator::On,
|
||||
gutter_rainbow: true,
|
||||
pending_clipboard: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3046,15 +3037,14 @@ impl EditorState {
|
|||
|
||||
/// Copy `{line} → {value}` to clipboard. Used by both long-press (just
|
||||
/// 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) {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
let line = self.read_line_at(block_id, after_line).unwrap_or_default();
|
||||
let trimmed = line.trim_end();
|
||||
let clip = format!("{trimmed} {RESULT_PREFIX}{value}");
|
||||
pbcopy(&clip);
|
||||
self.pending_clipboard = Some(format!("{trimmed} {RESULT_PREFIX}{value}"));
|
||||
}
|
||||
|
||||
/// 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::{clipboard, keyboard, mouse, window, Color, Event, Font, Pixels, Point, Size, Theme};
|
||||
use iced_wgpu::Engine;
|
||||
use raw_window_handle::{
|
||||
AppKitDisplayHandle, AppKitWindowHandle, RawDisplayHandle, RawWindowHandle,
|
||||
};
|
||||
use raw_window_handle::{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::palette;
|
||||
use crate::table_block::TableMessage;
|
||||
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> {
|
||||
// `from_utf8_lossy` rather than strict `from_utf8` — some rich-text
|
||||
// clipboard sources produce stray bytes when pbpaste coerces their
|
||||
// format to plain text, and a single bad byte would otherwise drop
|
||||
// the whole paste.
|
||||
//
|
||||
// Line-ending normalisation: web pages, Discord, and some cross-
|
||||
// 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()
|
||||
// arboard uses NSPasteboard on macOS, Win32 on Windows — no subprocess.
|
||||
// Line-ending normalisation: web pages and cross-platform apps keep
|
||||
// `\r\n` in the pasteboard; collapse to `\n` so iced's buffer and
|
||||
// our gutter line counter agree.
|
||||
self.board.borrow_mut()
|
||||
.get_text()
|
||||
.ok()
|
||||
.map(|o| {
|
||||
let s = String::from_utf8_lossy(&o.stdout).into_owned();
|
||||
s.replace("\r\n", "\n").replace('\r', "\n")
|
||||
})
|
||||
.map(|s| s.replace("\r\n", "\n").replace('\r', "\n"))
|
||||
}
|
||||
|
||||
fn write(&mut self, _kind: clipboard::Kind, contents: String) {
|
||||
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(contents.as_bytes());
|
||||
}
|
||||
let _ = child.wait();
|
||||
}
|
||||
let _ = self.board.borrow_mut().set_text(contents);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
nsview: *mut c_void,
|
||||
native_handle: *mut c_void,
|
||||
width: f32,
|
||||
height: f32,
|
||||
scale: f32,
|
||||
) -> 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 {
|
||||
backends: wgpu::Backends::METAL,
|
||||
backends,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let raw_window = RawWindowHandle::AppKit(AppKitWindowHandle::new(ptr));
|
||||
let raw_display = RawDisplayHandle::AppKit(AppKitDisplayHandle::new());
|
||||
#[cfg(target_os = "macos")]
|
||||
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 {
|
||||
raw_display_handle: raw_display,
|
||||
|
|
@ -181,7 +185,9 @@ pub fn render(handle: &mut ViewportHandle) {
|
|||
&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 consumed: Vec<usize> = Vec::new();
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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();
|
||||
let pending_focus = handle.state.take_pending_focus();
|
||||
// Drain BEFORE the second `ui` is built — `view()` re-borrows state and
|
||||
|
|
|
|||
Loading…
Reference in New Issue