diff --git a/m/styledStringSplitter.go b/m/styledStringSplitter.go index 23dca4e8..8dd34edc 100644 --- a/m/styledStringSplitter.go +++ b/m/styledStringSplitter.go @@ -125,7 +125,7 @@ func (s *styledStringSplitter) consumeControlSequence(charAfterEsc rune) bool { if char == ';' || (char >= '0' && char <= '9') { // Sequence still in progress - if s.input[startIndex:s.nextByteIndex] == "8;;" { + if charAfterEsc == ']' && s.input[startIndex:s.nextByteIndex] == "8;;" { // Special case, here comes the URL return s.handleUrl() } @@ -142,6 +142,10 @@ func (s *styledStringSplitter) consumeControlSequence(charAfterEsc rune) bool { // If the whole CSI sequence is ESC[33m, you should call this function with just // "33m". func (s *styledStringSplitter) handleCompleteControlSequence(charAfterEsc rune, sequence string) bool { + if charAfterEsc == ']' { + return s.handleOsc(sequence) + } + if charAfterEsc != '[' { return false } @@ -162,6 +166,24 @@ func (s *styledStringSplitter) handleCompleteControlSequence(charAfterEsc rune, return false } +func (s *styledStringSplitter) handleOsc(sequence string) bool { + if strings.HasPrefix(sequence, "133;") && len(sequence) == len("133;A") { + // Got ESC]133;X, where "X" could be anything. These are prompt hints, + // and rendering those makes no sense. We should just ignore them: + // https://gitlab.freedesktop.org/Per_Bothner/specifications/blob/master/proposals/semantic-prompts.md + endMarker := s.nextChar() + if endMarker == '\x07' { + return true + } + + if endMarker == esc { + return s.nextChar() == '\\' + } + } + + return false +} + // We just got ESC]8; and should now read the URL. URLs end with ASCII 7 BEL or ESC \. func (s *styledStringSplitter) handleUrl() bool { // Valid URL characters. diff --git a/m/styledStringSplitter_test.go b/m/styledStringSplitter_test.go index e0e26c2e..a6beef5e 100644 --- a/m/styledStringSplitter_test.go +++ b/m/styledStringSplitter_test.go @@ -3,6 +3,7 @@ package m import ( "testing" + "github.com/walles/moar/twin" "gotest.tools/v3/assert" ) @@ -25,3 +26,23 @@ func TestNextCharLastChar_empty(t *testing.T) { assert.Equal(t, rune(-1), s.nextChar()) assert.Equal(t, rune(-1), s.lastChar()) } + +// We should ignore OSC 133 sequences. +// +// Ref: +// https://gitlab.freedesktop.org/Per_Bothner/specifications/blob/master/proposals/semantic-prompts.md +func TestIgnorePromptHints(t *testing.T) { + // From an e-mail I got titled "moar question: "--RAW-CONTROL-CHARS" equivalent" + result := styledStringsFromString("\x1b]133;A\x1b\\hello") + assert.Equal(t, twin.StyleDefault, result.trailer) + assert.Equal(t, 1, len(result.styledStrings)) + assert.Equal(t, "hello", result.styledStrings[0].String) + assert.Equal(t, twin.StyleDefault, result.styledStrings[0].Style) + + // C rather than A, different end-of-sequence, should also be ignored + result = styledStringsFromString("\x1b]133;C\x07hello") + assert.Equal(t, twin.StyleDefault, result.trailer) + assert.Equal(t, 1, len(result.styledStrings)) + assert.Equal(t, "hello", result.styledStrings[0].String) + assert.Equal(t, twin.StyleDefault, result.styledStrings[0].Style) +}