Skip to content

Commit

Permalink
feature: underline styles
Browse files Browse the repository at this point in the history
This supports now curly, double, dashed, and dotted underline styles
where trhe terminal supports it.  This works well on Windows Terminal,
reasonably on iTerm2, Alacritty, Kitty, and probably others.

The wasm mode terminal includes support for this, dependent on the browser
capabilities.

The macOS Terminal just changes the background color.  Legacy Windows
console does nothing.

We will try to provide a regular underscore as a fallback.  A new style.go
demo is included to see some style combinations.
  • Loading branch information
gdamore committed Mar 4, 2024
1 parent d1364d5 commit 75a6956
Show file tree
Hide file tree
Showing 20 changed files with 693 additions and 244 deletions.
214 changes: 214 additions & 0 deletions _demos/style.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
//go:build ignore
// +build ignore

// Copyright 2019 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// unicode just displays a Unicode test on your screen.
// Press ESC to exit the program.
package main

import (
"fmt"
"os"

"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/encoding"
runewidth "github.com/mattn/go-runewidth"
)

var row = 0
var style = tcell.StyleDefault

func putln(s tcell.Screen, str string) {

puts(s, style, 1, row, str)
row++
}

func puts(s tcell.Screen, style tcell.Style, x, y int, str string) {
i := 0
var deferred []rune
dwidth := 0
zwj := false
for _, r := range str {
if r == '\u200d' {
if len(deferred) == 0 {
deferred = append(deferred, ' ')
dwidth = 1
}
deferred = append(deferred, r)
zwj = true
continue
}
if zwj {
deferred = append(deferred, r)
zwj = false
continue
}
switch runewidth.RuneWidth(r) {
case 0:
if len(deferred) == 0 {
deferred = append(deferred, ' ')
dwidth = 1
}
case 1:
if len(deferred) != 0 {
s.SetContent(x+i, y, deferred[0], deferred[1:], style)
i += dwidth
}
deferred = nil
dwidth = 1
case 2:
if len(deferred) != 0 {
s.SetContent(x+i, y, deferred[0], deferred[1:], style)
i += dwidth
}
deferred = nil
dwidth = 2
}
deferred = append(deferred, r)
}
if len(deferred) != 0 {
s.SetContent(x+i, y, deferred[0], deferred[1:], style)
i += dwidth
}
}

func main() {

s, e := tcell.NewScreen()
if e != nil {
fmt.Fprintf(os.Stderr, "%v\n", e)
os.Exit(1)
}

encoding.Register()

if e = s.Init(); e != nil {
fmt.Fprintf(os.Stderr, "%v\n", e)
os.Exit(1)
}

plain := tcell.StyleDefault
bold := style.Bold(true)

s.SetStyle(tcell.StyleDefault.
Foreground(tcell.ColorBlack).
Background(tcell.ColorWhite))
s.Clear()

quit := make(chan struct{})

style = bold.Foreground(tcell.ColorBlue).Background(tcell.ColorSilver)

row = 2
puts(s, style, 2, row, "Press ESC to Exit")
row = 4
puts(s, plain, 2, row, "Note: Style support is dependent on your terminal.")
row = 6

plain = tcell.StyleDefault.Foreground(tcell.ColorBlack).Background(tcell.ColorWhite)

style = plain
puts(s, style, 2, row, "Plain")
row++

style = plain.Blink(true)
puts(s, style, 2, row, "Blink")
row++

style = plain.Reverse(true)
puts(s, style, 2, row, "Reverse")
row++

style = plain.Dim(true)
puts(s, style, 2, row, "Dim")
row++

style = plain.Underline(true)
puts(s, style, 2, row, "Underline")
row++

style = plain.Italic(true)
puts(s, style, 2, row, "Italic")
row++

style = plain.Bold(true)
puts(s, style, 2, row, "Bold")
row++

style = plain.Bold(true).Italic(true)
puts(s, style, 2, row, "Bold Italic")
row++

style = plain.Bold(true).Italic(true).Underline(true)
puts(s, style, 2, row, "Bold Italic Underline")
row++

style = plain.StrikeThrough(true)
puts(s, style, 2, row, "Strikethrough")
row++

style = plain.DoubleUnderline(true)
puts(s, style, 2, row, "Double Underline")
row++

style = plain.CurlyUnderline(true)
puts(s, style, 2, row, "Curly Underline")
row++

style = plain.DottedUnderline(true)
puts(s, style, 2, row, "Dotted Underline")
row++

style = plain.DashedUnderline(true)
puts(s, style, 2, row, "Dashed Underline")
row++

style = plain.Url("http://github.com/gdamore/tcell")
puts(s, style, 2, row, "HyperLink")
row++

style = plain.Foreground(tcell.ColorRed)
puts(s, style, 2, row, "Red Foreground")
row++

style = plain.Background(tcell.ColorRed)
puts(s, style, 2, row, "Red Background")
row++

s.Show()
go func() {
for {
ev := s.PollEvent()
switch ev := ev.(type) {
case *tcell.EventKey:
switch ev.Key() {
case tcell.KeyEscape, tcell.KeyEnter:
close(quit)
return
case tcell.KeyCtrlL:
s.Sync()
}
case *tcell.EventResize:
s.Sync()
}
}
}()

<-quit

s.Fini()
}
13 changes: 9 additions & 4 deletions attr.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 The TCell Authors
// Copyright 2024 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
Expand All @@ -19,7 +19,8 @@ package tcell
type AttrMask int

