mirror of
https://github.com/helix-editor/termina.git
synced 2025-10-06 00:22:43 +02:00
Add credit comments pointing back to original code
This commit is contained in:
@@ -9,7 +9,7 @@ Termina only "speaks text/VT" but aims to work on Windows as well as *NIX. This
|
||||
|
||||
API-wise Termina aims to merge the nicer parts of crossterm and termwiz. In particular termwiz's `Drop` behavior for restoring the terminal is a good idea while crossterm's `EventStream` is nice to work with. Termina tries to have a lower level API when it comes to escape sequences, however, which should make it easier to add new sequences in the future.
|
||||
|
||||
Termina uses significant chunks of code from both crossterm and termwiz and as such the license may be MIT (or MPL-2.0, at your option). (TODO: add annotations for credit.)
|
||||
Termina uses significant chunks of code from both crossterm and termwiz and as such the license may be MIT (or MPL-2.0, at your option).
|
||||
|
||||
Currently Crossterm does not support reading VT sequences on Windows while Termwiz does. Termina will bail if the host terminal does not support VT. Note that there are some places where Termina reaches into the Windows Console API. These match Microsoft's recommendation for [exceptions for using Windows Console APIs](https://learn.microsoft.com/en-us/windows/console/classic-vs-vt#exceptions-for-using-windows-console-apis).
|
||||
|
||||
|
@@ -1,3 +1,6 @@
|
||||
// CREDIT: This module is mostly based on crossterm's `event-read` example with minor
|
||||
// modifications to adapt to the termina API.
|
||||
// <https://github.com/crossterm-rs/crossterm/blob/36d95b26a26e64b0f8c12edfe11f410a6d56a812/examples/event-read.rs>
|
||||
use std::{
|
||||
io::{self, Write as _},
|
||||
time::Duration,
|
||||
|
@@ -1,9 +1,14 @@
|
||||
// CREDIT:
|
||||
// A minimal base64 implementation to keep from pulling in a crate for just that. It's based on
|
||||
// https://github.com/marshallpierce/rust-base64 but without all the customization options.
|
||||
// The biggest portion comes from
|
||||
// https://github.com/marshallpierce/rust-base64/blob/a675443d327e175f735a37f574de803d6a332591/src/engine/naive.rs#L42
|
||||
// Thanks, rust-base64!
|
||||
|
||||
// CREDIT: this was yanked from the Helix codebase:
|
||||
// <https://github.com/helix-editor/helix/blob/4130b162a7bbc7de739807abc05a0e8ba3712133/helix-view/src/base64.rs>
|
||||
// Also see <https://github.com/helix-editor/helix/pull/3220>.
|
||||
|
||||
// The MIT License (MIT)
|
||||
|
||||
// Copyright (c) 2015 Alice Maz
|
||||
|
@@ -1,11 +1,65 @@
|
||||
//! ANSI escape sequences.
|
||||
|
||||
// CREDIT: this tree of modules is mostly yanked from the equivalents in TermWiz with some
|
||||
// stylistic edits and additions/subtractions of some escape sequences.
|
||||
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
num::NonZeroU16,
|
||||
};
|
||||
|
||||
pub mod csi;
|
||||
pub mod dcs;
|
||||
pub mod osc;
|
||||
|
||||
// Originally yanked from <https://github.com/wezterm/wezterm/blob/a87358516004a652ad840bc1661bdf65ffc89b43/term/src/lib.rs#L131-L135>
|
||||
pub const CSI: &str = "\x1b[";
|
||||
pub const DCS: &str = "\x1bP";
|
||||
pub const ST: &str = "\x1b\\";
|
||||
pub const OSC: &str = "\x1b]";
|
||||
pub const ST: &str = "\x1b\\";
|
||||
pub const SS3: &str = "\x1bO";
|
||||
pub const DCS: &str = "\x1bP";
|
||||
|
||||
/// A helper type which avoids tripping over Unix terminal's one-indexed conventions.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
// CREDIT: <https://github.com/wezterm/wezterm/blob/a87358516004a652ad840bc1661bdf65ffc89b43/termwiz/src/escape/mod.rs#L527-L588>.
|
||||
// This can be seen as a reimplementation on top of NonZeroU16.
|
||||
pub struct OneBased(NonZeroU16);
|
||||
|
||||
impl OneBased {
|
||||
pub const fn new(n: u16) -> Option<Self> {
|
||||
match NonZeroU16::new(n) {
|
||||
Some(n) => Some(Self(n)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_zero_based(n: u16) -> Self {
|
||||
Self(unsafe { NonZeroU16::new_unchecked(n + 1) })
|
||||
}
|
||||
|
||||
pub const fn get(self) -> u16 {
|
||||
self.0.get()
|
||||
}
|
||||
|
||||
pub const fn get_zero_based(self) -> u16 {
|
||||
self.get() - 1
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OneBased {
|
||||
fn default() -> Self {
|
||||
Self(unsafe { NonZeroU16::new_unchecked(1) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for OneBased {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonZeroU16> for OneBased {
|
||||
fn from(n: NonZeroU16) -> Self {
|
||||
Self(n)
|
||||
}
|
||||
}
|
||||
|
@@ -1,23 +1,17 @@
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
num::NonZeroU16,
|
||||
};
|
||||
// CREDIT: This module was incrementally yanked from
|
||||
// <https://github.com/wezterm/wezterm/blob/a87358516004a652ad840bc1661bdf65ffc89b43/termwiz/src/escape/osc.rs>.
|
||||
// I've made stylistic changes like eliminating some traits (FromPrimitive, ToPrimitive) and only
|
||||
// copied parts - TermWiz has a more complete set of OSC escapes. I have added some new escapes
|
||||
// though, for example Contour's theme mode extension in `Mode::QueryTheme` and friends.
|
||||
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
use crate::{
|
||||
escape::OneBased,
|
||||
event::Modifiers,
|
||||
style::{Blink, ColorSpec, CursorStyle, Font, Intensity, RgbaColor, Underline, VerticalAlign},
|
||||
};
|
||||
|
||||
// TODO: keep these consts? Or just document them?
|
||||
|
||||
pub const ENTER_ALTERNATE_SCREEN: Csi = Csi::Mode(Mode::SetDecPrivateMode(DecPrivateMode::Code(
|
||||
DecPrivateModeCode::ClearAndEnableAlternateScreen,
|
||||
)));
|
||||
|
||||
pub const EXIT_ALTERNATE_SCREEN: Csi = Csi::Mode(Mode::ResetDecPrivateMode(DecPrivateMode::Code(
|
||||
DecPrivateModeCode::ClearAndEnableAlternateScreen,
|
||||
)));
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Csi {
|
||||
/// "Select Graphics Rendition" (SGR).
|
||||
@@ -406,48 +400,6 @@ impl Display for TabulationClear {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct OneBased(NonZeroU16);
|
||||
|
||||
impl OneBased {
|
||||
pub const fn new(n: u16) -> Option<Self> {
|
||||
match NonZeroU16::new(n) {
|
||||
Some(n) => Some(Self(n)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_zero_based(n: u16) -> Self {
|
||||
Self(unsafe { NonZeroU16::new_unchecked(n + 1) })
|
||||
}
|
||||
|
||||
pub const fn get(self) -> u16 {
|
||||
self.0.get()
|
||||
}
|
||||
|
||||
pub const fn get_zero_based(self) -> u16 {
|
||||
self.get() - 1
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OneBased {
|
||||
fn default() -> Self {
|
||||
Self(unsafe { NonZeroU16::new_unchecked(1) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for OneBased {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonZeroU16> for OneBased {
|
||||
fn from(n: NonZeroU16) -> Self {
|
||||
Self(n)
|
||||
}
|
||||
}
|
||||
|
||||
// Edit
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -1078,6 +1030,15 @@ impl Display for Device {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
const ENTER_ALTERNATE_SCREEN: Csi = Csi::Mode(Mode::SetDecPrivateMode(DecPrivateMode::Code(
|
||||
DecPrivateModeCode::ClearAndEnableAlternateScreen,
|
||||
)));
|
||||
|
||||
const EXIT_ALTERNATE_SCREEN: Csi = Csi::Mode(Mode::ResetDecPrivateMode(DecPrivateMode::Code(
|
||||
DecPrivateModeCode::ClearAndEnableAlternateScreen,
|
||||
)));
|
||||
|
||||
#[test]
|
||||
fn encoding() {
|
||||
// Enter the alternate screen using the mode part of CSI.
|
||||
|
@@ -6,7 +6,7 @@ use crate::style::CursorStyle;
|
||||
pub enum Dcs {
|
||||
// DECRQSS: <https://vt100.net/docs/vt510-rm/DECRQSS.html>
|
||||
Request(DcsRequest),
|
||||
// DECRPSS
|
||||
// DECRPSS: <https://vt100.net/docs/vt510-rm/DECRPSS.html>
|
||||
Response {
|
||||
is_request_valid: bool,
|
||||
value: DcsResponse,
|
||||
@@ -16,7 +16,7 @@ pub enum Dcs {
|
||||
impl Display for Dcs {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// DCS
|
||||
write!(f, "\x1bP")?;
|
||||
f.write_str(super::DCS)?;
|
||||
match self {
|
||||
// DCS $ q D...D ST
|
||||
Self::Request(request) => write!(f, "$q{request}")?,
|
||||
@@ -27,7 +27,7 @@ impl Display for Dcs {
|
||||
} => write!(f, "{}$r{value}", if *is_request_valid { 1 } else { 0 })?,
|
||||
}
|
||||
// ST
|
||||
write!(f, "\x1b\\")
|
||||
f.write_str(super::ST)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,3 +1,7 @@
|
||||
// CREDIT: this is a quite shallow copy of <https://github.com/wezterm/wezterm/blob/a87358516004a652ad840bc1661bdf65ffc89b43/termwiz/src/escape/osc.rs>.
|
||||
// I've replaced some macros and the base64 implementation however, as well as make the commands
|
||||
// borrow a `str` instead of own a `String`.
|
||||
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
use crate::base64;
|
||||
|
@@ -1,3 +1,7 @@
|
||||
// CREDIT: Most event code is adapted from crossterm. The main difference is that I include escape
|
||||
// sequences like CSI and DCS in the `Event` struct and do not make a distinction between
|
||||
// `InternalEvent` and `Event`. Otherwise all `KeyEvent` code is nearly identical to crossterm.
|
||||
|
||||
use crate::escape::{csi::Csi, dcs::Dcs};
|
||||
|
||||
pub(crate) mod reader;
|
||||
@@ -23,7 +27,6 @@ pub enum Event {
|
||||
/// sequence to deliver the entire pasted content.
|
||||
Paste(String),
|
||||
/// A parsed escape sequence starting with CSI (control sequence introducer).
|
||||
// TODO: generic `Escape` event?
|
||||
Csi(Csi),
|
||||
Dcs(Dcs),
|
||||
}
|
||||
@@ -35,6 +38,7 @@ impl Event {
|
||||
}
|
||||
}
|
||||
|
||||
// CREDIT: <https://github.com/crossterm-rs/crossterm/blob/36d95b26a26e64b0f8c12edfe11f410a6d56a812/src/event.rs#L777-L1158>
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct KeyEvent {
|
||||
pub code: KeyCode,
|
||||
|
@@ -1,3 +1,9 @@
|
||||
// CREDIT: <https://github.com/crossterm-rs/crossterm/blob/36d95b26a26e64b0f8c12edfe11f410a6d56a812/src/event/read.rs>
|
||||
// This module provides an `Arc<Mutex<T>>` wrapper around a type which is basically the crossterm
|
||||
// `InternalEventReader`. This allows it to live on the Terminal and an EventStream rather than
|
||||
// statically.
|
||||
// Instead of crossterm's `Filter` trait I have opted for a `Fn(&Event) -> bool` for simplicity.
|
||||
|
||||
use std::{collections::VecDeque, io, sync::Arc, time::Duration};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
@@ -20,12 +20,14 @@ pub(crate) type PlatformWaker = UnixWaker;
|
||||
#[cfg(windows)]
|
||||
pub(crate) type PlatformWaker = WindowsWaker;
|
||||
|
||||
// CREDIT: <https://github.com/crossterm-rs/crossterm/blob/36d95b26a26e64b0f8c12edfe11f410a6d56a812/src/event/source.rs#L12-L27>
|
||||
pub(crate) trait EventSource: Send + Sync {
|
||||
fn try_read(&mut self, timeout: Option<Duration>) -> std::io::Result<Option<crate::Event>>;
|
||||
|
||||
fn waker(&self) -> PlatformWaker;
|
||||
}
|
||||
|
||||
// CREDIT: <https://github.com/crossterm-rs/crossterm/blob/36d95b26a26e64b0f8c12edfe11f410a6d56a812/src/event/timeout.rs#L5-L40>
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PollTimeout {
|
||||
timeout: Option<Duration>,
|
||||
|
@@ -1,3 +1,8 @@
|
||||
// CREDIT: This is mostly a mirror of crossterm `tty` event source adjusted to use rustix
|
||||
// exclusively, reaching into parts of the `filedescriptor` dependency (NOTE: which is part of the
|
||||
// WezTerm repo) but reimplementing with rustix instead of libc.
|
||||
// Crossterm: <https://github.com/crossterm-rs/crossterm/blob/36d95b26a26e64b0f8c12edfe11f410a6d56a812/src/event/source/unix/tty.rs>
|
||||
// Termwiz: <https://github.com/wezterm/wezterm/blob/a87358516004a652ad840bc1661bdf65ffc89b43/filedescriptor/src/unix.rs#L444-L584>
|
||||
use std::{
|
||||
io::{self, Read, Write as _},
|
||||
os::{
|
||||
|
@@ -1,3 +1,8 @@
|
||||
// CREDIT: This one is shared between crossterm and termwiz but is mostly termwiz.
|
||||
// Termwiz: <https://github.com/wezterm/wezterm/blame/a87358516004a652ad840bc1661bdf65ffc89b43/termwiz/src/terminal/windows.rs#L810-L853>
|
||||
// Crossterm: <https://github.com/crossterm-rs/crossterm/blob/36d95b26a26e64b0f8c12edfe11f410a6d56a812/src/event/source/windows.rs>
|
||||
// Also see the necessary methods on the handle from the terminal module and the credit comment
|
||||
// there.
|
||||
use std::{io, os::windows::prelude::*, ptr, sync::Arc, time::Duration};
|
||||
|
||||
use windows_sys::Win32::System::Threading;
|
||||
|
@@ -1,3 +1,7 @@
|
||||
// CREDIT: This is basically all crossterm.
|
||||
// <https://github.com/crossterm-rs/crossterm/blob/36d95b26a26e64b0f8c12edfe11f410a6d56a812/src/event/stream.rs>
|
||||
// I added the dummy stream for integration testing in Helix.
|
||||
|
||||
use std::{
|
||||
io,
|
||||
pin::Pin,
|
||||
|
@@ -1 +0,0 @@
|
||||
|
16
src/parse.rs
16
src/parse.rs
@@ -1,3 +1,14 @@
|
||||
// CREDIT: This is nearly all crossterm (with modifications and additions).
|
||||
// <https://github.com/crossterm-rs/crossterm/blob/36d95b26a26e64b0f8c12edfe11f410a6d56a812/src/event/sys/unix/parse.rs>
|
||||
// See a below credit comment about the `decode_input_records` function however.
|
||||
// I have extended the parsing functions from
|
||||
//
|
||||
// Crossterm comments say that the parser is a bit scary and probably in need of a refactor. I
|
||||
// like this approach though since it's quite easy to read and test. I'm unsure of the performance
|
||||
// though because of the loop in `process_bytes`: we consider the bytes as an increasing slice of
|
||||
// the buffer until it becomes valid or invalid. WezTerm and Alacritty have more formal parsers
|
||||
// (`vtparse` and `vte`, respectively) but I'm unsure of using a terminal program's parser since
|
||||
// it may be larger or more complex than an application needs.
|
||||
use std::{collections::VecDeque, num::NonZeroU16, str};
|
||||
|
||||
use crate::{
|
||||
@@ -71,6 +82,11 @@ impl Parser {
|
||||
}
|
||||
}
|
||||
|
||||
// CREDIT: <https://github.com/wezterm/wezterm/blob/a87358516004a652ad840bc1661bdf65ffc89b43/termwiz/src/input.rs#L676-L885>
|
||||
// I have dropped the legacy Console API handling however and switched to the `AsciiChar` part of
|
||||
// the key record. I suspect that Termwiz may be incorrect here as the Microsoft docs say that the
|
||||
// proper way to read UTF-8 is to use the `A` variant (`ReadConsoleInputA` while WezTerm uses
|
||||
// `ReadConsoleInputW`) to read a byte.
|
||||
#[cfg(windows)]
|
||||
mod windows {
|
||||
use windows_sys::Win32::System::Console;
|
||||
|
@@ -1,5 +1,9 @@
|
||||
//! Types for styling terminal cells.
|
||||
|
||||
// CREDIT: This is shared almost fairly between crossterm and termwiz. SGR properties like
|
||||
// `Underline`, `CursorStyle` and `Intensity` are from termwiz. The `StyleExt` trait is similar
|
||||
// to a crossterm trait.
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::{self, Display},
|
||||
|
@@ -22,6 +22,13 @@ pub type PlatformTerminal = UnixTerminal;
|
||||
#[cfg(windows)]
|
||||
pub type PlatformTerminal = WindowsTerminal;
|
||||
|
||||
// CREDIT: This is heavily based on termwiz.
|
||||
// <https://github.com/wezterm/wezterm/blob/a87358516004a652ad840bc1661bdf65ffc89b43/termwiz/src/terminal/mod.rs#L50-L111>
|
||||
// This trait is simpler, however, and the terminals themselves do not have drop glue or try
|
||||
// to enable features like bracketed paste: that is left to dependents of `termina`. The `poll`
|
||||
// and `read` functions mirror <https://github.com/crossterm-rs/crossterm/blob/36d95b26a26e64b0f8c12edfe11f410a6d56a812/src/event.rs#L204-L255>.
|
||||
// Also see `src/event/reader.rs`.
|
||||
|
||||
pub trait Terminal: io::Write {
|
||||
/// Enters the "raw" terminal mode.
|
||||
///
|
||||
|
@@ -14,6 +14,10 @@ use super::Terminal;
|
||||
|
||||
const BUF_SIZE: usize = 4096;
|
||||
|
||||
// CREDIT: FileDescriptor stuff is mostly based on the WezTerm crate `filedescriptor` but has been
|
||||
// rewritten with `rustix` instead of `libc`.
|
||||
// <https://github.com/wezterm/wezterm/blob/a87358516004a652ad840bc1661bdf65ffc89b43/filedescriptor/src/unix.rs>
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum FileDescriptor {
|
||||
Owned(OwnedFd),
|
||||
@@ -80,6 +84,15 @@ fn open_pty() -> io::Result<(FileDescriptor, FileDescriptor)> {
|
||||
Ok((read, write))
|
||||
}
|
||||
|
||||
// CREDIT: <https://github.com/wezterm/wezterm/blob/a87358516004a652ad840bc1661bdf65ffc89b43/termwiz/src/terminal/unix.rs>
|
||||
// Some discussion, though: Termwiz's terminals combine the terminal interaction (reading
|
||||
// dimensions, reading events, writing bytes, etc.) all in one type. Crossterm splits these
|
||||
// concerns and I prefer that interface. As such this type is very much based on Termwiz'
|
||||
// `UnixTerminal` but the responsibilities are split between this file and
|
||||
// `src/event/source/unix.rs` - the latter being more inspired by crossterm.
|
||||
// Ultimately this terminal doesn't look much like Termwiz' due to the use of `rustix` and
|
||||
// differences in the trait and `Drop` behavior (see `super`'s CREDIT comment).
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnixTerminal {
|
||||
/// Shared wrapper around the reader (stdin or `/dev/tty`)
|
||||
|
@@ -39,6 +39,12 @@ const BUF_SIZE: usize = 128;
|
||||
|
||||
type CodePageID = u32;
|
||||
|
||||
// CREDIT: Like the Unix terminal module this is mainly based on WezTerm code (except for the
|
||||
// event source parts in `src/event/source/windows.rs` which reaches into these functions).
|
||||
// <https://github.com/wezterm/wezterm/blob/a87358516004a652ad840bc1661bdf65ffc89b43/filedescriptor/src/windows.rs>
|
||||
// This crate however uses `windows-sys` instead of `winapi` and has a slightly different API for
|
||||
// the `InputHandle` and `OutputHandle`.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct InputHandle {
|
||||
handle: OwnedHandle,
|
||||
@@ -258,15 +264,20 @@ fn open_pty() -> io::Result<(InputHandle, OutputHandle)> {
|
||||
Ok((input, output))
|
||||
}
|
||||
|
||||
// CREDIT: Again, like the UnixTerminal in the unix module this is mostly based on WezTerm but
|
||||
// only covers the parts not related to the event source.
|
||||
// <https://github.com/wezterm/wezterm/blob/a87358516004a652ad840bc1661bdf65ffc89b43/termwiz/src/terminal/windows.rs#L482-L860>
|
||||
// Also, the legacy Console API is not implemented.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WindowsTerminal {
|
||||
input: InputHandle,
|
||||
output: BufWriter<OutputHandle>,
|
||||
reader: EventReader,
|
||||
original_input_mode: u32,
|
||||
original_output_mode: u32,
|
||||
original_input_cp: u32,
|
||||
original_output_cp: u32,
|
||||
original_input_mode: CONSOLE_MODE,
|
||||
original_output_mode: CONSOLE_MODE,
|
||||
original_input_cp: CodePageID,
|
||||
original_output_cp: CodePageID,
|
||||
}
|
||||
|
||||
impl WindowsTerminal {
|
||||
|
Reference in New Issue
Block a user