kopia lustrzana https://github.com/browsh-org/browsh
295 wiersze
7.2 KiB
Go
295 wiersze
7.2 KiB
Go
package browsh
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
|
|
"github.com/gdamore/tcell"
|
|
"github.com/go-errors/errors"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
var (
|
|
screen tcell.Screen
|
|
uiHeight = 2
|
|
// IsMonochromeMode decides whether to render the TTY in full colour or monochrome
|
|
IsMonochromeMode = false
|
|
|
|
errNormalExit = errors.New("normal")
|
|
)
|
|
|
|
func setupTcell() {
|
|
var err error
|
|
if err = screen.Init(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
IsMonochromeMode = viper.GetBool("monochrome")
|
|
screen.EnableMouse()
|
|
screen.Clear()
|
|
}
|
|
|
|
func sendTtySize() {
|
|
width, height := screen.Size()
|
|
urlInputBox.Width = width
|
|
sendMessageToWebExtension(fmt.Sprintf("/tty_size,%d,%d", width, height))
|
|
}
|
|
|
|
// This is basically a proxy that listens to STDIN and forwards all relevant input
|
|
// from the user to the webextension. So keyboard, mouse, terminal resizes, etc.
|
|
func readStdin() {
|
|
for {
|
|
ev := screen.PollEvent()
|
|
switch ev := ev.(type) {
|
|
case *tcell.EventKey:
|
|
handleUserKeyPress(ev)
|
|
case *tcell.EventResize:
|
|
handleTTYResize()
|
|
case *tcell.EventMouse:
|
|
handleMouseEvent(ev)
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleUserKeyPress(ev *tcell.EventKey) {
|
|
if CurrentTab == nil {
|
|
if ev.Key() == tcell.KeyCtrlQ {
|
|
quitBrowsh()
|
|
}
|
|
return
|
|
}
|
|
switch ev.Key() {
|
|
case tcell.KeyCtrlQ:
|
|
quitBrowsh()
|
|
case tcell.KeyCtrlL:
|
|
urlBarFocusToggle()
|
|
case tcell.KeyCtrlT:
|
|
createNewEmptyTab()
|
|
case tcell.KeyCtrlU:
|
|
if !isNewEmptyTabActive() {
|
|
sendMessageToWebExtension("/new_tab,view-source:" + CurrentTab.URI)
|
|
}
|
|
case tcell.KeyCtrlW:
|
|
removeTab(CurrentTab.ID)
|
|
case tcell.KeyBackspace, tcell.KeyBackspace2:
|
|
if activeInputBox == nil {
|
|
sendMessageToWebExtension("/tab_command,/history_back")
|
|
}
|
|
}
|
|
if ev.Rune() == 'm' && ev.Modifiers() == 4 {
|
|
toggleMonochromeMode()
|
|
}
|
|
if ev.Key() == 279 && ev.Modifiers() == 0 {
|
|
// F1 key
|
|
openHelpTab()
|
|
}
|
|
if isKey("tty.keys.next-tab", ev) {
|
|
nextTab()
|
|
}
|
|
if !urlInputBox.isActive {
|
|
forwardKeyPress(ev)
|
|
}
|
|
if activeInputBox != nil {
|
|
handleInputBoxInput(ev)
|
|
} else {
|
|
handleScrolling(ev) // TODO: shouldn't you be able to still use mouse scrolling?
|
|
}
|
|
}
|
|
|
|
func isKey(userKey string, ev *tcell.EventKey) bool {
|
|
key := viper.GetStringSlice(userKey)
|
|
runeMatch := []rune(key[0])[0] == ev.Rune()
|
|
intKey, _ := strconv.Atoi(key[1])
|
|
keyCodeMatch := intKey == int(ev.Key())
|
|
modifierKey, _ := strconv.Atoi(key[2])
|
|
modifierMatch := modifierKey == int(ev.Modifiers())
|
|
return runeMatch && keyCodeMatch && modifierMatch
|
|
}
|
|
|
|
func quitBrowsh() {
|
|
if !viper.GetBool("firefox.use-existing") {
|
|
quitFirefox()
|
|
}
|
|
Shutdown(errNormalExit)
|
|
}
|
|
|
|
func toggleMonochromeMode() {
|
|
IsMonochromeMode = !IsMonochromeMode
|
|
}
|
|
|
|
func openHelpTab() {
|
|
sendMessageToWebExtension("/new_tab,https://www.brow.sh/docs/introduction/")
|
|
}
|
|
|
|
func forwardKeyPress(ev *tcell.EventKey) {
|
|
if isMultiLineEnter(ev) {
|
|
return
|
|
}
|
|
eventMap := map[string]interface{}{
|
|
"key": int(ev.Key()),
|
|
"char": string(ev.Rune()),
|
|
"mod": int(ev.Modifiers()),
|
|
}
|
|
marshalled, _ := json.Marshal(eventMap)
|
|
sendMessageToWebExtension("/stdin," + string(marshalled))
|
|
}
|
|
|
|
// Allow user to use ENTER key without triggering submission on multiline input
|
|
// boxes.
|
|
func isMultiLineEnter(ev *tcell.EventKey) bool {
|
|
if activeInputBox == nil {
|
|
return false
|
|
}
|
|
return activeInputBox.isMultiLine() && ev.Key() == 13 && ev.Modifiers() != 4
|
|
}
|
|
|
|
func handleScrolling(ev *tcell.EventKey) {
|
|
yScrollOriginal := CurrentTab.frame.yScroll
|
|
_, height := screen.Size()
|
|
height -= uiHeight
|
|
if ev.Key() == tcell.KeyUp {
|
|
CurrentTab.frame.yScroll -= 2
|
|
}
|
|
if ev.Key() == tcell.KeyDown {
|
|
CurrentTab.frame.yScroll += 2
|
|
}
|
|
if ev.Key() == tcell.KeyPgUp {
|
|
CurrentTab.frame.yScroll -= height
|
|
}
|
|
if ev.Key() == tcell.KeyPgDn {
|
|
CurrentTab.frame.yScroll += height
|
|
}
|
|
CurrentTab.frame.limitScroll(height)
|
|
sendMessageToWebExtension(
|
|
fmt.Sprintf(
|
|
"/tab_command,/scroll_status,%d,%d",
|
|
CurrentTab.frame.xScroll,
|
|
CurrentTab.frame.yScroll*2))
|
|
if CurrentTab.frame.yScroll != yScrollOriginal {
|
|
renderCurrentTabWindow()
|
|
}
|
|
}
|
|
|
|
func handleMouseEvent(ev *tcell.EventMouse) {
|
|
if CurrentTab == nil {
|
|
return
|
|
}
|
|
x, y := ev.Position()
|
|
xInFrame := x + CurrentTab.frame.xScroll
|
|
yInFrame := y - uiHeight + CurrentTab.frame.yScroll
|
|
button := ev.Buttons()
|
|
if button == tcell.WheelUp || button == tcell.WheelDown {
|
|
handleMouseScroll(button)
|
|
}
|
|
if button == 1 {
|
|
CurrentTab.frame.maybeFocusInputBox(xInFrame, yInFrame)
|
|
}
|
|
eventMap := map[string]interface{}{
|
|
"button": int(button),
|
|
"mouse_x": int(xInFrame),
|
|
"mouse_y": int(yInFrame),
|
|
"modifiers": int(ev.Modifiers()),
|
|
}
|
|
marshalled, _ := json.Marshal(eventMap)
|
|
sendMessageToWebExtension("/stdin," + string(marshalled))
|
|
}
|
|
|
|
func handleMouseScroll(scrollType tcell.ButtonMask) {
|
|
yScrollOriginal := CurrentTab.frame.yScroll
|
|
_, height := screen.Size()
|
|
height -= uiHeight
|
|
if scrollType == tcell.WheelUp {
|
|
CurrentTab.frame.yScroll -= 1
|
|
} else if scrollType == tcell.WheelDown {
|
|
CurrentTab.frame.yScroll += 1
|
|
}
|
|
CurrentTab.frame.limitScroll(height)
|
|
sendMessageToWebExtension(
|
|
fmt.Sprintf(
|
|
"/tab_command,/scroll_status,%d,%d",
|
|
CurrentTab.frame.xScroll,
|
|
CurrentTab.frame.yScroll*2))
|
|
if CurrentTab.frame.yScroll != yScrollOriginal {
|
|
renderCurrentTabWindow()
|
|
}
|
|
}
|
|
|
|
func handleTTYResize() {
|
|
width, _ := screen.Size()
|
|
urlInputBox.Width = width
|
|
screen.Sync()
|
|
sendTtySize()
|
|
}
|
|
|
|
// Tcell uses a buffer to collect screen updates on, it only actually sends
|
|
// ANSI rendering commands to the terminal when we tell it to. And even then it
|
|
// will try to minimise rendering commands by only rendering parts of the terminal
|
|
// that have changed.
|
|
func renderCurrentTabWindow() {
|
|
var currentCell cell
|
|
styling := tcell.StyleDefault
|
|
var runeChars []rune
|
|
width, height := screen.Size()
|
|
if CurrentTab == nil || CurrentTab.frame.cells == nil {
|
|
return
|
|
}
|
|
CurrentTab.frame.overlayInputBoxContent()
|
|
for y := 0; y < height-uiHeight; y++ {
|
|
for x := 0; x < width; x++ {
|
|
currentCell = getCell(x, y)
|
|
runeChars = currentCell.character
|
|
// TODO: do this is in isCharacterTransparent()
|
|
if len(runeChars) == 0 {
|
|
continue
|
|
}
|
|
if IsMonochromeMode {
|
|
styling = styling.Foreground(tcell.ColorWhite)
|
|
styling = styling.Background(tcell.ColorBlack)
|
|
if runeChars[0] == '▄' {
|
|
runeChars[0] = ' '
|
|
}
|
|
} else {
|
|
styling = styling.Foreground(currentCell.fgColour)
|
|
styling = styling.Background(currentCell.bgColour)
|
|
}
|
|
screen.SetCell(x, y+uiHeight, styling, runeChars[0])
|
|
}
|
|
}
|
|
if activeInputBox != nil {
|
|
activeInputBox.renderCursor()
|
|
}
|
|
overlayPageStatusMessage()
|
|
overlayCallToSupport()
|
|
screen.Show()
|
|
}
|
|
|
|
func getCell(x, y int) cell {
|
|
var currentCell cell
|
|
var ok bool
|
|
frame := &CurrentTab.frame
|
|
index := ((y + frame.yScroll) * frame.totalWidth) + (x + frame.xScroll)
|
|
if currentCell, ok = frame.cells.load(index); !ok {
|
|
fgColour, bgColour := getHatchedCellColours(x)
|
|
currentCell = cell{
|
|
fgColour: fgColour,
|
|
bgColour: bgColour,
|
|
character: []rune("▄"),
|
|
}
|
|
}
|
|
return currentCell
|
|
}
|
|
|
|
func getHatchedCellColours(x int) (tcell.Color, tcell.Color) {
|
|
var bgColour, fgColour tcell.Color
|
|
if x%2 == 0 {
|
|
bgColour = tcell.NewHexColor(0xa9a9a9)
|
|
fgColour = tcell.NewHexColor(0x797979)
|
|
} else {
|
|
bgColour = tcell.NewHexColor(0x797979)
|
|
fgColour = tcell.NewHexColor(0xa9a9a9)
|
|
}
|
|
return fgColour, bgColour
|
|
}
|