// Attributes are not colors, but affect the display of text. They can
// be combined.
// be combined, in some cases, but not others. (E.g. you can have Dim Italic,
// but only CurlyUnderline cannot be mixed with DottedUnderline.)
const (
AttrBold AttrMask = 1 << iota
AttrBlink
Expand All @@ -28,6 +29,10 @@ const (
AttrDim
AttrItalic
AttrStrikeThrough
AttrInvalid // Mark the style or attributes invalid
AttrNone AttrMask = 0 // Just normal text.
AttrDoubleUnderline
AttrCurlyUnderline
AttrDottedUnderline
AttrDashedUnderline
AttrInvalid AttrMask = 1 << 31 // Mark the style or attributes invalid
AttrNone AttrMask = 0 // Just normal text.
)
16 changes: 15 additions & 1 deletion console_win.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ const (
vtEnableAm = "\x1b[?7h"
vtEnterCA = "\x1b[?1049h\x1b[22;0;0t"
vtExitCA = "\x1b[?1049l\x1b[23;0;0t"
vtDoubleUnderline = "\x1b[4:2m"
vtCurlyUnderline = "\x1b[4:3m"
vtDottedUnderline = "\x1b[4:4m"
vtDashedUnderline = "\x1b[4:5m"
)

var vtCursorStyles = map[CursorStyle]string{
Expand Down Expand Up @@ -922,8 +926,18 @@ func (s *cScreen) sendVtStyle(style Style) {
if attrs&AttrBlink != 0 {
esc.WriteString(vtBlink)
}
if attrs&AttrUnderline != 0 {
if attrs&(AttrUnderline|AttrDoubleUnderline|AttrCurlyUnderline|AttrDottedUnderline|AttrDashedUnderline) != 0 {
esc.WriteString(vtUnderline)
// legacy ConHost does not understand these but Terminal does
if (attrs & AttrDoubleUnderline) != 0 {
esc.WriteString(vtDoubleUnderline)
} else if (attrs & AttrCurlyUnderline) != 0 {
esc.WriteString(vtCurlyUnderline)
} else if (attrs & AttrDottedUnderline) != 0 {
esc.WriteString(vtDottedUnderline)
} else if (attrs & AttrDashedUnderline) != 0 {
esc.WriteString(vtDashedUnderline)
}
}
if attrs&AttrReverse != 0 {
esc.WriteString(vtReverse)
Expand Down
18 changes: 17 additions & 1 deletion style.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022 The TCell Authors
// Copyright 2024 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
Expand Down Expand Up @@ -136,6 +136,22 @@ func (s Style) StrikeThrough(on bool) Style {
return s.setAttrs(AttrStrikeThrough, on)
}

func (s Style) DoubleUnderline(on bool) Style {
return s.setAttrs(AttrDoubleUnderline, on)
}

func (s Style) CurlyUnderline(on bool) Style {
return s.setAttrs(AttrCurlyUnderline, on)
}

func (s Style) DottedUnderline(on bool) Style {
return s.setAttrs(AttrDottedUnderline, on)
}

func (s Style) DashedUnderline(on bool) Style {
return s.setAttrs(AttrDashedUnderline, on)
}

// Attributes returns a new style based on s, with its attributes set as
// specified.
func (s Style) Attributes(attrs AttrMask) Style {
Expand Down
5 changes: 5 additions & 0 deletions terminfo/a/alacritty/term.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,10 @@ func init() {
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
DoubleUnderline: "\x1b[4:2m",
CurlyUnderline: "\x1b[4:3m",
DottedUnderline: "\x1b[4:4m",
DashedUnderline: "\x1b[4:5m",
XTermLike: true,
})
}
2 changes: 2 additions & 0 deletions terminfo/g/gnome/term.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func init() {
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
XTermLike: true,
})

// GNOME Terminal with xterm 256-colors
Expand Down Expand Up @@ -130,5 +131,6 @@ func init() {
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
XTermLike: true,
})
}
2 changes: 2 additions & 0 deletions terminfo/k/konsole/term.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func init() {
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
XTermLike: true,
})

