multi-line copy/paste, more copy/paste

fix C and D in nimbus romono
This commit is contained in:
stjet
2025-04-26 05:17:06 +00:00
parent 7c6a7d6b6d
commit 724ffbd494
12 changed files with 78 additions and 19 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "ming-wm" name = "ming-wm"
version = "1.0.3" version = "1.1.0"
repository = "https://github.com/stjet/ming-wm" repository = "https://github.com/stjet/ming-wm"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
edition = "2021" edition = "2021"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 B

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 B

After

Width:  |  Height:  |  Size: 534 B

View File

@@ -15,6 +15,8 @@ Since the windows and the window manager are separate binaries, they need some w
The serialization format is in `ming-wm-lib/src/serialize.rs`. Make sure any newlines (`\n`) in strings are removed before/after serializations. When doing IPC, the window manager assumes the response to a query is one line, so if a newline is present, it will fail to parse the response. The serialization format is in `ming-wm-lib/src/serialize.rs`. Make sure any newlines (`\n`) in strings are removed before/after serializations. When doing IPC, the window manager assumes the response to a query is one line, so if a newline is present, it will fail to parse the response.
> In the case of `WindowMessage::Request(WindowManagerRequest::ClipboardCopy(<copy_string>))`, windows should convert any `\n` into `𐘂` when copying to clipboard and vice versa when pasting, in order to allow for multi-line clipboard contents.
## Hello, World! ## Hello, World!
A minimal example using `ming-wm-lib`. A minimal example using `ming-wm-lib`.

View File

@@ -14,6 +14,8 @@ Type to write commands, backspace to delete last character, and enter to run com
Tab completion is supported for the `<dir>` and `<dir / playlist file>` arguments. Tab completion is supported for the `<dir>` and `<dir / playlist file>` arguments.
The copy shortcut will copy the currently playing song's file name, if there is a currently playing song.
## Playlists ## Playlists
Example playlist file: Example playlist file:

View File

