Repository: charmbracelet/sequin
Pull request #62: implement FinalTerm escape sequences
Author: login: acarl005
Created: 2025-03-17T16:54:14Z | Merged: 2025-03-18T19:29:44Z

This PR implements #46 


Diff:
diff --git a/final_term.go b/final_term.go
new file mode 100644
index 0000000..5195850
--- /dev/null
+++ b/final_term.go
@@ -0,0 +1,44 @@
+// https://github.com/gnachman/iterm2-website/blob/master/source/_includes/3.4/documentation-escape-codes.md#shell-integrationfinalterm
+package main
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/charmbracelet/x/ansi"
+)
+
+//nolint:mnd
+func handleFinalTerm(p *ansi.Parser) (string, error) {
+	parts := bytes.Split(p.Data(), []byte{';'})
+
+	if len(parts) < 2 {
+		return "", errInvalid
+	}
+
+	if len(parts[1]) != 1 {
+		return "", errInvalid
+	}
+
+	var buf string
+	switch parts[1][0] {
+	case 'A':
+		buf += "Prompt start"
+		break
+	case 'B':
+		buf += "Command start"
+		break
+	case 'C':
+		buf += "Command executed"
+		break
+	case 'D':
+		buf += "Command finished"
+		if len(parts) > 2 && len(parts[2]) > 1 {
+			buf += fmt.Sprintf(", exit code: %s", string(parts[2]))
+		}
+		break
+	default:
+		return "", errInvalid
+	}
+	return buf, nil
+}
diff --git a/go.mod b/go.mod
index 3e0be22..44bf325 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.22.8
 require (
 	github.com/charmbracelet/colorprofile v0.2.2
 	github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250127175135-cf3915a699b4
-	github.com/charmbracelet/x/ansi v0.8.0
+	github.com/charmbracelet/x/ansi v0.8.1-0.20250219181712-d0cc2ccd9c9c
 	github.com/charmbracelet/x/exp/golden v0.0.0-20241029204245-3ef5e7b1ea37
 	github.com/charmbracelet/x/term v0.2.1
 	github.com/charmbracelet/x/xpty v0.1.2
diff --git a/go.sum b/go.sum
index 44db391..4d7f7e5 100644
--- a/go.sum
+++ b/go.sum
@@ -4,8 +4,8 @@ github.com/charmbracelet/colorprofile v0.2.2 h1:Cqkg0GqDd9vCdhRIBsCbeS8kvePcm8Py
 github.com/charmbracelet/colorprofile v0.2.2/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250127175135-cf3915a699b4 h1:HSnZgsIWrA0JbfFIZWJabtSdpqXuXbHYakidqzKP1dA=
 github.com/charmbracelet/lipgloss/v2 v2.0.0-alpha.2.0.20250127175135-cf3915a699b4/go.mod h1:wNdvvvSFeykFQxJ9rXcTA+IbZE9wgMEqhM4c8gV/faE=
-github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
-github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
+github.com/charmbracelet/x/ansi v0.8.1-0.20250219181712-d0cc2ccd9c9c h1:epVetfA9DwHBiiL3WOpEXwPWVzkPGCk8RY2V5uRdEh0=
+github.com/charmbracelet/x/ansi v0.8.1-0.20250219181712-d0cc2ccd9c9c/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
 github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
 github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
 github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
diff --git a/handlers.go b/handlers.go
index 7460dda..5407e9c 100644
--- a/handlers.go
+++ b/handlers.go
@@ -66,6 +66,7 @@ var oscHandlers = map[int]handlerFn{
 	110: handleResetTerminalColor,
 	111: handleResetTerminalColor,
 	112: handleResetTerminalColor,
+	133: handleFinalTerm,
 }
 
 var dcsHandlers = map[int]handlerFn{
diff --git a/main_test.go b/main_test.go
index ca8f90e..5c16807 100644
--- a/main_test.go
+++ b/main_test.go
@@ -243,6 +243,16 @@ var clipboard = map[string]string{
 	"invalid":         strings.Replace(ansi.SetPrimaryClipboard("hello"), "=", "", 1),
 }
 
+var finalterm = map[string]string{
+	"prompt start":               ansi.FinalTermPrompt(),
+	"prompt start invalid":       ansi.FinalTerm("AB"),
+	"command start":              ansi.FinalTermCmdStart(),
+	"command executed":           ansi.FinalTermCmdExecuted(),
+	"command finished":           ansi.FinalTermCmdFinished(),
+	"command finished exit code": ansi.FinalTermCmdFinished("127"),
+	"invalid":                    ansi.FinalTerm("Q"),
+}
+
 var keypad = map[string]string{
 	"normal keypad":      ansi.KeypadNumericMode,
 	"application keypad": ansi.KeypadApplicationMode,
@@ -265,6 +275,7 @@ func TestSequences(t *testing.T) {
 		"termcolor": termcolor,
 		"clipboard": clipboard,
 		"others":    others,
+		"finalterm": finalterm,
 		"keypad":    keypad,
 	} {
 		t.Run(name, func(t *testing.T) {
diff --git a/testdata/TestSequences/finalterm/command_executed.golden b/testdata/TestSequences/finalterm/command_executed.golden
new file mode 100644
index 0000000..3f64728
--- /dev/null
+++ b/testdata/TestSequences/finalterm/command_executed.golden
@@ -0,0 +1 @@
+ OSC 133;C: Command executed
diff --git a/testdata/TestSequences/finalterm/command_finished.golden b/testdata/TestSequences/finalterm/command_finished.golden
new file mode 100644
index 0000000..261c94b
--- /dev/null
+++ b/testdata/TestSequences/finalterm/command_finished.golden
@@ -0,0 +1 @@
+ OSC 133;D: Command finished
diff --git a/testdata/TestSequences/finalterm/command_finished_exit_code.golden b/testdata/TestSequences/finalterm/command_finished_exit_code.golden
new file mode 100644
index 0000000..6caf6ce
--- /dev/null
+++ b/testdata/TestSequences/finalterm/command_finished_exit_code.golden
@@ -0,0 +1 @@
+ OSC 133;D;127: Command finished, exit code: 127
diff --git a/testdata/TestSequences/finalterm/command_start.golden b/testdata/TestSequences/finalterm/command_start.golden
new file mode 100644
index 0000000..b9e41c8
--- /dev/null
+++ b/testdata/TestSequences/finalterm/command_start.golden
@@ -0,0 +1 @@
+ OSC 133;B: Command start
diff --git a/testdata/TestSequences/finalterm/invalid.golden b/testdata/TestSequences/finalterm/invalid.golden
new file mode 100644
index 0000000..0a53797
--- /dev/null
+++ b/testdata/TestSequences/finalterm/invalid.golden
@@ -0,0 +1 @@
+ OSC 133;Q: invalid sequence
diff --git a/testdata/TestSequences/finalterm/prompt_start.golden b/testdata/TestSequences/finalterm/prompt_start.golden
new file mode 100644
index 0000000..2d13fcd
--- /dev/null
+++ b/testdata/TestSequences/finalterm/prompt_start.golden
@@ -0,0 +1 @@
+ OSC 133;A: Prompt start
diff --git a/testdata/TestSequences/finalterm/prompt_start_invalid.golden b/testdata/TestSequences/finalterm/prompt_start_invalid.golden
new file mode 100644
index 0000000..813045c
--- /dev/null
+++ b/testdata/TestSequences/finalterm/prompt_start_invalid.golden
@@ -0,0 +1 @@
+ OSC 133;AB: invalid sequence


Reviews:
user: login: caarlos0
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:52:30Z
user: login: aymanbagabas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T05:59:12Z
user: login: aymanbagabas
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-18T17:59:41Z

Review comments:
diff_hunk: @@ -0,0 +1,44 @@
+// https://github.com/gnachman/iterm2-website/blob/master/source/_includes/3.4/documentation-escape-codes.md#shell-integrationfinalterm
+package main
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/charmbracelet/x/ansi"
+)
+
+//nolint:mnd
+func handleFinalTerm(p *ansi.Parser) (string, error) {
+	parts := bytes.Split(p.Data(), []byte{';'})
+
+	if len(parts) < 2 {
+		return "", errInvalid
+	}
+
+	if len(parts[1]) != 1 {
+		return "", errInvalid
+	}
+
+	var buf string
+	switch parts[1][0] {
+	case 65:
path: final_term.go
user: login: aymanbagabas
body: Let's use ASCII here to make this more obvious 
```suggestion
	case 'A':
```

created_at: 2025-03-18T05:57:48Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,44 @@
+// https://github.com/gnachman/iterm2-website/blob/master/source/_includes/3.4/documentation-escape-codes.md#shell-integrationfinalterm
+package main
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/charmbracelet/x/ansi"
+)
+
+//nolint:mnd
+func handleFinalTerm(p *ansi.Parser) (string, error) {
+	parts := bytes.Split(p.Data(), []byte{';'})
+
+	if len(parts) < 2 {
+		return "", errInvalid
+	}
+
+	if len(parts[1]) != 1 {
+		return "", errInvalid
+	}
+
+	var buf string
+	switch parts[1][0] {
+	case 65:
+		buf += "Prompt start"
+		break
+	case 66:
path: final_term.go
user: login: aymanbagabas
body: Same
```suggestion
	case 'B':
```

created_at: 2025-03-18T05:58:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,44 @@
+// https://github.com/gnachman/iterm2-website/blob/master/source/_includes/3.4/documentation-escape-codes.md#shell-integrationfinalterm
+package main
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/charmbracelet/x/ansi"
+)
+
+//nolint:mnd
+func handleFinalTerm(p *ansi.Parser) (string, error) {
+	parts := bytes.Split(p.Data(), []byte{';'})
+
+	if len(parts) < 2 {
+		return "", errInvalid
+	}
+
+	if len(parts[1]) != 1 {
+		return "", errInvalid
+	}
+
+	var buf string
+	switch parts[1][0] {
+	case 65:
+		buf += "Prompt start"
+		break
+	case 66:
+		buf += "Command start"
+		break
+	case 67:
path: final_term.go
user: login: aymanbagabas
body: ```suggestion
	case 'C':
```

created_at: 2025-03-18T05:58:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,44 @@
+// https://github.com/gnachman/iterm2-website/blob/master/source/_includes/3.4/documentation-escape-codes.md#shell-integrationfinalterm
+package main
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/charmbracelet/x/ansi"
+)
+
+//nolint:mnd
+func handleFinalTerm(p *ansi.Parser) (string, error) {
+	parts := bytes.Split(p.Data(), []byte{';'})
+
+	if len(parts) < 2 {
+		return "", errInvalid
+	}
+
+	if len(parts[1]) != 1 {
+		return "", errInvalid
+	}
+
+	var buf string
+	switch parts[1][0] {
+	case 65:
+		buf += "Prompt start"
+		break
+	case 66:
+		buf += "Command start"
+		break
+	case 67:
+		buf += "Command executed"
+		break
+	case 68:
path: final_term.go
user: login: aymanbagabas
body: ```suggestion
	case 'D':
```

created_at: 2025-03-18T05:58:58Z
author_association: MEMBER

PR comments:
user: login: coveralls
created_at: 2025-03-18T00:43:39Z
body: ## Pull Request Test Coverage Report for [Build 13931773996](https://coveralls.io/builds/72795419)


### Details

* **27** of **29**   **(93.1%)**  changed or added relevant lines in **1** file are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.2%**) to **87.631%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [final_term.go](https://coveralls.io/builds/72795419/source?filename=final_term.go#L16) | 27 | 29 | 93.1%
<!-- | **Total:** | **27** | **29** | **93.1%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/72795419/badge)](https://coveralls.io/builds/72795419) |
| :-- | --: |
| Change from base [Build 13931749881](https://coveralls.io/builds/72795391): |  0.2% |
| Covered Lines: | 751 |
| Relevant Lines: | 857 |

---
##### 💛  - [Coveralls](https://coveralls.io)

author_association: NONE
user: login: caarlos0
created_at: 2025-03-18T19:29:52Z
body: Thanks @acarl005 :D 
author_association: CONTRIBUTOR