// KDE console window with xterm 256-colors
Expand Down Expand Up @@ -132,5 +133,6 @@ func init() {
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
XTermLike: true,
})
}
1 change: 1 addition & 0 deletions terminfo/k/kterm/term.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,6 @@ func init() {
KeyF19: "\x1b[33~",
KeyF20: "\x1b[34~",
AutoMargin: true,
XTermLike: true,
})
}
22 changes: 22 additions & 0 deletions terminfo/mkinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,23 @@ func getinfo(name string) (*terminfo.Terminfo, string, error) {
t.SetFgBg = fg + ";" + bg
}

if tc.getflag("XT") {
t.XTermLike = true
}
if smulx := tc.getstr("Smulx"); smulx != "" {
if t.DoubleUnderline == "" {
t.DoubleUnderline = t.TParm(smulx, 2)
}
if t.CurlyUnderline == "" {
t.CurlyUnderline = t.TParm(smulx, 3)
}
if t.DottedUnderine == "" {
t.DottedUnderine = t.TParm(smulx, 4)
}
if t.DashedUnderline == "" {
t.DashedUnderline = t.TParm(smulx, 5)
}
}
return t, tc.desc, nil
}

Expand Down Expand Up @@ -621,6 +638,11 @@ func dotGoInfo(w io.Writer, terms []*TData) {
dotGoAddStr(w, "CursorSteadyUnderline", t.CursorSteadyUnderline)
dotGoAddStr(w, "CursorBlinkingBar", t.CursorBlinkingBar)
dotGoAddStr(w, "CursorSteadyBar", t.CursorSteadyBar)
dotGoAddStr(w, "DoubleUnderline", t.DoubleUnderline)
dotGoAddStr(w, "CurlyUnderline", t.CurlyUnderline)
dotGoAddStr(w, "DottedUnderline", t.DottedUnderine)
dotGoAddStr(w, "DashedUnderline", t.DashedUnderline)
dotGoAddFlag(w, "XTermLike", t.XTermLike)
fmt.Fprintln(w, "\t})")
}
fmt.Fprintln(w, "}")
Expand Down
Loading

0 comments on commit 75a6956

Please sign in to comment.