1
0
mirror of https://github.com/gdamore/tcell.git synced 2025-04-24 13:48:51 +08:00

feature: underline styles

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.
This commit is contained in:
Garrett D'Amore 2024-03-03 17:01:54 -08:00
parent edd4f70bdd
commit 1fb8cfe768
20 changed files with 692 additions and 243 deletions

214
_demos/style.go Normal file
View File

@ -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
attr.go
View File

@ -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.
@ -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
@ -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.
)

View File

@ -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{
@ -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)

View File

@ -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.
@ -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 {

View File

@ -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,
})
}

View File

@ -67,6 +67,7 @@ func init() {
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
XTermLike: true,
})
// GNOME Terminal with xterm 256-colors
@ -130,5 +131,6 @@ func init() {
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
XTermLike: true,
})
}

View File

@ -68,6 +68,7 @@ func init() {
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
XTermLike: true,
})
// KDE console window with xterm 256-colors
@ -132,5 +133,6 @@ func init() {
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
XTermLike: true,
})
}

View File

@ -66,5 +66,6 @@ func init() {
KeyF19: "\x1b[33~",
KeyF20: "\x1b[34~",
AutoMargin: true,
XTermLike: true,
})
}

View File

@ -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
}
@ -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, "}")

View File

@ -110,6 +110,7 @@ func init() {
KeyCtrlHome: "\x1b[7^",
KeyCtrlEnd: "\x1b[8^",
AutoMargin: true,
XTermLike: true,
})
// rxvt 2.7.9 with xterm 256-colors
@ -215,6 +216,7 @@ func init() {
KeyCtrlHome: "\x1b[7^",
KeyCtrlEnd: "\x1b[8^",
AutoMargin: true,
XTermLike: true,
})
// rxvt 2.7.9 with xterm 88-colors
@ -320,6 +322,7 @@ func init() {
KeyCtrlHome: "\x1b[7^",
KeyCtrlEnd: "\x1b[8^",
AutoMargin: true,
XTermLike: true,
})
// rxvt-unicode terminal (X Window System)

View File

@ -67,6 +67,7 @@ func init() {
KeyClear: "\x1b[3;5~",
Modifiers: 1,
AutoMargin: true,
XTermLike: true,
})
// simpleterm with 256 colors
@ -130,5 +131,6 @@ func init() {
KeyClear: "\x1b[3;5~",
Modifiers: 1,
AutoMargin: true,
XTermLike: true,
})
}

View File

