Skip to content
Draft
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
7 changes: 4 additions & 3 deletions internal/tui2/prose/inline.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,9 @@ func (r *renderer) codeSpan(n *ast.CodeSpan, w *wrapper, st style) {
next := st
next.tok = tokens.TextPrimary
// ONE VISIBLE MARK PER TOKEN. The callback carries semantic knowledge prose
// does not own; when it says another mark is coming, adding the plane here
// would turn one token into the loudest object in its sentence.
// does not own; when it says another mark is coming, prose still renders the
// text plainly and lets that later mark carry the affordance. Ordinary inline
// code is plain too: code fences and semantic highlights own the raised plane.
if r.opts.PlainCodeSpan != nil && r.opts.PlainCodeSpan(text) {
w.push(text, next)
return
Expand All @@ -130,7 +131,7 @@ func (r *renderer) codeSpan(n *ast.CodeSpan, w *wrapper, st style) {
w.push("`"+text+"`", next)
return
}
next.ground = true
next.tok = tokens.TextSecondary
w.push(text, next)
}

Expand Down
37 changes: 22 additions & 15 deletions internal/tui3/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -813,10 +813,10 @@ func wrapCodeLine(line string, width int) []string {
//
// CODE IS NOT PROSE AND GETS NO LINKS. Two guards cover the code that can name
// a task: a fenced block carries [tokens.GlyphCodeGutter], while a non-path
// inline span carries prose's raised plane. Path spans deliberately drop that
// plane under ONE VISIBLE MARK PER TOKEN, but the linker's path grammar cannot
// recognize their whitespace-bearing task reference anyway. At profiles with
// no plane, prose restores backticks and [masked] reads those instead.
// inline span carries prose's quieter inline-code ink. Path spans deliberately
// render plain under ONE VISIBLE MARK PER TOKEN, but the linker's path grammar
// cannot recognize their whitespace-bearing task reference anyway. At profiles
// with no plane, prose restores backticks and [masked] reads those instead.

// taskLink is one drawn reference: the columns it occupies on its row, and the
// node behind them.
Expand Down Expand Up @@ -864,7 +864,7 @@ func linkifyTasks(text string, pal palette, look func(uint64) (string, bool), ho
if !hasTaskWord(text) {
return text, nil
}
flat, ground := flatten(text)
flat, mask := flatten(text)
if strings.Contains(flat, tokens.GlyphCodeGutter) {
// A fenced line. The whole row is source, and source that says "task 7"
// is saying it to a compiler.
Expand All @@ -873,7 +873,7 @@ func linkifyTasks(text string, pal palette, look func(uint64) (string, bool), ho
refs := taskRefs(flat)
kept := refs[:0]
for _, ref := range refs {
if grounded(ground, ref.from, ref.to) || masked(flat, ref.from) {
if grounded(mask, ref.from, ref.to) || masked(flat, ref.from) {
continue
}
title, ok := look(ref.id)
Expand Down Expand Up @@ -1068,29 +1068,29 @@ func indexFold(s, needle string, from int) int {
// ── the painted row, taken apart and put back together ──────────────────────

// flatten strips one painted row to its plain bytes and reports, byte for byte,
// whether it was drawn on a background.
// whether it belongs to a span task links must ignore.
//
// It does its own stripping rather than calling [ansi.Strip] for one reason: the
// two answers have to be indexed the same way, and the only guarantee of that is
// that one walk produced both.
func flatten(text string) (string, []bool) {
var (
out strings.Builder
ground []bool
on bool
out strings.Builder
mask []bool
on bool
)
out.Grow(len(text))
for i := 0; i < len(text); {
if n := escLen(text, i); n > 0 {
on = sgrGround(text[i:i+n], on)
on = sgrTaskMask(text[i:i+n], on)
i += n
continue
}
out.WriteByte(text[i])
ground = append(ground, on)
mask = append(mask, on)
i++
}
return out.String(), ground
return out.String(), mask
}

// paintLinks writes the row back out with every reference inked, and returns the
Expand Down Expand Up @@ -1268,14 +1268,21 @@ func escLen(s string, i int) int {
}

// sgrGround folds one escape sequence into "is there a background under this".
func sgrGround(seq string, on bool) bool {
func sgrTaskMask(seq string, on bool) bool {
params, ok := sgrParams(seq)
if !ok {
return on
}
body := strings.TrimPrefix(seq, "\x1b[")
body = strings.TrimSuffix(body, "m")
inlineCode := strings.TrimPrefix(tokens.TextSecondary.Fg(tokens.TrueColor, tokens.FocusNormal), "\x1b[")
inlineCode = strings.TrimSuffix(inlineCode, "m")
if body == inlineCode {
on = true
}
for i := 0; i < len(params); i++ {
switch param := params[i]; {
case param == "", param == "0", param == "49":
case param == "", param == "0", param == "39", param == "49":
on = false
case param == "48":
on = true
Expand Down
10 changes: 6 additions & 4 deletions internal/tui3/markdown_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,12 @@ func TestMarkdownEmphasisIsWeightNotPunctuation(t *testing.T) {
if !strings.Contains(row, "\x1b[3m") {
t.Fatalf("italic lost its slant: %q", row)
}
// An inline code span is set apart by its ground, not by its punctuation:
// the raised plane where the profile has one.
if sheet := tokens.Sheet.Bg(tokens.TrueColor, tokens.FocusNormal); !strings.Contains(row, sheet) {
t.Fatalf("the code span is not raised onto the sheet: %q", row)
// An inline code span is set apart without its punctuation or a raised plane.
if sheet := tokens.Sheet.Bg(tokens.TrueColor, tokens.FocusNormal); strings.Contains(row, sheet) {
t.Fatalf("the code span is raised onto the sheet: %q", row)
}
if code := tokens.TextSecondary.Fg(tokens.TrueColor, tokens.FocusNormal) + "code"; !strings.Contains(row, code) {
t.Fatalf("the code span is not distinguished as secondary text: %q", row)
}
}

Expand Down
8 changes: 5 additions & 3 deletions internal/tui3/pathlink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,15 +219,17 @@ func TestInlineCodePathsAndCodeSpendOneMarkEach(t *testing.T) {
row := rows[0]
sheet := tokens.Sheet.Bg(tokens.TrueColor, tokens.FocusNormal)
path := pal.underline("internal/tui3/pathlink.go")
code := sheet + tokens.TextPrimary.Fg(tokens.TrueColor, tokens.FocusNormal) + "len(x)"
if !strings.Contains(row, path) {
t.Fatalf("the path does not wear the palette underline: %q", row)
}
if strings.Contains(row, sheet+tokens.TextPrimary.Fg(tokens.TrueColor, tokens.FocusNormal)+"internal/tui3/pathlink.go") {
t.Fatalf("the path also wears the raised plane: %q", row)
}
if !strings.Contains(row, code) {
t.Fatalf("ordinary inline code lost the raised plane: %q", row)
if strings.Contains(row, sheet) {
t.Fatalf("inline code kept the raised code plane: %q", row)
}
if !strings.Contains(row, "len(x)") {
t.Fatalf("ordinary inline code disappeared: %q", row)
}
if strings.Count(row, sgrUnderOn) != 1 {
t.Fatalf("ordinary inline code also wears an underline: %q", row)
Expand Down
8 changes: 4 additions & 4 deletions internal/tui3/tasklink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ func TestTheTaskLinkGrammarRecognizesOnlyWhatNamesATask(t *testing.T) {
}

// CODE IS NOT PROSE AND GETS NO LINKS — both ways task-shaped code reaches a
// row: the fenced block behind its gutter, and the non-path inline span on the
// raised plane.
// row: the fenced block behind its gutter, and the non-path inline span in
// prose's quieter inline-code ink.
func TestTaskLinksSkipCode(t *testing.T) {
look := func(uint64) (string, bool) { return "Fix the nil-map", true }
pal := newPalette(tokens.ANSI256, false)
Expand All @@ -126,8 +126,8 @@ func TestTaskLinksSkipCode(t *testing.T) {
t.Fatalf("a fenced line grew %d links: %q", len(links), plain(out))
}

// AN INLINE SPAN, through the real renderer: a code span is drawn on the
// surface's one raised plane, and this pass reads the plane off the row.
// AN INLINE SPAN, through the real renderer: a code span is drawn without
// the raised plane, and this pass still tells it apart from prose.
st := tokens.NewStyler(tokens.TrueColor, tokens.FocusNormal)
if !tokens.TrueColor.SheetGround() {
t.Skip("this profile has no raised plane to test")
Expand Down