Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions internal/anisearch/anisearch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,26 @@ func TestRenderSearchResultsLayout(t *testing.T) {
}
}

func TestWriteTerminalFrameUsesCRLFInRawMode(t *testing.T) {
var output bytes.Buffer
writeTerminalFrame(&output, "first\nsecond\n", true)

want := "\033[H\033[Jfirst\r\nsecond\r\n"
if got := output.String(); got != want {
t.Fatalf("writeTerminalFrame() = %q, want %q", got, want)
}
}

func TestWriteTerminalFramePreservesNewlinesOutsideRawMode(t *testing.T) {
var output bytes.Buffer
writeTerminalFrame(&output, "first\nsecond\n", false)

want := "\033[H\033[Jfirst\nsecond\n"
if got := output.String(); got != want {
t.Fatalf("writeTerminalFrame() = %q, want %q", got, want)
}
}

func TestFormatEpisodes(t *testing.T) {
tests := []struct {
name string
Expand Down
8 changes: 4 additions & 4 deletions internal/anisearch/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,24 +73,24 @@ func readKey() KeyEvent {

// runInRawTerminal executes f while stdin is in raw terminal mode.
// It restores the terminal state before returning.
func runInRawTerminal(f func()) {
func runInRawTerminal(f func(raw bool)) {
fd := int(os.Stdin.Fd())

if !term.IsTerminal(fd) {
// Not a terminal; just run f directly.
f()
f(false)
return
}

oldState, err := term.MakeRaw(fd)
if err != nil {
// Cannot set raw mode; run f directly anyway.
f()
f(false)
return
}
defer term.Restore(fd, oldState) //nolint:errcheck

f()
f(true)
}

// PromptForQuery reads a search query from stdin.
Expand Down
19 changes: 14 additions & 5 deletions internal/anisearch/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package anisearch
import (
"context"
"fmt"
"io"
"os"
"strings"
"time"

"github.com/calmcacil/CalmsToolkit/internal/config"
Expand Down Expand Up @@ -132,11 +134,11 @@ func runInteractive(ctx context.Context, client *AniListClient, query string, cu
selected := 0
currentPage := 1

runInRawTerminal(func() {
runInRawTerminal(func(raw bool) {
for {
// Render search results.
output := renderSearchResults(query, currentResult, selected, mapping, cfg)
fmt.Fprint(os.Stdout, "\033[H\033[J"+output)
writeTerminalFrame(os.Stdout, output, raw)

// Read key.
key := readKey()
Expand All @@ -152,7 +154,7 @@ func runInteractive(ctx context.Context, client *AniListClient, query string, cu
}
case KeyEnter:
if selected >= 0 && selected < len(currentResult.Media) {
runDetailView(currentResult.Media[selected], mapping, cfg)
runDetailView(currentResult.Media[selected], mapping, cfg, raw)
}
case KeyNext:
if currentResult.PageInfo.HasNextPage {
Expand Down Expand Up @@ -186,14 +188,14 @@ func runInteractive(ctx context.Context, client *AniListClient, query string, cu
}

// runDetailView shows the detail for a single show until the user presses B or Q.
func runDetailView(show Show, mapping *AnibridgeMapping, cfg ToolConfig) {
func runDetailView(show Show, mapping *AnibridgeMapping, cfg ToolConfig, raw bool) {
tvdbID := 0
if mapping != nil {
tvdbID, _ = mapping.LookupByAniList(show.ID)
}

output := renderDetail(show, tvdbID, cfg)
fmt.Fprint(os.Stdout, "\033[H\033[J"+output)
writeTerminalFrame(os.Stdout, output, raw)

for {
key := readKey()
Expand All @@ -207,3 +209,10 @@ func runDetailView(show Show, mapping *AnibridgeMapping, cfg ToolConfig) {
}
}
}

func writeTerminalFrame(w io.Writer, frame string, raw bool) {
if raw {
frame = strings.ReplaceAll(frame, "\n", "\r\n")
}
fmt.Fprint(w, "\033[H\033[J"+frame)
}