@ -8,64 +8,68 @@ func init() {
// tmux terminal multiplexer
terminfo.AddTerminfo(&terminfo.Terminfo{
Name: "tmux",
Columns: 80,
Lines: 24,
Colors: 8,
Bell: "\a",
Clear: "\x1b[H\x1b[J",
EnterCA: "\x1b[?1049h",
ExitCA: "\x1b[?1049l",
ShowCursor: "\x1b[34h\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[m\x0f",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e",
ExitAcs: "\x0f",
EnableAcs: "\x1b(B\x1b)0",
StrikeThrough: "\x1b[9m",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1bM",
KeyUp: "\x1bOA",
KeyDown: "\x1bOB",
KeyRight: "\x1bOC",
KeyLeft: "\x1bOD",
KeyInsert: "\x1b[2~",
KeyDelete: "\x1b[3~",
KeyBackspace: "\x7f",
KeyHome: "\x1b[1~",
KeyEnd: "\x1b[4~",
KeyPgUp: "\x1b[5~",
KeyPgDn: "\x1b[6~",
KeyF1: "\x1bOP",
KeyF2: "\x1bOQ",
KeyF3: "\x1bOR",
KeyF4: "\x1bOS",
KeyF5: "\x1b[15~",
KeyF6: "\x1b[17~",
KeyF7: "\x1b[18~",
KeyF8: "\x1b[19~",
KeyF9: "\x1b[20~",
KeyF10: "\x1b[21~",
KeyF11: "\x1b[23~",
KeyF12: "\x1b[24~",
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
Name: "tmux",
Columns: 80,
Lines: 24,
Colors: 8,
Bell: "\a",
Clear: "\x1b[H\x1b[J",
EnterCA: "\x1b[?1049h",
ExitCA: "\x1b[?1049l",
ShowCursor: "\x1b[34h\x1b[?25h",
HideCursor: "\x1b[?25l",
AttrOff: "\x1b[m\x0f",
Underline: "\x1b[4m",
Bold: "\x1b[1m",
Dim: "\x1b[2m",
Italic: "\x1b[3m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
EnterKeypad: "\x1b[?1h\x1b=",
ExitKeypad: "\x1b[?1l\x1b>",
SetFg: "\x1b[3%p1%dm",
SetBg: "\x1b[4%p1%dm",
SetFgBg: "\x1b[3%p1%d;4%p2%dm",
ResetFgBg: "\x1b[39;49m",
PadChar: "\x00",
AltChars: "++,,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~",
EnterAcs: "\x0e",
ExitAcs: "\x0f",
EnableAcs: "\x1b(B\x1b)0",
StrikeThrough: "\x1b[9m",
Mouse: "\x1b[M",
SetCursor: "\x1b[%i%p1%d;%p2%dH",
CursorBack1: "\b",
CursorUp1: "\x1bM",
KeyUp: "\x1bOA",
KeyDown: "\x1bOB",
KeyRight: "\x1bOC",
KeyLeft: "\x1bOD",
KeyInsert: "\x1b[2~",
KeyDelete: "\x1b[3~",
KeyBackspace: "\x7f",
KeyHome: "\x1b[1~",
KeyEnd: "\x1b[4~",
KeyPgUp: "\x1b[5~",
KeyPgDn: "\x1b[6~",
KeyF1: "\x1bOP",
KeyF2: "\x1bOQ",
KeyF3: "\x1bOR",
KeyF4: "\x1bOS",
KeyF5: "\x1b[15~",
KeyF6: "\x1b[17~",
KeyF7: "\x1b[18~",
KeyF8: "\x1b[19~",
KeyF9: "\x1b[20~",
KeyF10: "\x1b[21~",
KeyF11: "\x1b[23~",
KeyF12: "\x1b[24~",
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
DoubleUnderline: "\x1b[4:2m",
CurlyUnderline: "\x1b[4:3m",
DottedUnderline: "\x1b[4:4m",
DashedUnderline: "\x1b[4:5m",
})
}

View File

@ -234,6 +234,11 @@ type Terminfo struct {
DisableFocusReporting string
DisableAutoMargin string // smam
EnableAutoMargin string // rmam
DoubleUnderline string // Smulx with param 2
CurlyUnderline string // Smulx with param 3
DottedUnderline string // Smulx with param 4
DashedUnderline string // Smulx with param 5
XTermLike bool // (XT) has XTerm extensions
}
const (

View File

@ -65,5 +65,6 @@ func init() {
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
XTermLike: true,
})
}

View File

@ -68,6 +68,7 @@ func init() {
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
XTermLike: true,
})
// xterm with 88 colors
@ -131,6 +132,7 @@ func init() {
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
XTermLike: true,
})
// xterm with 256 colors
@ -194,5 +196,6 @@ func init() {
KeyBacktab: "\x1b[Z",
Modifiers: 1,
AutoMargin: true,
XTermLike: true,
})
}

View File

@ -67,5 +67,9 @@ func init() {
Modifiers: 1,
TrueColor: true,
AutoMargin: true,
DoubleUnderline: "\x1b[4:2m",
CurlyUnderline: "\x1b[4:3m",
DottedUnderline: "\x1b[4:4m",
DashedUnderline: "\x1b[4:5m",
})
}

View File