@@ -25,7 +25,7 @@ To get sudo to read from stdin, the `-S` option will need to be used (eg, `sudo
## Copy / Paste ## Copy / Paste
This window-like supports the paste [shortcut](../system/shortcuts.md) (`Alt+P`) if in INPUT or STDIN mode. This window-like supports the paste [shortcut](../system/shortcuts.md) (`Alt+P`) if in INPUT or STDIN mode. The copy shortcut will copy the output of the last ran command.
## Notes ## Notes

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "ming-wm-lib" name = "ming-wm-lib"
version = "0.1.7" version = "0.2.0"
repository = "https://github.com/stjet/ming-wm" repository = "https://github.com/stjet/ming-wm"
description = "library for building windows for ming-wm in rust" description = "library for building windows for ming-wm in rust"
readme = "README.md" readme = "README.md"

View File

@@ -58,9 +58,11 @@ pub fn listen(mut window_like: impl WindowLike) {
let arg = &parts.collect::<Vec<&str>>().join(" "); let arg = &parts.collect::<Vec<&str>>().join(" ");
let output = match method { let output = match method {
"handle_message" => { "handle_message" => {
//newlines allowed for ClipboardCopy, but represented by the Linear A char
window_like.handle_message(WindowMessage::deserialize(arg).unwrap()).serialize().to_string() window_like.handle_message(WindowMessage::deserialize(arg).unwrap()).serialize().to_string()
}, },
"draw" => { "draw" => {
//newlines never allowed
window_like.draw(&ThemeInfo::deserialize(arg).unwrap()).serialize().replace("\n", "").to_string() window_like.draw(&ThemeInfo::deserialize(arg).unwrap()).serialize().replace("\n", "").to_string()
}, },
"title" => { "title" => {

View File

@@ -146,7 +146,7 @@ impl Serializable for WindowMessageResponse {
WindowMessageResponse::Request(req) => { WindowMessageResponse::Request(req) => {
let req = match req { let req = match req {
WindowManagerRequest::OpenWindow(name) => format!("OpenWindow/{}", name), WindowManagerRequest::OpenWindow(name) => format!("OpenWindow/{}", name),
WindowManagerRequest::ClipboardCopy(name) => format!("ClipboardCopy/{}", name), WindowManagerRequest::ClipboardCopy(copy_string) => format!("ClipboardCopy/{}", copy_string.replace("\n", "𐘂")), //serialised output must be 1 line
WindowManagerRequest::CloseStartMenu => "CloseStartMenu".to_string(), WindowManagerRequest::CloseStartMenu => "CloseStartMenu".to_string(),
WindowManagerRequest::Unlock => "Unlock".to_string(), WindowManagerRequest::Unlock => "Unlock".to_string(),
WindowManagerRequest::Lock => "Lock".to_string(), WindowManagerRequest::Lock => "Lock".to_string(),
@@ -646,7 +646,7 @@ impl Serializable for WindowMessage {
"FullscreenWindow" => Some(ShortcutType::FullscreenWindow), "FullscreenWindow" => Some(ShortcutType::FullscreenWindow),
"HalfWidthWindow" => Some(ShortcutType::HalfWidthWindow), "HalfWidthWindow" => Some(ShortcutType::HalfWidthWindow),
"ClipboardCopy" => Some(ShortcutType::ClipboardCopy), "ClipboardCopy" => Some(ShortcutType::ClipboardCopy),
"ClipboardPaste" => Some(ShortcutType::ClipboardPaste(get_rest_of_split(&mut parts, Some("/")))), "ClipboardPaste" => Some(ShortcutType::ClipboardPaste(get_rest_of_split(&mut parts, Some("/")).replace("𐘂", "\n"))),
_ => None, _ => None,
}; };
if let Some(shortcut) = shortcut { if let Some(shortcut) = shortcut {

View File

@@ -14,7 +14,7 @@ use mp4ameta;
use metaflac; use metaflac;
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType }; use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse }; use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType };
use ming_wm_lib::framebuffer_types::Dimensions; use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo; use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::utils::{ concat_paths, get_all_files, path_autocomplete, format_seconds, Substring }; use ming_wm_lib::utils::{ concat_paths, get_all_files, path_autocomplete, format_seconds, Substring };
@@ -79,7 +79,6 @@ pub struct AudioPlayer {
impl WindowLike for AudioPlayer { impl WindowLike for AudioPlayer {
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
//
match message { match message {
WindowMessage::Init(dimensions) => { WindowMessage::Init(dimensions) => {
self.dimensions = dimensions; self.dimensions = dimensions;
@@ -116,6 +115,27 @@ impl WindowLike for AudioPlayer {
} }
WindowMessageResponse::JustRedraw WindowMessageResponse::JustRedraw
}, },
WindowMessage::Shortcut(shortcut) => {
match shortcut {
ShortcutType::ClipboardPaste(paste_string) => {
self.command += &paste_string.replace("\n", "");
WindowMessageResponse::JustRedraw
},
ShortcutType::ClipboardCopy => {
let internal_locked = self.internal.lock().unwrap();
let sink_len = internal_locked.sink.len();
if sink_len > 0 {
let queue = &internal_locked.queue;
let current = &queue[queue.len() - sink_len];
let current_name = current.0.file_name().unwrap().to_string_lossy().into_owned();
WindowMessageResponse::Request(WindowManagerRequest::ClipboardCopy(current_name))
} else {
WindowMessageResponse::DoNothing
}
},
_ => WindowMessageResponse::DoNothing,
}
},
_ => { _ => {
WindowMessageResponse::DoNothing WindowMessageResponse::DoNothing
}, },

View File

@@ -457,11 +457,21 @@ impl WindowLike for Malvim {
ShortcutType::ClipboardPaste(copy_string) => { ShortcutType::ClipboardPaste(copy_string) => {
if self.mode == Mode::Insert { if self.mode == Mode::Insert {
let current_file = &mut self.files[self.current_file_index]; let current_file = &mut self.files[self.current_file_index];
for (i, cs) in copy_string.split("\n").enumerate() {
if i == 0 {
//modify current line
let line = &current_file.content[current_file.line_pos]; let line = &current_file.content[current_file.line_pos];
current_file.content[current_file.line_pos] = line.substring(0, current_file.cursor_pos).to_string() + &copy_string + line.substring(current_file.cursor_pos, line.chars().count()); current_file.content[current_file.line_pos] = line.substring(0, current_file.cursor_pos).to_string() + &cs + line.substring(current_file.cursor_pos, line.chars().count());
current_file.cursor_pos += copy_string.len(); current_file.cursor_pos += copy_string.len();
} else {
//insert a new line
current_file.content.insert(current_file.line_pos + 1, cs.to_string());
current_file.line_pos += 1;
current_file.cursor_pos = cs.chars().count();
}
}
self.calc_top_line_pos(); self.calc_top_line_pos();
self.calc_current(); //too over zealous but whatever self.calc_current();
self.files[self.current_file_index].changed = true; self.files[self.current_file_index].changed = true;
WindowMessageResponse::JustRedraw WindowMessageResponse::JustRedraw
} else { } else {

View File

@@ -11,7 +11,7 @@ use std::fmt;
use pty_process::blocking; use pty_process::blocking;
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType }; use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, ShortcutType }; use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType };
use ming_wm_lib::framebuffer_types::Dimensions; use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo; use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::utils::{ concat_paths, path_autocomplete, Substring }; use ming_wm_lib::utils::{ concat_paths, path_autocomplete, Substring };
@@ -46,6 +46,11 @@ fn strip_ansi_escape_codes(line: String) -> String {
new_line new_line
} }
fn bytes_to_string(bytes: Vec<u8>) -> String {
let bytes_len = bytes.len();
String::from_utf8(bytes).unwrap_or("?".repeat(bytes_len))
}
#[derive(Default, PartialEq)] #[derive(Default, PartialEq)]
enum Mode { enum Mode {
#[default] #[default]
@@ -78,6 +83,7 @@ pub struct Terminal {
current_path: String, current_path: String,
running_process: Option<Child>, running_process: Option<Child>,
process_current_line: Vec<u8>, //bytes of line process_current_line: Vec<u8>, //bytes of line
output: String, //current or previous running output of command
pty_outerr_rx: Option<Receiver<u8>>, pty_outerr_rx: Option<Receiver<u8>>,
pty_in_tx: Option<Sender<String>>, pty_in_tx: Option<Sender<String>>,
history: Vec<String>, history: Vec<String>,
@@ -116,6 +122,7 @@ impl WindowLike for Terminal {
self.history_index = None; self.history_index = None;
self.mode = self.process_command(); self.mode = self.process_command();
self.current_input = String::new(); self.current_input = String::new();
self.output = String::new();
} else if key_press.key == '\t' { //tab } else if key_press.key == '\t' { //tab
//autocomplete assuming it's a file system path //autocomplete assuming it's a file system path
//...mostly working //...mostly working
@@ -144,8 +151,10 @@ impl WindowLike for Terminal {
loop { loop {
if let Ok(ci) = self.pty_outerr_rx.as_mut().unwrap().recv_timeout(Duration::from_millis(5)) { if let Ok(ci) = self.pty_outerr_rx.as_mut().unwrap().recv_timeout(Duration::from_millis(5)) {
if char::from(ci) == '\n' { if char::from(ci) == '\n' {
let pcl_len = self.process_current_line.len(); let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()));
self.lines.push(strip_ansi_escape_codes(String::from_utf8(self.process_current_line.clone()).unwrap_or("?".repeat(pcl_len)))); self.output += &append_line;
self.output += "\n";
self.lines.push(append_line);
self.process_current_line = Vec::new(); self.process_current_line = Vec::new();
} else if char::from(ci) == '\r' { } else if char::from(ci) == '\r' {
//for now, ignore //for now, ignore
@@ -166,7 +175,14 @@ impl WindowLike for Terminal {
//process exited //process exited
self.pty_outerr_rx = None; self.pty_outerr_rx = None;
self.mode = Mode::Input; self.mode = Mode::Input;
if self.process_current_line.len() > 0 {
//add to lines
let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()));
self.output += &append_line;
self.lines.push(append_line);
//only need to reset if not empty
self.process_current_line = Vec::new(); self.process_current_line = Vec::new();
}
changed = true; changed = true;
} else { } else {
if key_press.key == 'i' { if key_press.key == 'i' {
@@ -187,8 +203,9 @@ impl WindowLike for Terminal {
} else if key_press.is_enter() { } else if key_press.is_enter() {
let _ = self.pty_in_tx.as_mut().unwrap().send(self.current_stdin_input.clone()); let _ = self.pty_in_tx.as_mut().unwrap().send(self.current_stdin_input.clone());
self.mode = Mode::Running; self.mode = Mode::Running;
let pcl_len = self.process_current_line.len(); let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()) + &self.current_stdin_input);
self.lines.push(strip_ansi_escape_codes(String::from_utf8(self.process_current_line.clone()).unwrap_or("?".repeat(pcl_len))) + &self.current_stdin_input); self.output += &append_line;
self.lines.push(append_line);
self.current_stdin_input = String::new(); self.current_stdin_input = String::new();
self.process_current_line = Vec::new(); self.process_current_line = Vec::new();
} else if key_press.is_backspace() { } else if key_press.is_backspace() {
@@ -210,6 +227,12 @@ impl WindowLike for Terminal {
//kills and running_process is now None //kills and running_process is now None
let _ = self.running_process.take().unwrap().kill(); let _ = self.running_process.take().unwrap().kill();
self.mode = Mode::Input; self.mode = Mode::Input;
if self.process_current_line.len() > 0 {
let append_line = strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()));
self.output += &append_line;
self.lines.push(append_line);
self.process_current_line = Vec::new();
}
WindowMessageResponse::JustRedraw WindowMessageResponse::JustRedraw
} else if self.mode == Mode::Input && (key_press.key == 'p' || key_press.key == 'n') { } else if self.mode == Mode::Input && (key_press.key == 'p' || key_press.key == 'n') {
//only the last command is saved unlike other terminals. good enough for me //only the last command is saved unlike other terminals. good enough for me
@@ -231,6 +254,7 @@ impl WindowLike for Terminal {
}, },
WindowMessage::Shortcut(shortcut) => { WindowMessage::Shortcut(shortcut) => {
match shortcut { match shortcut {
ShortcutType::ClipboardCopy => WindowMessageResponse::Request(WindowManagerRequest::ClipboardCopy(self.output.clone())),
ShortcutType::ClipboardPaste(copy_string) => { ShortcutType::ClipboardPaste(copy_string) => {
if self.mode == Mode::Input || self.mode == Mode::Stdin { if self.mode == Mode::Input || self.mode == Mode::Stdin {
if self.mode == Mode::Input { if self.mode == Mode::Input {
@@ -381,8 +405,7 @@ impl Terminal {
//must_add_current_line will be false //must_add_current_line will be false
"$ ".to_string() + &self.current_input + "" "$ ".to_string() + &self.current_input + ""
} else { } else {
let pcl_len = self.process_current_line.len(); strip_ansi_escape_codes(bytes_to_string(self.process_current_line.clone()) + &self.current_stdin_input.clone() + "")
strip_ansi_escape_codes(String::from_utf8(self.process_current_line.clone()).unwrap_or("?".repeat(pcl_len))) + &self.current_stdin_input.clone() + ""
} }
} else { } else {
self.lines[line_num].clone() self.lines[line_num].clone()