feat: add `.editor` command (#9)

* feat: add `.editor` command

Use `.editor` to edit/input multiline text

* support paste
pull/10/head
sigoden 1 year ago committed by GitHub
parent 069583098f
commit 5179b9a827
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

320
Cargo.lock generated

@ -24,7 +24,9 @@ dependencies = [
"anyhow",
"bytes",
"clap",
"copypasta",
"crossbeam",
"crossterm 0.26.1",
"dirs",
"eventsource-stream",
"futures-util",
@ -160,6 +162,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "brotli"
version = "3.3.4"
@ -278,6 +286,16 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "clipboard-win"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342"
dependencies = [
"lazy-bytes-cast",
"winapi",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@ -294,6 +312,20 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "copypasta"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133fc8675ee3a4ec9aa513584deda9aa0faeda3586b87f7f0f2ba082c66fb172"
dependencies = [
"clipboard-win",
"objc",
"objc-foundation",
"objc_id",
"smithay-clipboard",
"x11-clipboard",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
@ -363,7 +395,7 @@ dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"memoffset 0.8.0",
"scopeguard",
]
@ -419,6 +451,22 @@ dependencies = [
"winapi",
]
[[package]]
name = "crossterm"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
dependencies = [
"bitflags",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.0"
@ -504,6 +552,21 @@ dependencies = [
"winapi",
]
[[package]]
name = "dlib"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794"
dependencies = [
"libloading",
]
[[package]]
name = "downcast-rs"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "dyn-clone"
version = "1.0.11"
@ -730,6 +793,16 @@ dependencies = [
"slab",
]
[[package]]
name = "gethostname"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "gethostname"
version = "0.4.1"
@ -1063,6 +1136,12 @@ dependencies = [
"arrayvec 0.7.2",
]
[[package]]
name = "lazy-bytes-cast"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b"
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -1081,6 +1160,16 @@ version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "link-cplusplus"
version = "1.0.8"
@ -1115,6 +1204,15 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
dependencies = [
"libc",
]
[[package]]
name = "matchers"
version = "0.1.0"
@ -1136,7 +1234,7 @@ dependencies = [
"clap",
"clap_complete",
"env_proxy",
"gethostname",
"gethostname 0.4.1",
"image",
"libc",
"mime",
@ -1168,6 +1266,15 @@ dependencies = [
"libc",
]
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "memoffset"
version = "0.8.0"
@ -1238,6 +1345,18 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "nix"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
dependencies = [
"bitflags",
"cfg-if",
"libc",
"memoffset 0.6.5",
]
[[package]]
name = "nom"
version = "7.1.3"
@ -1298,6 +1417,35 @@ dependencies = [
"libc",
]
[[package]]
name = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
]
[[package]]
name = "objc-foundation"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
dependencies = [
"block",
"objc",
"objc_id",
]
[[package]]
name = "objc_id"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
dependencies = [
"objc",
]
[[package]]
name = "once_cell"
version = "1.17.1"
@ -1389,6 +1537,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "png"
version = "0.17.7"
@ -1750,6 +1904,12 @@ dependencies = [
"windows-sys 0.42.0",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scoped_threadpool"
version = "0.1.9"
@ -1938,6 +2098,34 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "smithay-client-toolkit"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454"
dependencies = [
"bitflags",
"dlib",
"lazy_static",
"log",
"memmap2",
"nix",
"pkg-config",
"wayland-client",
"wayland-cursor",
"wayland-protocols",
]
[[package]]
name = "smithay-clipboard"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8"
dependencies = [
"smithay-client-toolkit",
"wayland-client",
]
[[package]]
name = "socket2"
version = "0.4.7"
@ -2615,6 +2803,79 @@ dependencies = [
"web-sys",
]
[[package]]
name = "wayland-client"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715"
dependencies = [
"bitflags",
"downcast-rs",
"libc",
"nix",
"scoped-tls",
"wayland-commons",
"wayland-scanner",
"wayland-sys",
]
[[package]]
name = "wayland-commons"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
dependencies = [
"nix",
"once_cell",
"smallvec",
"wayland-sys",
]
[[package]]
name = "wayland-cursor"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
dependencies = [
"nix",
"wayland-client",
"xcursor",
]
[[package]]
name = "wayland-protocols"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6"
dependencies = [
"bitflags",
"wayland-client",
"wayland-commons",
"wayland-scanner",
]
[[package]]
name = "wayland-scanner"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
dependencies = [
"proc-macro2",
"quote",
"xml-rs",
]
[[package]]
name = "wayland-sys"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4"
dependencies = [
"dlib",
"lazy_static",
"pkg-config",
]
[[package]]
name = "web-sys"
version = "0.3.61"
@ -2675,6 +2936,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "winapi-wsapoll"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@ -2786,6 +3056,52 @@ dependencies = [
"winapi",
]
[[package]]
name = "x11-clipboard"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0827f86aa910c4e73329a4f619deabe88ebb4b042370bf023c2d5d8b4eb54695"
dependencies = [
"x11rb",
]
[[package]]
name = "x11rb"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507"
dependencies = [
"gethostname 0.2.3",
"nix",
"winapi",
"winapi-wsapoll",
"x11rb-protocol",
]
[[package]]
name = "x11rb-protocol"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67"
dependencies = [
"nix",
]
[[package]]
name = "xcursor"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7"
dependencies = [
"nom",
]
[[package]]
name = "xml-rs"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
[[package]]
name = "xmlparser"
version = "0.13.5"

@ -30,6 +30,8 @@ tokio = { version = "1.26.0", features = ["full"] }
mdcat = { version = "1.1.0", default_features = false, features =["static"] }
pulldown-cmark = { version = "0.9.2", default-features = false, features = ['simd'] }
crossbeam = "0.8.2"
crossterm = "0.26.1"
copypasta = "0.8.2"
[dependencies.syntect]
version = "5.0.0"

@ -0,0 +1,114 @@
use std::io::{self, Stdout, Write};
use anyhow::{anyhow, Result};
use copypasta::{ClipboardContext, ClipboardProvider};
use crossterm::{
cursor,
event::{self, Event, KeyCode, KeyModifiers},
queue, style,
terminal::{self, disable_raw_mode, enable_raw_mode},
};
pub fn edit() -> Result<String> {
enable_raw_mode()?;
let mut stdout = io::stdout();
let ret = edit_inner(&mut stdout);
// restore terminal
disable_raw_mode()?;
ret
}
fn edit_inner(writer: &mut Stdout) -> Result<String> {
let mut session = Session::new(writer);
loop {
let evt = event::read()?;
if let Event::Key(key) = evt {
match key.code {
KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => {
// quit
return Ok(String::new());
}
KeyCode::Char('d') if key.modifiers == KeyModifiers::CONTROL => {
// submit
return Ok(session.buffer);
}
KeyCode::Char('v') if key.modifiers == KeyModifiers::CONTROL => {
let content = paste()?;
session.push_str(&content)?;
}
KeyCode::Char(c)
if matches!(key.modifiers, KeyModifiers::NONE | KeyModifiers::SHIFT) =>
{
session.push(c)?;
}
KeyCode::Enter => {
session.push('\n')?;
}
_ => {}
}
}
session.flush()?;
}
}
struct Session<'a, T: Write> {
writer: &'a mut T,
buffer: String,
dirty: bool,
}
impl<'a, T: Write> Session<'a, T> {
fn new<'b: 'a>(writer: &'b mut T) -> Self {
Self {
buffer: String::new(),
writer,
dirty: false,
}
}
fn push(&mut self, ch: char) -> io::Result<()> {
if ch == '\n' {
self.new_line()?;
} else {
queue!(self.writer, style::Print(ch))?;
}
self.buffer.push(ch);
self.dirty = true;
Ok(())
}
fn push_str(&mut self, text: &str) -> io::Result<()> {
for line in text.lines() {
if !line.is_empty() {
queue!(self.writer, style::Print(line))?;
}
self.new_line()?;
}
Ok(())
}
fn new_line(&mut self) -> io::Result<()> {
let (_, y) = cursor::position()?;
let (_, h) = terminal::size()?;
if y == h - 1 {
queue!(self.writer, terminal::ScrollUp(1), cursor::MoveTo(0, y))?;
} else {
queue!(self.writer, cursor::MoveToNextLine(1))?;
}
Ok(())
}
fn flush(&mut self) -> io::Result<()> {
if self.dirty {
return self.writer.flush();
}
Ok(())
}
}
fn paste() -> Result<String> {
let mut ctx = ClipboardContext::new().map_err(|err| anyhow!("{err}"))?;
ctx.get_contents().map_err(|err| anyhow!("{err}"))
}

@ -1,6 +1,7 @@
mod cli;
mod client;
mod config;
mod editor;
mod render;
mod repl;

@ -27,7 +27,6 @@ pub fn render_stream(
}
if let Ok(evt) = rx.try_recv() {
match evt {
RenderStreamEvent::Start(_) => {}
RenderStreamEvent::Text(text) => {
buffer.push_str(&text);
if text.contains('\n') {

@ -1,5 +1,6 @@
use crate::client::ChatGptClient;
use crate::config::{Config, Role};
use crate::editor;
use crate::render::{self, MarkdownRender};
use anyhow::{anyhow, Result};
use crossbeam::channel::{unbounded, Sender};
@ -16,10 +17,11 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread::spawn;
const REPL_COMMANDS: [(&str, &str); 7] = [
const REPL_COMMANDS: [(&str, &str); 8] = [
(".clear", "Clear the screen"),
(".clear-history", "Clear the history"),
(".clear-role", "Clear the role status"),
(".editor", "Enter multiline editor"),
(".exit", "Exit the REPL"),
(".help", "Print this help message"),
(".history", "Print the history"),
@ -129,6 +131,15 @@ impl Repl {
handler.handle(ReplCmd::UnsetRole)?;
dump("", 1);
}
".editor" => {
dump(
"// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)",
1,
);
let content = editor::edit()?;
dump("", 1);
handler.handle(ReplCmd::Input(content))?;
}
_ => dump_unknown_command(),
}
} else {
@ -242,7 +253,6 @@ impl ReplCmdHandler {
} else {
ReplyReceiver::new(None)
};
receiver.start(&input);
self.client
.acquire_stream(&input, prompt, &mut receiver, self.ctrlc.clone())?;
Config::save_message(
@ -283,17 +293,6 @@ impl ReplyReceiver {
}
}
fn start(&self, input: &str) {
match self.sender.as_ref() {
Some(tx) => {
let _ = tx.send(RenderStreamEvent::Start(input.to_string()));
}
None => {
dump("", 2);
}
}
}
pub fn text(&mut self, text: &str) {
match self.sender.as_ref() {
Some(tx) => {
@ -319,7 +318,6 @@ impl ReplyReceiver {
}
pub enum RenderStreamEvent {
Start(String),
Text(String),
Done,
}

Loading…
Cancel
Save