@ -154,6 +154,10 @@ type tScreen struct {
setWinSize string
enableFocus string
disableFocus string
doubleUnder string
curlyUnder string
dottedUnder string
dashedUnder string
cursorStyles map[CursorStyle]string
cursorStyle CursorStyle
saved *term.State
@ -338,7 +342,7 @@ func (t *tScreen) prepareBracketedPaste() {
t.disablePaste = t.ti.DisablePaste
t.prepareKey(keyPasteStart, t.ti.PasteStart)
t.prepareKey(keyPasteEnd, t.ti.PasteEnd)
} else if t.ti.Mouse != "" {
} else if t.ti.Mouse != "" || t.ti.XTermLike {
t.enablePaste = "\x1b[?2004h"
t.disablePaste = "\x1b[?2004l"
t.prepareKey(keyPasteStart, "\x1b[200~")
@ -346,6 +350,30 @@ func (t *tScreen) prepareBracketedPaste() {
}
}
func (t *tScreen) prepareUnderlines() {
if t.ti.DoubleUnderline != "" {
t.doubleUnder = t.ti.DoubleUnderline
} else if t.ti.XTermLike {
t.doubleUnder = "\x1b[4:2m"
}
if t.ti.CurlyUnderline != "" {
t.curlyUnder = t.ti.CurlyUnderline
} else {
t.curlyUnder = "\x1b[4:3m"
}
if t.ti.DottedUnderline != "" {
t.dottedUnder = t.ti.DottedUnderline
} else {
t.dottedUnder = "\x1b[4:4m"
}
if t.ti.DashedUnderline != "" {
t.dashedUnder = t.ti.DashedUnderline
} else {
t.dashedUnder = "\x1b[4:5m"
}
// Still TODO: Underline Color
}
func (t *tScreen) prepareExtendedOSC() {
// Linux is a special beast - because it has a mouse entry, but does
// not swallow these OSC commands properly.
@ -397,7 +425,7 @@ func (t *tScreen) prepareCursorStyles() {
CursorStyleBlinkingBar: t.ti.CursorBlinkingBar,
CursorStyleSteadyBar: t.ti.CursorSteadyBar,
}
} else if t.ti.Mouse != "" {
} else if t.ti.Mouse != "" || t.ti.XTermLike {
t.cursorStyles = map[CursorStyle]string{
CursorStyleDefault: "\x1b[0 q",
CursorStyleBlinkingBlock: "\x1b[1 q",
@ -408,6 +436,8 @@ func (t *tScreen) prepareCursorStyles() {
CursorStyleSteadyBar: "\x1b[6 q",
}
}
// Still TODO: Cursor Color
}
func (t *tScreen) prepareKey(key Key, val string) {
@ -550,6 +580,7 @@ func (t *tScreen) prepareKeys() {
t.prepareXtermModifiers()
t.prepareBracketedPaste()
t.prepareCursorStyles()
t.prepareUnderlines()
t.prepareExtendedOSC()
outer:
@ -750,8 +781,17 @@ func (t *tScreen) drawCell(x, y int) int {
if attrs&AttrBold != 0 {
t.TPuts(ti.Bold)
}
if attrs&AttrUnderline != 0 {
t.TPuts(ti.Underline)
if attrs&(AttrUnderline|AttrDoubleUnderline|AttrCurlyUnderline|AttrDottedUnderline|AttrDashedUnderline) != 0 {
t.TPuts(ti.Underline) // to ensure everyone gets at least a basic underline
if (attrs & AttrDoubleUnderline) != 0 {
t.TPuts(t.doubleUnder)
} else if (attrs & AttrCurlyUnderline) != 0 {
t.TPuts(t.curlyUnder)
} else if (attrs & AttrDottedUnderline) != 0 {
t.TPuts(t.dottedUnder)
} else if (attrs & AttrDashedUnderline) != 0 {
t.TPuts(t.dashedUnder)
}
}
if attrs&AttrReverse != 0 {
t.TPuts(ti.Reverse)

View File

@ -1,4 +1,4 @@
// Copyright 2023 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.
@ -12,202 +12,258 @@
// See the License for the specific language governing permissions and
// limitations under the License.
const wasmFilePath = "main.wasm"
const term = document.getElementById("terminal")
var width = 80; var height = 24
const wasmFilePath = "main.wasm";
const term = document.getElementById("terminal");
var width = 80;
var height = 24;
const beepAudio = new Audio("beep.wav");
var cx = -1; var cy = -1
var cursorClass = "cursor-blinking-block"
var cx = -1;
var cy = -1;
var cursorClass = "cursor-blinking-block";
var content // {data: row[height], dirty: bool}
var content; // {data: row[height], dirty: bool}
// row = {data: element[width], previous: span}
// dirty/[previous being null] indicates if previous (or entire terminal) needs to be recalculated.
// dirty is true/null if terminal/previous need to be re-calculated/shown
function initialize() {
resize(width, height) // initialize content
show() // then show the screen
resize(width, height); // initialize content
show(); // then show the screen
}
function resize(w, h) {
width = w;
height = h;
content = { data: new Array(height), dirty: true };
for (let i = 0; i < height; i++) {
content.data[i] = { data: new Array(width), previous: null };
}
width = w
height = h
content = {data: new Array(height), dirty: true}
for (let i = 0; i < height; i++) {
content.data[i] = {data: new Array(width), previous: null}
}
clearScreen()
clearScreen();
}
function clearScreen(fg, bg) {
if (fg) { term.style.color = intToHex(fg) }
if (bg) { term.style.backgroundColor = intToHex(bg) }
if (fg) {
term.style.color = intToHex(fg);
}
if (bg) {
term.style.backgroundColor = intToHex(bg);
}
content.dirty = true
for (let i = 0; i < height; i++) {
content.data[i].previous = null // we set the row to be recalculated later
for (let j = 0; j < width; j++) {
content.data[i].data[j] = document.createTextNode(" ") // set the entire row to spaces.
}
content.dirty = true;
for (let i = 0; i < height; i++) {
content.data[i].previous = null; // we set the row to be recalculated later
for (let j = 0; j < width; j++) {
content.data[i].data[j] = document.createTextNode(" "); // set the entire row to spaces.
}
}
}
function drawCell(x, y, mainc, combc, fg, bg, attrs) {
var combString = String.fromCharCode(mainc)
combc.forEach(char => {combString += String.fromCharCode(char)});
var combString = String.fromCharCode(mainc);
combc.forEach((char) => {
combString += String.fromCharCode(char);
});
var span = document.createElement("span")
var use = false
var span = document.createElement("span");
var use = false;
if ((attrs & (1<<2)) != 0) { // reverse video
var temp = bg
bg = fg
fg = temp
use = true
if ((attrs & (1 << 2)) != 0) {
// reverse video
var temp = bg;
bg = fg;
fg = temp;
use = true;
}
if (fg != -1) {
span.style.color = intToHex(fg);
use = true;
}
if (bg != -1) {
span.style.backgroundColor = intToHex(bg);
use = true;
}
// NB: these has to be updated if Attrs.go changes
if (attrs != 0) {
use = true;
if ((attrs & 1) != 0) {
span.classList.add("bold");
}
if (fg != -1) { span.style.color = intToHex(fg); use = true }
if (bg != -1) { span.style.backgroundColor = intToHex(bg); use = true }
if (attrs != 0) {
use = true
if ((attrs & 1) != 0) { span.classList.add("bold") }
if ((attrs & (1<<1)) != 0) { span.classList.add("blink") }
if ((attrs & (1<<3)) != 0) { span.classList.add("underline") }
if ((attrs & (1<<4)) != 0) { span.classList.add("dim") }
if ((attrs & (1<<5)) != 0) { span.classList.add("italic") }
if ((attrs & (1<<6)) != 0) { span.classList.add("strikethrough") }
if ((attrs & (1 << 1)) != 0) {
span.classList.add("blink");
}
if ((attrs & (1 << 3)) != 0) {
span.classList.add("underline");
}
if ((attrs & (1 << 4)) != 0) {
span.classList.add("dim");
}
if ((attrs & (1 << 5)) != 0) {
span.classList.add("italic");
}
if ((attrs & (1 << 6)) != 0) {
span.classList.add("strikethrough");
}
if ((attrs & (1 << 7)) != 0) {
span.classList.add("double_underline");
}
if ((attrs & (1 << 8)) != 0) {
span.classList.add("curly_underline");
}
if ((attrs & (1 << 9)) != 0) {
span.classList.add("dotted_underline");
}
if ((attrs & (1 << 10)) != 0) {
span.classList.add("dashed_underline");
}
}
var textnode = document.createTextNode(combString)
span.appendChild(textnode)
var textnode = document.createTextNode(combString);
span.appendChild(textnode);
content.dirty = true // invalidate terminal- new cell
content.data[y].previous = null // invalidate row- new row
content.data[y].data[x] = use ? span : textnode
content.dirty = true; // invalidate terminal- new cell
content.data[y].previous = null; // invalidate row- new row
content.data[y].data[x] = use ? span : textnode;
}
function show() {
if (!content.dirty) {
return // no new draws; no need to update
if (!content.dirty) {
return; // no new draws; no need to update
}
displayCursor();
term.innerHTML = "";
content.data.forEach((row) => {
if (row.previous == null) {
row.previous = document.createElement("span");
row.data.forEach((c) => {
row.previous.appendChild(c);
});
row.previous.appendChild(document.createTextNode("\n"));
}
term.appendChild(row.previous);
});
displayCursor()
term.innerHTML = ""
content.data.forEach(row => {
if (row.previous == null) {
row.previous = document.createElement("span")
row.data.forEach(c => {
row.previous.appendChild(c)
})
row.previous.appendChild(document.createTextNode("\n"))
}
term.appendChild(row.previous)
})
content.dirty = false
content.dirty = false;
}
function showCursor(x, y) {
content.dirty = true
content.dirty = true;
if (!(cx < 0 || cy < 0)) { // if original position is a valid cursor position
content.data[cy].previous = null;
if (content.data[cy].data[cx].classList) {
content.data[cy].data[cx].classList.remove(cursorClass)
}
if (!(cx < 0 || cy < 0)) {
// if original position is a valid cursor position
content.data[cy].previous = null;
if (content.data[cy].data[cx].classList) {
content.data[cy].data[cx].classList.remove(cursorClass);
}
}
cx = x
cy = y
cx = x;
cy = y;
}
function displayCursor() {
content.dirty = true
content.dirty = true;
if (!(cx < 0 || cy < 0)) { // if new position is a valid cursor position
content.data[cy].previous = null;
if (!(cx < 0 || cy < 0)) {
// if new position is a valid cursor position
content.data[cy].previous = null;
if (!content.data[cy].data[cx].classList) {
var span = document.createElement("span")
span.appendChild(content.data[cy].data[cx])
content.data[cy].data[cx] = span
}
content.data[cy].data[cx].classList.add(cursorClass)
if (!content.data[cy].data[cx].classList) {
var span = document.createElement("span");
span.appendChild(content.data[cy].data[cx]);
content.data[cy].data[cx] = span;
}
content.data[cy].data[cx].classList.add(cursorClass);
}
}
function setCursorStyle(newClass) {
if (newClass == cursorClass) {
return
if (newClass == cursorClass) {
return;
}
if (!(cx < 0 || cy < 0)) {
// mark cursor row as dirty; new class has been applied to (cx, cy)
content.dirty = true;
content.data[cy].previous = null;
if (content.data[cy].data[cx].classList) {
content.data[cy].data[cx].classList.remove(cursorClass);
}
if (!(cx < 0 || cy < 0)) {
// mark cursor row as dirty; new class has been applied to (cx, cy)
content.dirty = true
content.data[cy].previous = null
// adding the new class will be dealt with when displayCursor() is called
}
if (content.data[cy].data[cx].classList) {
content.data[cy].data[cx].classList.remove(cursorClass)
}
// adding the new class will be dealt with when displayCursor() is called
}
cursorClass = newClass
cursorClass = newClass;
}
function beep() {
beepAudio.currentTime = 0;
beepAudio.play();
beepAudio.currentTime = 0;
beepAudio.play();
}
function intToHex(n) {
return "#" + n.toString(16).padStart(6, '0')
return "#" + n.toString(16).padStart(6, "0");
}
initialize()
initialize();
let fontwidth = term.clientWidth / width
let fontheight = term.clientHeight / height
let fontwidth = term.clientWidth / width;
let fontheight = term.clientHeight / height;
document.addEventListener("keydown", e => {
onKeyEvent(e.key, e.shiftKey, e.altKey, e.ctrlKey, e.metaKey)
})
document.addEventListener("keydown", (e) => {
onKeyEvent(e.key, e.shiftKey, e.altKey, e.ctrlKey, e.metaKey);
});
term.addEventListener("click", e => {
onMouseClick(Math.min((e.offsetX / fontwidth) | 0, width-1), Math.min((e.offsetY / fontheight) | 0, height-1), e.which, e.shiftKey, e.altKey, e.ctrlKey)
})
term.addEventListener("click", (e) => {
onMouseClick(
Math.min((e.offsetX / fontwidth) | 0, width - 1),
Math.min((e.offsetY / fontheight) | 0, height - 1),
e.which,
e.shiftKey,
e.altKey,
e.ctrlKey
);
});
term.addEventListener("mousemove", e => {
onMouseMove(Math.min((e.offsetX / fontwidth) | 0, width-1), Math.min((e.offsetY / fontheight) | 0, height-1), e.which, e.shiftKey, e.altKey, e.ctrlKey)
})
term.addEventListener("mousemove", (e) => {
onMouseMove(
Math.min((e.offsetX / fontwidth) | 0, width - 1),
Math.min((e.offsetY / fontheight) | 0, height - 1),
e.which,
e.shiftKey,
e.altKey,
e.ctrlKey
);
});
term.addEventListener("focus", e => {
onFocus(true)
})
term.addEventListener("focus", (e) => {
onFocus(true);
});
term.addEventListener("blur", e => {
onFocus(false)
})
term.tabIndex = 0
term.addEventListener("blur", (e) => {
onFocus(false);
});
term.tabIndex = 0;
document.addEventListener("paste", e => {
e.preventDefault();
var text = (e.originalEvent || e).clipboardData.getData('text/plain');
onPaste(true)
for (let i = 0; i < text.length; i++) {
onKeyEvent(text.charAt(i), false, false, false, false)
}
onPaste(false)
document.addEventListener("paste", (e) => {
e.preventDefault();
var text = (e.originalEvent || e).clipboardData.getData("text/plain");
onPaste(true);
for (let i = 0; i < text.length; i++) {
onKeyEvent(text.charAt(i), false, false, false, false);
}
onPaste(false);
});
const go = new Go();
WebAssembly.instantiateStreaming(fetch(wasmFilePath), go.importObject).then((result) => {
WebAssembly.instantiateStreaming(fetch(wasmFilePath), go.importObject).then(
(result) => {
go.run(result.instance);
});
}
);

View File

@ -1,66 +1,116 @@
* {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-family: "Menlo", "Andale Mono", "Courier New", Monospace;
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-family: "Menlo", "Andale Mono", "Courier New", Monospace;
}
#terminal {
background-color: black;
color: green;
display: inline-block;
background-color: black;
color: green;
display: inline-block;
/* Copy paste! */
user-select: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
/* Copy paste! */
user-select: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
/* Style attributes */
.bold { font-weight: bold; }
.bold {
font-weight: bold;
}
.blink { animation: blinker 1s step-start infinite; }
.blink {
animation: blinker 1s step-start infinite;
}
.underline { text-decoration: underline; }
.underline {
text-decoration: underline;
}
.dim { filter: brightness(50) }
.dim {
filter: brightness(50);
}
.italic { font-style: italic; }
.italic {
font-style: italic;
}
.strikethrough { text-decoration: line-through; }
.strikethrough {
text-decoration: line-through;
}
.double_underline {
text-decoration: underline double;
}
.curly_underline {
text-decoration: underline wavy;
}
.dotted_underline {
text-decoration: underline dotted;
}
.dashed_underline {
text-decoration: underline dashed;
}
/* Cursor styles */
.cursor-steady-block { background-color: lightgrey !important; }
.cursor-blinking-block { animation: blinking-block 1s step-start infinite !important; }
@keyframes blinking-block { 50% { background-color: lightgrey; } }
.cursor-steady-underline { text-decoration: underline lightgrey !important; }
.cursor-blinking-underline { animation: blinking-underline 1s step-start infinite !important; }
@keyframes blinking-underline { 50% { text-decoration: underline lightgrey; } }
.cursor-steady-bar { margin-left: -2px; }
.cursor-steady-bar:before {
content: ' ';
width: 2px;
background-color: lightgrey !important;
display: inline-block;
.cursor-steady-block {
background-color: lightgrey !important;
}
.cursor-blinking-block {
animation: blinking-block 1s step-start infinite !important;
}
@keyframes blinking-block {
50% {
background-color: lightgrey;
}
}
.cursor-steady-underline {
text-decoration: underline lightgrey !important;
}
.cursor-blinking-underline {
animation: blinking-underline 1s step-start infinite !important;
}
@keyframes blinking-underline {
50% {
text-decoration: underline lightgrey;
}
}
.cursor-steady-bar {
margin-left: -2px;
}
.cursor-steady-bar:before {
content: " ";
width: 2px;
background-color: lightgrey !important;
display: inline-block;
}
.cursor-blinking-bar {
margin-left: -2px;
}
.cursor-blinking-bar { margin-left: -2px; }
.cursor-blinking-bar:before {
content: ' ';
width: 2px;
background-color: lightgrey !important;
display: inline-block;
animation: blinker 1s step-start infinite;
content: " ";
width: 2px;
background-color: lightgrey !important;
display: inline-block;
animation: blinker 1s step-start infinite;
}
/* General animations */
@keyframes blinker {
50% { opacity: 0; }
50% {
opacity: 0;
}
}

View File

@ -1,4 +1,4 @@
// Copyright 2023 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.