wez / wezterm

A GPU-accelerated cross-platform terminal emulator and multiplexer written by @wez and implemented in Rust
https://wezfurlong.org/wezterm/
Other
16.42k stars 731 forks source link

Support DECRQM 2027 - grapheme cluster processing #4320

Closed rockorager closed 11 months ago

rockorager commented 11 months ago

Is your feature request related to a problem? Please describe. Determining how a terminal will move the cursor when printing a complex unicode grapheme is a difficult chore. Wezterm is one of a handful of terminals (contour, foot being the others) that properly moves the cursor according to Unicode specifications.

Describe the solution you'd like The author of Contour has created a specification for this mode of rendering: terminal-unicode-core, implemented as a private DEC mode 2027. I think wezterm can accurately respond to DECRQM queries with a 3 (permanently enabled). It would be a bonus to be able to set/reset the mode using DECSET and DECRST but at least I would like to see a response to DECRQM so I can know what will happen.

Describe alternatives you've considered At application start, I query cursor position, print a complex emoji, and query cursor position again to determine support for grapheme processing. This is not ideal.

Additional context I've tested several terminals for proper unicode support. foot, contour, and wezterm are the only three that support proper processing. contour supports 2027, and foot has just opened a PR this morning to add support. It would be awesome to have all three respond to this query!

wez commented 11 months ago

Seems interesting! I filed https://github.com/contour-terminal/terminal-unicode-core/issues/2 to get a more easily viewable version of the spec in that repo

wez commented 11 months ago
diff --git a/term/src/terminalstate/mod.rs b/term/src/terminalstate/mod.rs
index d7a1fab2c..fc12cc005 100644
--- a/term/src/terminalstate/mod.rs
+++ b/term/src/terminalstate/mod.rs
@@ -1345,6 +1345,22 @@ impl TerminalState {
         }
     }

+    /// Indicates that mode is permanently enabled
+    fn decqrm_response_permanent(&mut self, mode: Mode) {
+        let (is_dec, number) = match &mode {
+            Mode::QueryDecPrivateMode(DecPrivateMode::Code(code)) => (true, code.to_u16().unwrap()),
+            Mode::QueryDecPrivateMode(DecPrivateMode::Unspecified(code)) => (true, *code),
+            Mode::QueryMode(TerminalMode::Code(code)) => (false, code.to_u16().unwrap()),
+            Mode::QueryMode(TerminalMode::Unspecified(code)) => (false, *code),
+            _ => unreachable!(),
+        };
+
+        let prefix = if is_dec { "?" } else { "" };
+
+        write!(self.writer, "\x1b[{prefix}{number};3$y").ok();
+        self.writer.flush().ok();
+    }
+
     fn decqrm_response(&mut self, mode: Mode, mut recognized: bool, enabled: bool) {
         let (is_dec, number) = match &mode {
             Mode::QueryDecPrivateMode(DecPrivateMode::Code(code)) => (true, code.to_u16().unwrap()),
@@ -1449,6 +1465,21 @@ impl TerminalState {
                 self.decqrm_response(mode, true, self.left_and_right_margin_mode);
             }

+            Mode::SetDecPrivateMode(DecPrivateMode::Code(
+                DecPrivateModeCode::GraphemeClustering,
+            ))
+            | Mode::ResetDecPrivateMode(DecPrivateMode::Code(
+                DecPrivateModeCode::GraphemeClustering,
+            )) => {
+                // Permanently enabled
+            }
+
+            Mode::QueryDecPrivateMode(DecPrivateMode::Code(
+                DecPrivateModeCode::GraphemeClustering,
+            )) => {
+                self.decqrm_response_permanent(mode);
+            }
+
             Mode::SetDecPrivateMode(DecPrivateMode::Code(DecPrivateModeCode::SaveCursor)) => {
                 self.dec_save_cursor();
             }
diff --git a/termwiz/src/escape/csi.rs b/termwiz/src/escape/csi.rs
index fe7a2cee7..810f99f1f 100644
--- a/termwiz/src/escape/csi.rs
+++ b/termwiz/src/escape/csi.rs
@@ -841,6 +841,11 @@ pub enum DecPrivateModeCode {
     EnableAlternateScreen = 47,
     OptEnableAlternateScreen = 1047,
     BracketedPaste = 2004,
+
+    /// <https://github.com/contour-terminal/terminal-unicode-core/>
+    /// Grapheme clustering mode
+    GraphemeClustering = 2027,
+
     /// Applies to sixel and regis modes
     UsePrivateColorRegistersForEachGraphic = 1070,

Could you try this diff and let me know if that does the job? This should be sufficient to respond to that query. I haven't done any testing beyond verifying that it compiles.

rockorager commented 11 months ago

Works great! Got CSI ? 2027 ; 3 y from my query, as expected. Thanks!

wez commented 11 months ago

Thanks! This is now in main and will show up in nightly builds within the next hour or so.

github-actions[bot] commented 10 months ago

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.