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

added webfiles directory

updated README.md
organized functions into original order
This commit is contained in:
Ahoys123 2023-01-02 17:29:40 -08:00 committed by Garrett D'Amore
parent 49cc9d21db
commit b86f5127ac
8 changed files with 594 additions and 317 deletions

View File

@ -264,7 +264,21 @@ Windows console mode applications are supported.
Modern console applications like ConEmu and the Windows 10 terminal, Modern console applications like ConEmu and the Windows 10 terminal,
support all the good features (resize, mouse tracking, etc.) support all the good features (resize, mouse tracking, etc.)
### Plan9, WASM, and others ### WASM
WASM needs special build flags and extra files in the same directory in order to work. You can build it by executing
```sh
GOOS=js GOARCH=wasm build -o yourfile.wasm
```
You also need 5 other files. Four (`tcell.html`, `tcell.js`, `termstyle.css`, and `beep.wav`) are provided in the `webfiles` directory. The last one, `wasm_exec.js`, can be copied from GOROOT into the current directory by executing
```sh
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ./
```
It is recomended use an iframe if you want to embed the app into a webpage:
```html
<iframe src="tcell.html" title="Tcell app"></iframe>
```
### Plan9 and others
These platforms won't work, but compilation stubs are supplied These platforms won't work, but compilation stubs are supplied
for folks that want to include parts of this in software for those for folks that want to include parts of this in software for those
@ -279,4 +293,4 @@ please let me know. PRs are especially welcome.
_Tcell_ is absolutely free, but if you want to obtain commercial, professional support, there are options. _Tcell_ is absolutely free, but if you want to obtain commercial, professional support, there are options.
- [TideLift](https://tidelift.com/) subscriptions include support for _Tcell_, as well as many other open source packages. - [TideLift](https://tidelift.com/) subscriptions include support for _Tcell_, as well as many other open source packages.
- [Staysail Systems Inc.](mailto:info@staysail.tech) offers direct support, and custom development around _Tcell_ on an hourly basis. - [Staysail Systems Inc.](mailto:info@staysail.tech) offers direct support, and custom development around _Tcell_ on an hourly basis.

View File

@ -35,18 +35,6 @@ func (t *tScreen) SetStyle(style Style) {
func (t *tScreen) Clear() { func (t *tScreen) Clear() {
t.Fill(' ', t.style) t.Fill(' ', t.style)
t.Lock()
t.clear = true
w, h := t.cells.Size()
// because we are going to clear (see t.clear) in the next cycle,
// let's also unmark the dirty bit so that we don't waste cycles
// drawing things that are already dealt with via the clear escape sequence.
for row := 0; row < h; row++ {
for col := 0; col < w; col++ {
t.cells.SetDirty(col, row, false)
}
}
t.Unlock()
} }
func (t *tScreen) Fill(r rune, style Style) { func (t *tScreen) Fill(r rune, style Style) {
@ -57,30 +45,6 @@ func (t *tScreen) Fill(r rune, style Style) {
t.Unlock() t.Unlock()
} }
func (t *tScreen) EnableMouse(flags ...MouseFlags) {
var f MouseFlags
flagsPresent := false
for _, flag := range flags {
f |= flag
flagsPresent = true
}
if !flagsPresent {
f = MouseMotionEvents | MouseDragEvents | MouseButtonEvents
}
t.Lock()
t.mouseFlags = f
t.enableMouse(f)
t.Unlock()
}
func (t *tScreen) DisableMouse() {
t.Lock()
t.mouseFlags = 0
t.enableMouse(0)
t.Unlock()
}
func (t *tScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) { func (t *tScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) {
t.Lock() t.Lock()
if !t.fini { if !t.fini {
@ -117,6 +81,30 @@ func (t *tScreen) Show() {
t.Unlock() t.Unlock()
} }
func (t *tScreen) EnableMouse(flags ...MouseFlags) {
var f MouseFlags
flagsPresent := false
for _, flag := range flags {
f |= flag
flagsPresent = true
}
if !flagsPresent {
f = MouseMotionEvents | MouseDragEvents | MouseButtonEvents
}
t.Lock()
t.mouseFlags = f
t.enableMouse(f)
t.Unlock()
}
func (t *tScreen) DisableMouse() {
t.Lock()
t.mouseFlags = 0
t.enableMouse(0)
t.Unlock()
}
func (t *tScreen) EnablePaste() { func (t *tScreen) EnablePaste() {
t.Lock() t.Lock()
t.pasteEnabled = true t.pasteEnabled = true
@ -225,3 +213,5 @@ func (t *tScreen) UnregisterRuneFallback(orig rune) {
delete(t.fallback, orig) delete(t.fallback, orig)
t.Unlock() t.Unlock()
} }
func (t *tScreen) Resize(int, int, int, int) {}

View File

@ -544,6 +544,36 @@ func (t *tScreen) finish() {
t.finalize() t.finalize()
} }
func (t *tScreen) encodeRune(r rune, buf []byte) []byte {
nb := make([]byte, 6)
ob := make([]byte, 6)
num := utf8.EncodeRune(ob, r)
ob = ob[:num]
dst := 0
var err error
if enc := t.encoder; enc != nil {
enc.Reset()
dst, _, err = enc.Transform(nb, ob, true)
}
if err != nil || dst == 0 || nb[0] == '\x1a' {
// Combining characters are elided
if len(buf) == 0 {
if acs, ok := t.acs[r]; ok {
buf = append(buf, []byte(acs)...)
} else if fb, ok := t.fallback[r]; ok {
buf = append(buf, []byte(fb)...)
} else {
buf = append(buf, '?')
}
}
} else {
buf = append(buf, nb[:dst]...)
}
return buf
}
func (t *tScreen) sendFgBg(fg Color, bg Color, attr AttrMask) AttrMask { func (t *tScreen) sendFgBg(fg Color, bg Color, attr AttrMask) AttrMask {
ti := t.ti ti := t.ti
if ti.Colors == 0 { if ti.Colors == 0 {
@ -625,47 +655,6 @@ func (t *tScreen) sendFgBg(fg Color, bg Color, attr AttrMask) AttrMask {
return attr return attr
} }
func (t *tScreen) draw() {
// clobber cursor position, because we're going to change it all
t.cx = -1
t.cy = -1
// make no style assumptions
t.curstyle = styleInvalid
t.buf.Reset()
t.buffering = true
defer func() {
t.buffering = false
}()
// hide the cursor while we move stuff around
t.hideCursor()
if t.clear {
t.clearScreen()
}
for y := 0; y < t.h; y++ {
for x := 0; x < t.w; x++ {
width := t.drawCell(x, y)
if width > 1 {
if x+1 < t.w {
// this is necessary so that if we ever
// go back to drawing that cell, we
// actually will *draw* it.
t.cells.SetDirty(x+1, y, true)
}
}
x += width - 1
}
}
// restore the cursor
t.showCursor()
_, _ = t.buf.WriteTo(t.tty)
}
func (t *tScreen) drawCell(x, y int) int { func (t *tScreen) drawCell(x, y int) int {
ti := t.ti ti := t.ti
@ -780,6 +769,19 @@ func (t *tScreen) drawCell(x, y int) int {
return width return width
} }
func (t *tScreen) ShowCursor(x, y int) {
t.Lock()
t.cursorx = x
t.cursory = y
t.Unlock()
}
func (t *tScreen) SetCursorStyle(cs CursorStyle) {
t.Lock()
t.cursorStyle = cs
t.Unlock()
}
func (t *tScreen) showCursor() { func (t *tScreen) showCursor() {
x, y := t.cursorx, t.cursory x, y := t.cursorx, t.cursory
@ -799,22 +801,6 @@ func (t *tScreen) showCursor() {
t.cy = y t.cy = y
} }
func (t *tScreen) resize() {
if w, h, e := t.tty.WindowSize(); e == nil {
if w != t.w || h != t.h {
t.cx = -1
t.cy = -1
t.cells.Resize(w, h)
t.cells.Invalidate()
t.h = h
t.w = w
ev := NewEventResize(w, h)
_ = t.PostEvent(ev)
}
}
}
// writeString sends a string to the terminal. The string is sent as-is and // writeString sends a string to the terminal. The string is sent as-is and
// this function does not expand inline padding indications (of the form // this function does not expand inline padding indications (of the form
// $<[delay]> where [delay] is msec). In order to have these expanded, use // $<[delay]> where [delay] is msec). In order to have these expanded, use
@ -858,6 +844,47 @@ func (t *tScreen) hideCursor() {
} }
} }
func (t *tScreen) draw() {
// clobber cursor position, because we're going to change it all
t.cx = -1
t.cy = -1
// make no style assumptions
t.curstyle = styleInvalid
t.buf.Reset()
t.buffering = true
defer func() {
t.buffering = false
}()
// hide the cursor while we move stuff around
t.hideCursor()
if t.clear {
t.clearScreen()
}
for y := 0; y < t.h; y++ {
for x := 0; x < t.w; x++ {
width := t.drawCell(x, y)
if width > 1 {
if x+1 < t.w {
// this is necessary so that if we ever
// go back to drawing that cell, we
// actually will *draw* it.
t.cells.SetDirty(x+1, y, true)
}
}
x += width - 1
}
}
// restore the cursor
t.showCursor()
_, _ = t.buf.WriteTo(t.tty)
}
func (t *tScreen) enableMouse(f MouseFlags) { func (t *tScreen) enableMouse(f MouseFlags) {
// Rather than using terminfo to find mouse escape sequences, we rely on the fact that // Rather than using terminfo to find mouse escape sequences, we rely on the fact that
// pretty much *every* terminal that supports mouse tracking follows the // pretty much *every* terminal that supports mouse tracking follows the
@ -893,6 +920,22 @@ func (t *tScreen) enablePasting(on bool) {
} }
} }
func (t *tScreen) resize() {
if w, h, e := t.tty.WindowSize(); e == nil {
if w != t.w || h != t.h {
t.cx = -1
t.cy = -1
t.cells.Resize(w, h)
t.cells.Invalidate()
t.h = h
t.w = w
ev := NewEventResize(w, h)
_ = t.PostEvent(ev)
}
}
}
func (t *tScreen) Colors() int { func (t *tScreen) Colors() int {
// this doesn't change, no need for lock // this doesn't change, no need for lock
if t.truecolor { if t.truecolor {
@ -972,36 +1015,6 @@ func (t *tScreen) buildAcsMap() {
} }
} }
func (t *tScreen) encodeRune(r rune, buf []byte) []byte {
nb := make([]byte, 6)
ob := make([]byte, 6)
num := utf8.EncodeRune(ob, r)
ob = ob[:num]
dst := 0
var err error
if enc := t.encoder; enc != nil {
enc.Reset()
dst, _, err = enc.Transform(nb, ob, true)
}
if err != nil || dst == 0 || nb[0] == '\x1a' {
// Combining characters are elided
if len(buf) == 0 {
if acs, ok := t.acs[r]; ok {
buf = append(buf, []byte(acs)...)
} else if fb, ok := t.fallback[r]; ok {
buf = append(buf, []byte(fb)...)
} else {
buf = append(buf, '?')
}
}
} else {
buf = append(buf, nb[:dst]...)
}
return buf
}
// buildMouseEvent returns an event based on the supplied coordinates and button // buildMouseEvent returns an event based on the supplied coordinates and button
// state. Note that the screen's mouse button state is updated based on the // state. Note that the screen's mouse button state is updated based on the
// input to this function (i.e. it mutates the receiver). // input to this function (i.e. it mutates the receiver).
@ -1483,17 +1496,8 @@ func (t *tScreen) inputLoop(stopQ chan struct{}) {
} }
} }
func (t *tScreen) ShowCursor(x, y int) { func (t *tScreen) CharacterSet() string {
t.Lock() return t.charset
t.cursorx = x
t.cursory = y
t.Unlock()
}
func (t *tScreen) SetCursorStyle(cs CursorStyle) {
t.Lock()
t.cursorStyle = cs
t.Unlock()
} }
func (t *tScreen) CanDisplay(r rune, checkFallbacks bool) bool { func (t *tScreen) CanDisplay(r rune, checkFallbacks bool) bool {
@ -1523,6 +1527,17 @@ func (t *tScreen) CanDisplay(r rune, checkFallbacks bool) bool {
return false return false
} }
func (t *tScreen) HasMouse() bool {
return len(t.mouse) != 0
}
func (t *tScreen) HasKey(k Key) bool {
if k == KeyRune {
return true
}
return t.keyexist[k]
}
func (t *tScreen) SetSize(w, h int) { func (t *tScreen) SetSize(w, h int) {
if t.setWinSize != "" { if t.setWinSize != "" {
t.TPuts(t.ti.TParm(t.setWinSize, w, h)) t.TPuts(t.ti.TParm(t.setWinSize, w, h))
@ -1540,17 +1555,6 @@ func (t *tScreen) Resume() error {
return t.engage() return t.engage()
} }
func (t *tScreen) CharacterSet() string {
return t.charset
}
func (t *tScreen) HasKey(k Key) bool {
if k == KeyRune {
return true
}
return t.keyexist[k]
}
// engage is used to place the terminal in raw mode and establish screen size, etc. // engage is used to place the terminal in raw mode and establish screen size, etc.
// Think of this is as tcell "engaging" the clutch, as it's going to be driving the // Think of this is as tcell "engaging" the clutch, as it's going to be driving the
// terminal interface. // terminal interface.
@ -1633,12 +1637,6 @@ func (t *tScreen) disengage() {
_ = t.tty.Stop() _ = t.tty.Stop()
} }
func (t *tScreen) HasMouse() bool {
return len(t.mouse) != 0
}
func (t *tScreen) Resize(int, int, int, int) {}
// Beep emits a beep to the terminal. // Beep emits a beep to the terminal.
func (t *tScreen) Beep() error { func (t *tScreen) Beep() error {
t.writeString(string(byte(7))) t.writeString(string(byte(7)))

View File

@ -12,7 +12,10 @@ import (
) )
func NewTerminfoScreen() (Screen, error) { func NewTerminfoScreen() (Screen, error) {
return &tScreen{}, nil t := &tScreen{}
t.fallback = make(map[rune]string)
return t, nil
} }
type tScreen struct { type tScreen struct {
@ -38,8 +41,7 @@ type tScreen struct {
} }
func (t *tScreen) Init() error { func (t *tScreen) Init() error {
t.w, t.h = 80, 24 t.w, t.h = 80, 24 // default for html as of now
t.fallback = make(map[rune]string)
t.evch = make(chan Event, 10) t.evch = make(chan Event, 10)
t.quit = make(chan struct{}) t.quit = make(chan struct{})
@ -59,6 +61,127 @@ func (t *tScreen) Fini() {
close(t.quit) close(t.quit)
} }
func (t *tScreen) drawCell(x, y int) int {
mainc, combc, style, width := t.cells.GetContent(x, y)
if !t.cells.Dirty(x, y) {
return width
}
if style == StyleDefault {
style = t.style
}
fg, bg := style.fg.Hex(), style.bg.Hex()
var combcarr []interface{} = make([]interface{}, len(combc))
for i, c := range combc {
combcarr[i] = c
}
t.cells.SetDirty(x, y, false)
js.Global().Call("drawCell", x, y, mainc, combcarr, fg, bg, int(style.attrs))
return width
}
func (t *tScreen) ShowCursor(x, y int) {
t.Lock()
js.Global().Call("showCursor", x, y)
t.Unlock()
}
func (t *tScreen) SetCursorStyle(cs CursorStyle) {
t.Lock()
js.Global().Call("setCursorStyle", curStyleClasses[cs])
t.Unlock()
}
func (t *tScreen) clearScreen() {
js.Global().Call("clearScreen", t.style.fg.Hex(), t.style.bg.Hex())
t.clear = false
}
func (t *tScreen) draw() {
if t.clear {
t.clearScreen()
}
for y := 0; y < t.h; y++ {
for x := 0; x < t.w; x++ {
width := t.drawCell(x, y)
x += width - 1
}
}
js.Global().Call("show")
}
func (t *tScreen) enableMouse(f MouseFlags) {
if f&MouseButtonEvents != 0 {
js.Global().Set("onMouseClick", js.FuncOf(t.onMouseEvent))
} else {
js.Global().Set("onMouseClick", js.FuncOf(t.unset))
}
if f&MouseDragEvents != 0 || f&MouseMotionEvents != 0 {
js.Global().Set("onMouseMove", js.FuncOf(t.onMouseEvent))
} else {
js.Global().Set("onMouseMove", js.FuncOf(t.unset))
}
}
func (t *tScreen) enablePasting(on bool) {
if on {
js.Global().Set("onPaste", js.FuncOf(t.onPaste))
} else {
js.Global().Set("onPaste", js.FuncOf(t.unset))
}
}
// resize does nothing, as asking the web window to resize
// without a specified width or height will cause no change.
func (t *tScreen) resize() {}
func (t *tScreen) Colors() int {
return 16777216 // 256 ^ 3
}
func (t *tScreen) onMouseEvent(this js.Value, args []js.Value) interface{} {
mod := ModNone
button := ButtonNone
switch args[2].Int() {
case 0:
if t.mouseFlags&MouseMotionEvents == 0 {
// don't want this event! is a mouse motion event, but user has asked not.
return nil
}
button = ButtonNone
case 1:
button = Button1
case 2:
button = Button3 // Note we prefer to treat right as button 2
case 3:
button = Button2 // And the middle button as button 3
}
if args[3].Bool() { // mod shift
mod |= ModShift
}
if args[4].Bool() { // mod alt
mod |= ModAlt
}
if args[5].Bool() { // mod ctrl
mod |= ModCtrl
}
t.PostEventWait(NewEventMouse(args[0].Int(), args[1].Int(), button, mod))
return nil
}
func (t *tScreen) onKeyEvent(this js.Value, args []js.Value) interface{} { func (t *tScreen) onKeyEvent(this js.Value, args []js.Value) interface{} {
key := args[0].String() key := args[0].String()
@ -109,128 +232,36 @@ func (t *tScreen) onPaste(this js.Value, args []js.Value) interface{} {
return nil return nil
} }
func (t *tScreen) enableMouse(f MouseFlags) { // unset is a dummy function for js when we want nothing to
if f&MouseButtonEvents != 0 { // happen when javascript calls a function (for example, when
js.Global().Set("onMouseClick", js.FuncOf(t.onMouseEvent)) // mouse input is disabled, when onMouseEvent() is called from
} else { // js, it redirects here and does nothing).
js.Global().Set("onMouseClick", js.FuncOf(t.unset))
}
if f&MouseDragEvents != 0 || f&MouseMotionEvents != 0 {
js.Global().Set("onMouseMove", js.FuncOf(t.onMouseEvent))
} else {
js.Global().Set("onMouseMove", js.FuncOf(t.unset))
}
}
func (t *tScreen) onMouseEvent(this js.Value, args []js.Value) interface{} {
mod := ModNone
button := ButtonNone
switch args[2].Int() {
case 0:
if t.mouseFlags&MouseMotionEvents == 0 {
// don't want this event! is a mouse motion event, but user has asked not.
return nil
}
button = ButtonNone
case 1:
button = Button1
case 2:
button = Button3 // Note we prefer to treat right as button 2
case 3:
button = Button2 // And the middle button as button 3
}
if args[3].Bool() { // mod shift
mod |= ModShift
}
if args[4].Bool() { // mod alt
mod |= ModAlt
}
if args[5].Bool() { // mod ctrl
mod |= ModCtrl
}
t.PostEventWait(NewEventMouse(args[0].Int(), args[1].Int(), button, mod))
return nil
}
func (t *tScreen) unset(this js.Value, args []js.Value) interface{} { func (t *tScreen) unset(this js.Value, args []js.Value) interface{} {
return nil return nil
} }
func (t *tScreen) Colors() int {
return 16777216 // 256 ^ 3
}
// resize does nothing, as asking the web window to resize
// without a specified width or height will cause no change.
func (t *tScreen) resize() {}
func (t *tScreen) draw() {
if t.clear {
t.clearScreen()
}
for y := 0; y < t.h; y++ {
for x := 0; x < t.w; x++ {
width := t.drawCell(x, y)
x += width - 1
}
}
js.Global().Call("show")
}
func (t *tScreen) clearScreen() {
js.Global().Call("clearScreen", t.style.fg.Hex(), t.style.bg.Hex())
t.clear = false
}
func (t *tScreen) drawCell(x, y int) int {
mainc, combc, style, width := t.cells.GetContent(x, y)
if !t.cells.Dirty(x, y) {
return width
}
if style == StyleDefault {
style = t.style
}
fg, bg := style.fg.Hex(), style.bg.Hex()
var combcarr []interface{} = make([]interface{}, len(combc))
for i, c := range combc {
combcarr[i] = c
}
t.cells.SetDirty(x, y, false)
js.Global().Call("drawCell", x, y, mainc, combcarr, fg, bg, int(style.attrs))
return width
}
func (t *tScreen) ShowCursor(x, y int) {
t.Lock()
js.Global().Call("showCursor", x, y)
t.Unlock()
}
func (t *tScreen) SetCursorStyle(cs CursorStyle) {
t.Lock()
js.Global().Call("setCursorStyle", curStyleClasses[cs])
t.Unlock()
}
func (t *tScreen) CharacterSet() string { func (t *tScreen) CharacterSet() string {
return "UTF-8" return "UTF-8"
} }
func (t *tScreen) CanDisplay(r rune, checkFallbacks bool) bool { func (t *tScreen) CanDisplay(r rune, checkFallbacks bool) bool {
if utf8.ValidRune(r) {
return true
}
if !checkFallbacks {
return false
}
if _, ok := t.fallback[r]; ok {
return true
}
return false
}
func (t *tScreen) HasMouse() bool {
return true
}
func (t *tScreen) HasKey(k Key) bool {
return true return true
} }
@ -247,6 +278,7 @@ func (t *tScreen) SetSize(w, h int) {
} }
// Suspend simply pauses all input and output, and clears the screen. // Suspend simply pauses all input and output, and clears the screen.
// There isn't a "default terminal" to go back to.
func (t *tScreen) Suspend() error { func (t *tScreen) Suspend() error {
t.Lock() t.Lock()
if !t.running { if !t.running {
@ -278,26 +310,6 @@ func (t *tScreen) Resume() error {
return nil return nil
} }
func (t *tScreen) HasKey(k Key) bool {
return true
}
func (t *tScreen) enablePasting(on bool) {
if on {
js.Global().Set("onPaste", js.FuncOf(t.onPaste))
} else {
js.Global().Set("onPaste", js.FuncOf(t.unset))
}
}
func (t *tScreen) HasMouse() bool {
return true
}
func (t *tScreen) Resize(x int, y int, w int, h int) {
t.SetSize(w, h)
}
func (t *tScreen) Beep() error { func (t *tScreen) Beep() error {
js.Global().Call("beep") js.Global().Call("beep")
return nil return nil
@ -396,34 +408,34 @@ var WebKeyNames = map[string]Key{
"F62": KeyF62, "F62": KeyF62,
"F63": KeyF63, "F63": KeyF63,
"F64": KeyF64, "F64": KeyF64,
"Ctrl-a": KeyCtrlA, // not supported by HTML- reported as key + modifier ctrl "Ctrl-a": KeyCtrlA, // not reported by HTML- need to do special check
"Ctrl-b": KeyCtrlB, // not supported by HTML- reported as key + modifier ctrl "Ctrl-b": KeyCtrlB, // not reported by HTML- need to do special check
"Ctrl-c": KeyCtrlC, // not supported by HTML- reported as key + modifier ctrl "Ctrl-c": KeyCtrlC, // not reported by HTML- need to do special check
"Ctrl-d": KeyCtrlD, // not supported by HTML- reported as key + modifier ctrl "Ctrl-d": KeyCtrlD, // not reported by HTML- need to do special check
"Ctrl-e": KeyCtrlE, // not supported by HTML- reported as key + modifier ctrl "Ctrl-e": KeyCtrlE, // not reported by HTML- need to do special check
"Ctrl-f": KeyCtrlF, // not supported by HTML- reported as key + modifier ctrl "Ctrl-f": KeyCtrlF, // not reported by HTML- need to do special check
"Ctrl-g": KeyCtrlG, // not supported by HTML- reported as key + modifier ctrl "Ctrl-g": KeyCtrlG, // not reported by HTML- need to do special check
"Ctrl-j": KeyCtrlJ, // not supported by HTML- reported as key + modifier ctrl "Ctrl-j": KeyCtrlJ, // not reported by HTML- need to do special check
"Ctrl-k": KeyCtrlK, // not supported by HTML- reported as key + modifier ctrl "Ctrl-k": KeyCtrlK, // not reported by HTML- need to do special check
"Ctrl-l": KeyCtrlL, // not supported by HTML- reported as key + modifier ctrl "Ctrl-l": KeyCtrlL, // not reported by HTML- need to do special check
"Ctrl-n": KeyCtrlN, // not supported by HTML- reported as key + modifier ctrl "Ctrl-n": KeyCtrlN, // not reported by HTML- need to do special check
"Ctrl-o": KeyCtrlO, // not supported by HTML- reported as key + modifier ctrl "Ctrl-o": KeyCtrlO, // not reported by HTML- need to do special check
"Ctrl-p": KeyCtrlP, // not supported by HTML- reported as key + modifier ctrl "Ctrl-p": KeyCtrlP, // not reported by HTML- need to do special check
"Ctrl-q": KeyCtrlQ, // not supported by HTML- reported as key + modifier ctrl "Ctrl-q": KeyCtrlQ, // not reported by HTML- need to do special check
"Ctrl-r": KeyCtrlR, // not supported by HTML- reported as key + modifier ctrl "Ctrl-r": KeyCtrlR, // not reported by HTML- need to do special check
"Ctrl-s": KeyCtrlS, // not supported by HTML- reported as key + modifier ctrl "Ctrl-s": KeyCtrlS, // not reported by HTML- need to do special check
"Ctrl-t": KeyCtrlT, // not supported by HTML- reported as key + modifier ctrl "Ctrl-t": KeyCtrlT, // not reported by HTML- need to do special check
"Ctrl-u": KeyCtrlU, // not supported by HTML- reported as key + modifier ctrl "Ctrl-u": KeyCtrlU, // not reported by HTML- need to do special check
"Ctrl-v": KeyCtrlV, // not supported by HTML- reported as key + modifier ctrl "Ctrl-v": KeyCtrlV, // not reported by HTML- need to do special check
"Ctrl-w": KeyCtrlW, // not supported by HTML- reported as key + modifier ctrl "Ctrl-w": KeyCtrlW, // not reported by HTML- need to do special check
"Ctrl-x": KeyCtrlX, // not supported by HTML- reported as key + modifier ctrl "Ctrl-x": KeyCtrlX, // not reported by HTML- need to do special check
"Ctrl-y": KeyCtrlY, // not supported by HTML- reported as key + modifier ctrl "Ctrl-y": KeyCtrlY, // not reported by HTML- need to do special check
"Ctrl-z": KeyCtrlZ, // not supported by HTML- reported as key + modifier ctrl "Ctrl-z": KeyCtrlZ, // not reported by HTML- need to do special check
"Ctrl- ": KeyCtrlSpace, // not supported by HTML- reported as key + modifier ctrl "Ctrl- ": KeyCtrlSpace, // not reported by HTML- need to do special check
"Ctrl-_": KeyCtrlUnderscore, // not supported by HTML- reported as key + modifier ctrl "Ctrl-_": KeyCtrlUnderscore, // not reported by HTML- need to do special check
"Ctrl-]": KeyCtrlRightSq, // not supported by HTML- reported as key + modifier ctrl "Ctrl-]": KeyCtrlRightSq, // not reported by HTML- need to do special check
"Ctrl-\\": KeyCtrlBackslash, // not supported by HTML- reported as key + modifier ctrl "Ctrl-\\": KeyCtrlBackslash, // not reported by HTML- need to do special check
"Ctrl-^": KeyCtrlCarat, // not supported by HTML- reported as key + modifier ctrl "Ctrl-^": KeyCtrlCarat, // not reported by HTML- need to do special check
} }
var curStyleClasses = map[CursorStyle]string{ var curStyleClasses = map[CursorStyle]string{

BIN
webfiles/beep.wav Normal file

Binary file not shown.

13
webfiles/tcell.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Tcell</title>
<script src="wasm_exec.js"></script>
<link rel="stylesheet" href="termstyle.css">
</head>
<body>
<pre id="terminal"></pre>
<script src="tcell.js"></script>
</body>
</html>

183
webfiles/tcell.js Normal file
View File

@ -0,0 +1,183 @@
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 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 recaclulated.
// dirty is true/null if terminal/previous need to be re-calculated/shown
function initialize() {
resize(width, height) // intialize 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}
}
clearScreen()
}
function clearScreen(fg, 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 recacluated 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 span = document.createElement("span")
var use = false
if (fg) { span.style.color = intToHex(fg); use = true }
if (bg) { 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<<2)) != 0) { span.classList.add("reverse") }
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") }
}
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
}
function show() {
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)
})
content.dirty = false
}
function showCursor(x, y) {
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)
}
}
cx = x
cy = y
}
function displayCursor() {
content.dirty = true
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)
}
}
function setCursorStyle(newClass) {
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)
}
// adding the new class will be dealt with when displayCursor() is called
}
cursorClass = newClass
}
function beep() {
beepAudio.currentTime = 0;
beepAudio.play();
}
function intToHex(n) {
return "#" + n.toString(16).padStart(6, '0')
}
initialize()
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)
})
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)
})
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("main.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});

67
webfiles/termstyle.css Normal file
View File

@ -0,0 +1,67 @@
* {
margin: 0;
padding: 0;
border: 0;
outline: 0;
}
#terminal {
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;
}
/* Style attributes */
.bold { font-weight: bold; }
.blink { animation: blinker 1s step-start infinite; }
.reverse { unicode-bidi: bidi-override; direction: rtl; }
.underline { text-decoration: underline; }
.dim { filter: brightness(50) }
.italic { font-style: italic; }
.strikethrough { text-decoration: line-through; }
/* 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-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;
}
/* General animations */
@keyframes blinker {
50% { opacity: 0; }
}