Compare commits
27 Commits
Author | SHA1 | Date |
---|---|---|
Tobias Gläßer | b2ade39223 | |
Tobias Gläßer | 935983725c | |
Thomas Buckley-Houston | b780a79f2e | |
Thomas Buckley-Houston | 714cad8615 | |
Tobias Gläßer | 7a622b230b | |
Thomas Buckley-Houston | ed79db0510 | |
Thomas Buckley-Houston | 8161ea34e6 | |
Thomas Buckley-Houston | 59d2c31acf | |
Thomas Buckley-Houston | eae72e94a6 | |
Thomas Buckley-Houston | e03923394c | |
Tobias Gläßer | 49eebee0c9 | |
Tobias Gläßer | d3fff67c61 | |
Tobias Gläßer | bd5c30640d | |
Tobias Gläßer | 0b7d1dc7ef | |
Tobias Gläßer | 15c7b45b6f | |
Tobias Gläßer | 7b7e6bc308 | |
Tobias Gläßer | 86acac617b | |
Tobias Gläßer | 631483bbd9 | |
Tobias Gläßer | c794f10287 | |
Tobias Gläßer | fac1af7f2a | |
Tobias Gläßer | 8fc15f3301 | |
Tobias Gläßer | ebc8de95b9 | |
Tobias Gläßer | dc9533969f | |
Tobias Gläßer | ca30b7722b | |
Tobias Gläßer | baf808f35d | |
Tobias Gläßer | 3b246ff796 | |
Tobias Gläßer | ee1291b41a |
|
@ -7,8 +7,10 @@ interfacer/vendor
|
|||
interfacer/dist
|
||||
interfacer/interfacer
|
||||
interfacer/browsh
|
||||
interfacer/debug
|
||||
webextension.go
|
||||
webext/node_modules
|
||||
webext/dist/*
|
||||
webext/manifest.json~
|
||||
dist
|
||||
*.xpi
|
||||
|
|
|
@ -40,8 +40,8 @@ script:
|
|||
- cd $REPO_ROOT/interfacer && go test test/tty/*.go -v -ginkgo.slowSpecThreshold=30 -ginkgo.flakeAttempts=3
|
||||
- cd $REPO_ROOT/interfacer && go test test/http-server/*.go -v -ginkgo.slowSpecThreshold=30 -ginkgo.flakeAttempts=3
|
||||
after_failure:
|
||||
- cat $REPO_ROOT/interfacer/test/tty/debug.log
|
||||
- cat $REPO_ROOT/interfacer/test/http-server/debug.log
|
||||
- cat $REPO_ROOT/interfacer/test/tty/debug.log | curl -F 'f:1=<-' ix.io
|
||||
- cat $REPO_ROOT/interfacer/test/http-server/debug.log | curl -F 'f:1=<-' ix.io
|
||||
after_success:
|
||||
- $REPO_ROOT/contrib/release_if_new_version.sh
|
||||
|
||||
|
|
|
@ -155,6 +155,7 @@ func TTYStart(injectedScreen tcell.Screen) {
|
|||
Log("Starting Browsh CLI client")
|
||||
go readStdin()
|
||||
startWebSocketServer()
|
||||
setupLinkHints()
|
||||
}
|
||||
|
||||
func toInt(char string) int {
|
||||
|
@ -181,6 +182,12 @@ func ttyEntry() {
|
|||
// from tcell.
|
||||
os.Setenv("TERM", "xterm-truecolor")
|
||||
}
|
||||
// This is for getting the clipboard (github.com/atotto/clipboard) to work
|
||||
// with the applications xsel and xclip on systems with an X display server.
|
||||
if os.Getenv("DISPLAY") == "" {
|
||||
os.Setenv("DISPLAY", ":0")
|
||||
}
|
||||
|
||||
realScreen, err := tcell.NewScreen()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
|
|
|
@ -88,6 +88,8 @@ func handleWebextensionCommand(message []byte) {
|
|||
}
|
||||
case "/screenshot":
|
||||
saveScreenshot(parts[1])
|
||||
case "/link_hints":
|
||||
parseJSONLinkHints(strings.Join(parts[1:], ","))
|
||||
default:
|
||||
Log("WEBEXT: " + string(message))
|
||||
}
|
||||
|
|
|
@ -73,6 +73,66 @@ func getFirefoxProfilePath() string {
|
|||
func setDefaults() {
|
||||
// Temporary experimental configurable keybindings
|
||||
viper.SetDefault("tty.keys.next-tab", []string{"\u001c", "28", "2"})
|
||||
|
||||
// Vim commands
|
||||
vimKeyMap["normal gg"] = "scrollToTop"
|
||||
vimKeyMap["normal G"] = "scrollToBottom"
|
||||
vimKeyMap["normal j"] = "scrollDown"
|
||||
vimKeyMap["normal k"] = "scrollUp"
|
||||
vimKeyMap["normal h"] = "scrollLeft"
|
||||
vimKeyMap["normal l"] = "scrollRight"
|
||||
vimKeyMap["normal d"] = "scrollHalfPageDown"
|
||||
vimKeyMap["normal <C-d>"] = "scrollHalfPageDown"
|
||||
vimKeyMap["normal u"] = "scrollHalfPageUp"
|
||||
vimKeyMap["normal <C-u>"] = "scrollHalfPageUp"
|
||||
vimKeyMap["normal e"] = "editURL"
|
||||
vimKeyMap["normal ge"] = "editURL"
|
||||
vimKeyMap["normal gE"] = "editURLInNewTab"
|
||||
vimKeyMap["normal H"] = "historyBack"
|
||||
vimKeyMap["normal L"] = "historyForward"
|
||||
vimKeyMap["normal J"] = "prevTab"
|
||||
vimKeyMap["normal K"] = "nextTab"
|
||||
vimKeyMap["normal r"] = "reload"
|
||||
vimKeyMap["normal xx"] = "removeTab"
|
||||
vimKeyMap["normal X"] = "restoreTab"
|
||||
vimKeyMap["normal t"] = "newTab"
|
||||
vimKeyMap["normal T"] = "searchForTab"
|
||||
vimKeyMap["normal /"] = "findMode"
|
||||
vimKeyMap["normal n"] = "findNext"
|
||||
vimKeyMap["normal N"] = "findPrevious"
|
||||
vimKeyMap["normal g0"] = "firstTab"
|
||||
vimKeyMap["normal g$"] = "lastTab"
|
||||
vimKeyMap["normal gu"] = "urlUp"
|
||||
vimKeyMap["normal gU"] = "urlRoot"
|
||||
vimKeyMap["normal <<"] = "moveTabLeft"
|
||||
vimKeyMap["normal >>"] = "moveTabRight"
|
||||
vimKeyMap["normal ^"] = "previouslyVisitedTab"
|
||||
vimKeyMap["normal m"] = "makeMark"
|
||||
vimKeyMap["normal '"] = "gotoMark"
|
||||
vimKeyMap["normal i"] = "insertMode"
|
||||
vimKeyMap["normal I"] = "insertModeHard"
|
||||
vimKeyMap["normal yy"] = "copyURL"
|
||||
vimKeyMap["normal p"] = "openClipboardURL"
|
||||
vimKeyMap["normal P"] = "openClipboardURLInNewTab"
|
||||
vimKeyMap["normal gi"] = "focusFirstTextInput"
|
||||
vimKeyMap["normal f"] = "openLinkInCurrentTab"
|
||||
vimKeyMap["normal F"] = "openLinkInNewTab"
|
||||
vimKeyMap["normal <M-f>"] = "openMultipleLinksInNewTab"
|
||||
vimKeyMap["normal yf"] = "copyLinkURL"
|
||||
vimKeyMap["normal [["] = "followLinkLabeledPrevious"
|
||||
vimKeyMap["normal ]]"] = "followLinkLabeledNext"
|
||||
vimKeyMap["normal yt"] = "duplicateTab"
|
||||
vimKeyMap["normal v"] = "visualMode"
|
||||
vimKeyMap["normal ?"] = "viewHelp"
|
||||
vimKeyMap["caret v"] = "visualMode"
|
||||
vimKeyMap["caret h"] = "moveCaretLeft"
|
||||
vimKeyMap["caret l"] = "moveCaretRight"
|
||||
vimKeyMap["caret j"] = "moveCaretDown"
|
||||
vimKeyMap["caret k"] = "moveCaretUp"
|
||||
vimKeyMap["caret <Enter>"] = "clickAtCaretPosition"
|
||||
vimKeyMap["visual c"] = "caretMode"
|
||||
vimKeyMap["visual o"] = "swapVisualModeCursorPosition"
|
||||
vimKeyMap["visual y"] = "copyVisualModeSelection"
|
||||
}
|
||||
|
||||
func loadConfig() {
|
||||
|
|
|
@ -160,9 +160,9 @@ func (f *frame) updateInputBoxes(incoming incomingFrameText) {
|
|||
inputBox := f.inputBoxes[incomingInputBox.ID]
|
||||
inputBox.X = incomingInputBox.X
|
||||
// TODO: Why do we have to add the 1 to the y coord??
|
||||
inputBox.Y = (incomingInputBox.Y + 1) / 2
|
||||
inputBox.Y = (incomingInputBox.Y + 0) / 2
|
||||
inputBox.Width = incomingInputBox.Width
|
||||
inputBox.Height = incomingInputBox.Height / 2
|
||||
inputBox.Height = (incomingInputBox.Height / 2) + 1
|
||||
inputBox.FgColour = incomingInputBox.FgColour
|
||||
inputBox.TagName = incomingInputBox.TagName
|
||||
inputBox.Type = incomingInputBox.Type
|
||||
|
@ -312,7 +312,7 @@ func (f *frame) maybeFocusInputBox(x, y int) {
|
|||
left := inputBox.X
|
||||
right := inputBox.X + inputBox.Width
|
||||
if x >= left && x < right && y >= top && y < bottom {
|
||||
urlBarFocus(false)
|
||||
URLBarFocus(false)
|
||||
inputBox.isActive = true
|
||||
activeInputBox = inputBox
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ var activeInputBox *inputBox
|
|||
// A box into which you can enter text. Generally will be forwarded to a standard
|
||||
// HTML input box in the real browser.
|
||||
//
|
||||
// Note that tcell alreay has some ready-made code in its 'views' concept for
|
||||
// Note that tcell already has some ready-made code in its 'views' concept for
|
||||
// dealing with input areas. However, at the time of writing it wasn't well documented,
|
||||
// so it was unclear how easy it would be to integrate the requirements of Browsh's
|
||||
// input boxes - namely overlaying them onto the existing graphics and having them
|
||||
|
@ -181,7 +181,7 @@ func (i *inputBox) handleEnterKey(modifier tcell.ModMask) {
|
|||
} else {
|
||||
sendMessageToWebExtension("/url_bar," + string(i.text))
|
||||
}
|
||||
urlBarFocus(false)
|
||||
URLBarFocus(false)
|
||||
}
|
||||
if i.isMultiLine() && modifier != tcell.ModAlt {
|
||||
i.cursorInsertRune([]rune("\n")[0])
|
||||
|
|
|
@ -18,6 +18,9 @@ var tabsOrder []int
|
|||
// the tab being deleted, so we need to keep track of all deleted IDs
|
||||
var tabsDeleted []int
|
||||
|
||||
// ID of the tab that was active before the current one
|
||||
var previouslyVisitedTabID int
|
||||
|
||||
// A single tab synced from the browser
|
||||
type tab struct {
|
||||
ID int `json:"id"`
|
||||
|
@ -61,6 +64,10 @@ func newTab(id int) {
|
|||
}
|
||||
}
|
||||
|
||||
func restoreTab() {
|
||||
sendMessageToWebExtension("/restore_tab")
|
||||
}
|
||||
|
||||
func removeTab(id int) {
|
||||
if len(Tabs) == 1 {
|
||||
quitBrowsh()
|
||||
|
@ -84,23 +91,63 @@ func removeTabIDfromTabsOrder(id int) {
|
|||
}
|
||||
}
|
||||
|
||||
func moveTabLeft(id int) {
|
||||
// If the tab ID is already completely to the left in the tab order
|
||||
// there's nothing to do
|
||||
if tabsOrder[0] == id {
|
||||
return
|
||||
}
|
||||
|
||||
for i, tabID := range tabsOrder {
|
||||
if tabID == id {
|
||||
tabsOrder[i-1], tabsOrder[i] = tabsOrder[i], tabsOrder[i-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func moveTabRight(id int) {
|
||||
// If the tab ID is already completely to the right in the tab order
|
||||
// there's nothing to do
|
||||
if tabsOrder[len(tabsOrder)-1] == id {
|
||||
return
|
||||
}
|
||||
|
||||
for i, tabID := range tabsOrder {
|
||||
if tabID == id {
|
||||
tabsOrder[i+1], tabsOrder[i] = tabsOrder[i], tabsOrder[i+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func duplicateTab(id int) {
|
||||
sendMessageToWebExtension(fmt.Sprintf("/duplicate_tab,%d", id))
|
||||
}
|
||||
|
||||
// Creating a new tab in the browser without a URI means it won't register with the
|
||||
// web extension, which means that, come the moment when we actually have a URI for the new
|
||||
// tab then we can't talk to it to tell it navigate. So we need to only create a real new
|
||||
// tab when we actually have a URL.
|
||||
func createNewEmptyTab() {
|
||||
createNewEmptyTabWithURI("")
|
||||
}
|
||||
|
||||
func createNewEmptyTabWithURI(URI string) {
|
||||
if isNewEmptyTabActive() {
|
||||
return
|
||||
}
|
||||
newTab(-1)
|
||||
tab := Tabs[-1]
|
||||
tab.Title = "New Tab"
|
||||
tab.URI = ""
|
||||
tab.URI = URI
|
||||
tab.Active = true
|
||||
CurrentTab = tab
|
||||
CurrentTab.frame.resetCells()
|
||||
renderUI()
|
||||
urlBarFocus(true)
|
||||
URLBarFocus(true)
|
||||
// Allows for typing directly at the end of URI
|
||||
urlInputBox.selectionOff()
|
||||
renderCurrentTabWindow()
|
||||
}
|
||||
|
||||
|
@ -116,15 +163,41 @@ func nextTab() {
|
|||
} else {
|
||||
i++
|
||||
}
|
||||
sendMessageToWebExtension(fmt.Sprintf("/switch_to_tab,%d", tabsOrder[i]))
|
||||
CurrentTab = Tabs[tabsOrder[i]]
|
||||
renderUI()
|
||||
renderCurrentTabWindow()
|
||||
switchToTab(tabsOrder[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func prevTab() {
|
||||
for i := 0; i < len(tabsOrder); i++ {
|
||||
if tabsOrder[i] == CurrentTab.ID {
|
||||
if i-1 < 0 {
|
||||
i = len(tabsOrder) - 1
|
||||
} else {
|
||||
i--
|
||||
}
|
||||
switchToTab(tabsOrder[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func previouslyVisitedTab() {
|
||||
if previouslyVisitedTabID == 0 {
|
||||
return
|
||||
}
|
||||
switchToTab(previouslyVisitedTabID)
|
||||
}
|
||||
|
||||
func switchToTab(num int) {
|
||||
sendMessageToWebExtension(fmt.Sprintf("/switch_to_tab,%d", num))
|
||||
previouslyVisitedTabID = CurrentTab.ID
|
||||
CurrentTab = Tabs[num]
|
||||
renderUI()
|
||||
renderCurrentTabWindow()
|
||||
}
|
||||
|
||||
func isTabPreviouslyDeleted(id int) bool {
|
||||
for i := 0; i < len(tabsDeleted); i++ {
|
||||
if tabsDeleted[i] == id {
|
||||
|
|
|
@ -5,14 +5,20 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Coordinate struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
var (
|
||||
screen tcell.Screen
|
||||
screen tcell.Screen
|
||||
// The height of the tabs and URL bar
|
||||
uiHeight = 2
|
||||
// IsMonochromeMode decides whether to render the TTY in full colour or monochrome
|
||||
IsMonochromeMode = false
|
||||
|
@ -51,7 +57,7 @@ func readStdin() {
|
|||
}
|
||||
}
|
||||
|
||||
func handleUserKeyPress(ev *tcell.EventKey) {
|
||||
func handleShortcuts(ev *tcell.EventKey) {
|
||||
if CurrentTab == nil {
|
||||
if ev.Key() == tcell.KeyCtrlQ {
|
||||
quitBrowsh()
|
||||
|
@ -82,16 +88,24 @@ func handleUserKeyPress(ev *tcell.EventKey) {
|
|||
if isKey("tty.keys.next-tab", ev) {
|
||||
nextTab()
|
||||
}
|
||||
}
|
||||
|
||||
func handleUserKeyPress(ev *tcell.EventKey) {
|
||||
if currentVimMode != insertModeHard {
|
||||
handleShortcuts(ev)
|
||||
}
|
||||
if !urlInputBox.isActive {
|
||||
forwardKeyPress(ev)
|
||||
}
|
||||
if activeInputBox != nil {
|
||||
handleInputBoxInput(ev)
|
||||
} else {
|
||||
handleVimControl(ev)
|
||||
handleScrolling(ev) // TODO: shouldn't you be able to still use mouse scrolling?
|
||||
}
|
||||
}
|
||||
|
||||
// Matches a human-readable key defintion with a Tcell event
|
||||
func isKey(userKey string, ev *tcell.EventKey) bool {
|
||||
key := viper.GetStringSlice(userKey)
|
||||
runeMatch := []rune(key[0])[0] == ev.Rune()
|
||||
|
@ -139,34 +153,66 @@ func isMultiLineEnter(ev *tcell.EventKey) bool {
|
|||
return activeInputBox.isMultiLine() && ev.Key() == 13 && ev.Modifiers() != 4
|
||||
}
|
||||
|
||||
func handleScrolling(ev *tcell.EventKey) {
|
||||
func generateLeftClickYHack(x, y int, yHack bool) {
|
||||
newMouseEvent := tcell.NewEventMouse(x, y+uiHeight, tcell.Button1, 0)
|
||||
handleMouseEventYHack(newMouseEvent, yHack)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
newMouseEvent = tcell.NewEventMouse(x, y+uiHeight, 0, 0)
|
||||
handleMouseEventYHack(newMouseEvent, yHack)
|
||||
}
|
||||
|
||||
func generateLeftClick(x, y int) {
|
||||
generateLeftClickYHack(x, y, false)
|
||||
}
|
||||
|
||||
// TODO: This isn't working for opening new tabs.
|
||||
func generateMiddleClick(x, y int) {
|
||||
newMouseEvent := tcell.NewEventMouse(x, y+uiHeight, tcell.Button2, 0)
|
||||
handleMouseEvent(newMouseEvent)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
newMouseEvent = tcell.NewEventMouse(x, y+uiHeight, 0, 0)
|
||||
handleMouseEvent(newMouseEvent)
|
||||
}
|
||||
|
||||
func doScroll(relX int, relY int) {
|
||||
doScrollAbsolute(CurrentTab.frame.xScroll+relX, CurrentTab.frame.yScroll+relY)
|
||||
}
|
||||
|
||||
func doScrollAbsolute(absX int, absY int) {
|
||||
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.yScroll = absY
|
||||
CurrentTab.frame.xScroll = absX
|
||||
|
||||
CurrentTab.frame.limitScroll(height)
|
||||
sendMessageToWebExtension(
|
||||
fmt.Sprintf(
|
||||
"/tab_command,/scroll_status,%d,%d",
|
||||
CurrentTab.frame.xScroll,
|
||||
CurrentTab.frame.yScroll*2))
|
||||
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) {
|
||||
func handleScrolling(ev *tcell.EventKey) {
|
||||
_, height := screen.Size()
|
||||
height -= uiHeight
|
||||
if ev.Key() == tcell.KeyUp {
|
||||
doScroll(0, -2)
|
||||
}
|
||||
if ev.Key() == tcell.KeyDown {
|
||||
doScroll(0, 2)
|
||||
}
|
||||
if ev.Key() == tcell.KeyPgUp {
|
||||
doScroll(0, -height)
|
||||
}
|
||||
if ev.Key() == tcell.KeyPgDn {
|
||||
doScroll(0, height)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMouseEventYHack(ev *tcell.EventMouse, yHack bool) {
|
||||
if CurrentTab == nil {
|
||||
return
|
||||
}
|
||||
|
@ -186,27 +232,24 @@ func handleMouseEvent(ev *tcell.EventMouse) {
|
|||
"mouse_y": int(yInFrame),
|
||||
"modifiers": int(ev.Modifiers()),
|
||||
}
|
||||
if yHack {
|
||||
eventMap["y_hack"] = true
|
||||
}
|
||||
marshalled, _ := json.Marshal(eventMap)
|
||||
sendMessageToWebExtension("/stdin," + string(marshalled))
|
||||
}
|
||||
|
||||
func handleMouseEvent(ev *tcell.EventMouse) {
|
||||
handleMouseEventYHack(ev, false)
|
||||
}
|
||||
|
||||
func handleMouseScroll(scrollType tcell.ButtonMask) {
|
||||
yScrollOriginal := CurrentTab.frame.yScroll
|
||||
_, height := screen.Size()
|
||||
height -= uiHeight
|
||||
if scrollType == tcell.WheelUp {
|
||||
CurrentTab.frame.yScroll -= 1
|
||||
doScroll(0, -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()
|
||||
doScroll(0, 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,6 +298,7 @@ func renderCurrentTabWindow() {
|
|||
activeInputBox.renderCursor()
|
||||
}
|
||||
overlayPageStatusMessage()
|
||||
overlayVimMode()
|
||||
overlayCallToSupport()
|
||||
screen.Show()
|
||||
}
|
||||
|
@ -275,6 +319,8 @@ func getCell(x, y int) cell {
|
|||
return currentCell
|
||||
}
|
||||
|
||||
// These are the dark and light grey squares that appear in the background to indicate that
|
||||
// nothing has been rendered there yet.
|
||||
func getHatchedCellColours(x int) (tcell.Color, tcell.Color) {
|
||||
var bgColour, fgColour tcell.Color
|
||||
if x%2 == 0 {
|
||||
|
|
|
@ -87,13 +87,15 @@ func renderURLBar() {
|
|||
|
||||
func urlBarFocusToggle() {
|
||||
if urlInputBox.isActive {
|
||||
urlBarFocus(false)
|
||||
URLBarFocus(false)
|
||||
} else {
|
||||
urlBarFocus(true)
|
||||
URLBarFocus(true)
|
||||
}
|
||||
}
|
||||
|
||||
func urlBarFocus(on bool) {
|
||||
// Set the focus of the URL bar. Also used in tests to ensure the URL bar is in fact focussed as
|
||||
// toggling doesn't guarantee that you will gain focus.
|
||||
func URLBarFocus(on bool) {
|
||||
if !on {
|
||||
activeInputBox = nil
|
||||
urlInputBox.isActive = false
|
||||
|
@ -108,6 +110,46 @@ func urlBarFocus(on bool) {
|
|||
}
|
||||
}
|
||||
|
||||
func overlayVimMode() {
|
||||
_, height := screen.Size()
|
||||
switch currentVimMode {
|
||||
case insertMode:
|
||||
writeString(0, height-1, "ins", tcell.StyleDefault)
|
||||
case insertModeHard:
|
||||
writeString(0, height-1, "INS", tcell.StyleDefault)
|
||||
case linkMode:
|
||||
writeString(0, height-1, "lnk", tcell.StyleDefault)
|
||||
case linkModeNewTab:
|
||||
writeString(0, height-1, "LNK", tcell.StyleDefault)
|
||||
case linkModeMultipleNewTab:
|
||||
writeString(0, height-1, "*LNK", tcell.StyleDefault)
|
||||
case linkModeCopy:
|
||||
writeString(0, height-1, "cp", tcell.StyleDefault)
|
||||
case visualMode:
|
||||
writeString(0, height-1, "vis", tcell.StyleDefault)
|
||||
case caretMode:
|
||||
writeString(0, height-1, "car", tcell.StyleDefault)
|
||||
writeString(caretPos.X, caretPos.Y, "#", tcell.StyleDefault)
|
||||
case findMode:
|
||||
writeString(0, height-1, "/"+findText, tcell.StyleDefault)
|
||||
case markModeMake:
|
||||
writeString(0, height-1, "mark", tcell.StyleDefault)
|
||||
case markModeGoto:
|
||||
writeString(0, height-1, "goto", tcell.StyleDefault)
|
||||
}
|
||||
|
||||
switch currentVimMode {
|
||||
case linkMode, linkModeNewTab, linkModeMultipleNewTab, linkModeCopy:
|
||||
if !linkModeWithHints {
|
||||
findAndHighlightTextOnScreen(linkText)
|
||||
}
|
||||
|
||||
if linkHintWriteStringCalls != nil {
|
||||
(*linkHintWriteStringCalls)()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func overlayPageStatusMessage() {
|
||||
_, height := screen.Size()
|
||||
writeString(0, height-1, CurrentTab.StatusMessage, tcell.StyleDefault)
|
||||
|
|
|
@ -0,0 +1,672 @@
|
|||
package browsh
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
// TODO: A little description as to the respective responsibilties of this code versus the
|
||||
// vimium.js code.
|
||||
|
||||
type vimMode int
|
||||
|
||||
const (
|
||||
normalMode vimMode = iota + 1
|
||||
insertMode
|
||||
insertModeHard
|
||||
findMode
|
||||
linkMode
|
||||
linkModeNewTab
|
||||
linkModeMultipleNewTab
|
||||
linkModeCopy
|
||||
waitMode
|
||||
visualMode
|
||||
caretMode
|
||||
markModeMake
|
||||
markModeGoto
|
||||
)
|
||||
|
||||
// TODO: What's a mark?
|
||||
type mark struct {
|
||||
tabID int
|
||||
URI string
|
||||
xScroll int
|
||||
yScroll int
|
||||
}
|
||||
|
||||
type hintRect struct {
|
||||
Bottom int `json:"bottom"`
|
||||
Top int `json:"top"`
|
||||
Left int `json:"left"`
|
||||
Right int `json:"right"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Href string `json:"href"`
|
||||
}
|
||||
|
||||
var (
|
||||
currentVimMode = normalMode
|
||||
vimKeyMap = make(map[string]string)
|
||||
keyEvents = make([]*tcell.EventKey, 0, 11)
|
||||
waitModeStartTime time.Time
|
||||
waitModeMaxMilliseconds = 1000
|
||||
findText string
|
||||
latestKeyCombination string
|
||||
// Marks
|
||||
globalMarkMap = make(map[rune]*mark)
|
||||
localMarkMap = make(map[int]map[rune]*mark)
|
||||
// Position coordinate for caret mode
|
||||
caretPos Coordinate
|
||||
// For link modes
|
||||
linkText string
|
||||
linkHintRects []hintRect
|
||||
linkHintKeys = "asdfwerxcv"
|
||||
linkHints []string
|
||||
linkHintsToRects = make(map[string]*hintRect)
|
||||
linkModeWithHints = true
|
||||
linkHintWriteStringCalls *func()
|
||||
)
|
||||
|
||||
func setupLinkHints() {
|
||||
lowerAlpha := "abcdefghijklmnopqrstuvwxyz"
|
||||
missingAlpha := lowerAlpha
|
||||
|
||||
// Use linkHintKeys first to generate link hints
|
||||
for i := 0; i < len(linkHintKeys); i++ {
|
||||
for j := 0; j < len(linkHintKeys); j++ {
|
||||
linkHints = append(linkHints, string(linkHintKeys[i])+string(linkHintKeys[j]))
|
||||
}
|
||||
missingAlpha = strings.Replace(missingAlpha, string(linkHintKeys[i]), "", -1)
|
||||
}
|
||||
|
||||
// `missingAlpha` contains all keys that aren't in `linkHintKeys`
|
||||
// we use this to generate the last link hint key combinations,
|
||||
// so this will only be used when we run out of `linkHintKeys` based
|
||||
// link hint key combinations.
|
||||
for i := 0; i < len(missingAlpha); i++ {
|
||||
for j := 0; j < len(lowerAlpha); j++ {
|
||||
linkHints = append(linkHints, string(missingAlpha[i])+string(lowerAlpha[j]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Moves the caret in CaretMode.
|
||||
// `isCaretAtBoundary` is a function that tests for the reaching of the boundaries of the given axis.
|
||||
// The axis of motion is decided by giving a reference to `caretPos.X` or `caretPos.Y` as `valRef`.
|
||||
// The step size and direction is given by the value of step.
|
||||
func moveVimCaret(isCaretAtBoundary func() bool, valRef *int, step int) {
|
||||
var prevCell, nextCell, nextNextCell cell
|
||||
var r rune
|
||||
hasNextNextCell := false
|
||||
|
||||
for isCaretAtBoundary() {
|
||||
prevCell = getCell(caretPos.X, caretPos.Y-uiHeight)
|
||||
*valRef += step
|
||||
nextCell = getCell(caretPos.X, caretPos.Y-uiHeight)
|
||||
|
||||
if isCaretAtBoundary() {
|
||||
*valRef += step
|
||||
nextNextCell = getCell(caretPos.X, caretPos.Y-uiHeight)
|
||||
*valRef -= step
|
||||
hasNextNextCell = true
|
||||
} else {
|
||||
hasNextNextCell = false
|
||||
}
|
||||
|
||||
r = nextCell.character[0]
|
||||
// Check if the next cell is different in any way
|
||||
if !reflect.DeepEqual(prevCell, nextCell) {
|
||||
if hasNextNextCell {
|
||||
// This condition should apply to the spaces between words and the like
|
||||
// Checking with unicode.isSpace() didn't give correct results for some reason
|
||||
// TODO: find out what that reason is and improve this
|
||||
if !unicode.IsLetter(r) && unicode.IsLetter(nextNextCell.character[0]) {
|
||||
continue
|
||||
}
|
||||
// If the upcoming cell is deeply equal we can continue to go forward
|
||||
if reflect.DeepEqual(nextCell, nextNextCell) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// This cell is different and other conditions for continuing don't apply
|
||||
// therefore we stop going forward.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This fails if the tab with mark.tabID doesn't exist anymore it should recreate said tab, then go to the mark's URL and position
|
||||
func gotoMark(mark *mark) {
|
||||
if CurrentTab.ID != mark.tabID {
|
||||
ensureTabExists(mark.tabID)
|
||||
switchToTab(mark.tabID)
|
||||
}
|
||||
if CurrentTab.URI != mark.URI {
|
||||
sendMessageToWebExtension("/tab_command,/url," + mark.URI)
|
||||
//sleep?
|
||||
}
|
||||
doScrollAbsolute(mark.xScroll, mark.yScroll)
|
||||
}
|
||||
|
||||
// Make a mark at the current position in the current tab
|
||||
func makeMark() *mark {
|
||||
return &mark{CurrentTab.ID, CurrentTab.URI, CurrentTab.frame.xScroll, CurrentTab.frame.yScroll}
|
||||
}
|
||||
|
||||
func goIntoWaitMode() {
|
||||
changeVimMode(waitMode)
|
||||
waitModeStartTime = time.Now()
|
||||
}
|
||||
|
||||
func updateLinkHintDisplay() {
|
||||
linkHintsToRects = make(map[string]*hintRect)
|
||||
lh := len(linkHintRects)
|
||||
var ht string
|
||||
// List of closures
|
||||
var fc []*func()
|
||||
|
||||
for i, r := range linkHintRects {
|
||||
// When the number of link hints is small enough
|
||||
// using just one key for individual link hints suffices.
|
||||
// Otherwise use the prepared link hint key combinations.
|
||||
if lh <= len(linkHintKeys) {
|
||||
ht = string(linkHintKeys[i])
|
||||
} else {
|
||||
ht = linkHints[i]
|
||||
}
|
||||
// Add the key combination ht to the linkHintsToRects map.
|
||||
// When the user presses it, we can easily lookup the
|
||||
// link hint properties associated with it.
|
||||
linkHintsToRects[ht] = &linkHintRects[i]
|
||||
|
||||
// When the first key got hit,
|
||||
// shorten the link hints accordingly
|
||||
offsetLeft := 0
|
||||
if strings.HasPrefix(ht, linkText) {
|
||||
ht = ht[len(linkText):len(ht)]
|
||||
offsetLeft = len(linkText)
|
||||
}
|
||||
|
||||
// Make copies of parameter values
|
||||
rLeftCopy, rTopCopy, htCopy := r.Left, r.Top, ht
|
||||
|
||||
// Link hints are in upper case in new tab mode
|
||||
if currentVimMode == linkModeNewTab {
|
||||
htCopy = strings.ToUpper(htCopy)
|
||||
}
|
||||
|
||||
// Create closure
|
||||
f := func() {
|
||||
writeString(rLeftCopy+offsetLeft, rTopCopy+uiHeight, htCopy, tcell.StyleDefault)
|
||||
}
|
||||
fc = append(fc, &f)
|
||||
}
|
||||
// Create closure that calls the other closures
|
||||
ff := func() {
|
||||
for _, f := range fc {
|
||||
(*f)()
|
||||
}
|
||||
}
|
||||
linkHintWriteStringCalls = &ff
|
||||
}
|
||||
|
||||
func eraseLinkHints() {
|
||||
linkText = ""
|
||||
linkHintWriteStringCalls = nil
|
||||
linkHintsToRects = make(map[string]*hintRect)
|
||||
linkHintRects = nil
|
||||
}
|
||||
|
||||
func resetLinkHints() {
|
||||
linkText = ""
|
||||
updateLinkHintDisplay()
|
||||
}
|
||||
|
||||
func isNormalModeKey(ev *tcell.EventKey) bool {
|
||||
if ev != nil && ev.Key() == tcell.KeyESC {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func keyEventToString(ev *tcell.EventKey) string {
|
||||
if ev == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
r := string(ev.Rune())
|
||||
if ev.Modifiers()&tcell.ModAlt != 0 && ev.Modifiers()&tcell.ModCtrl != 0 {
|
||||
return "<C-M-" + r + ">"
|
||||
} else if ev.Modifiers()&tcell.ModAlt != 0 {
|
||||
return "<M-" + r + ">"
|
||||
} else if ev.Modifiers()&tcell.ModCtrl != 0 {
|
||||
return "<C-" + strings.ToLower(ev.Name()[5:]) + ">"
|
||||
}
|
||||
|
||||
switch ev.Key() {
|
||||
case tcell.KeyEnter:
|
||||
return "<Enter>"
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
func getNLastKeyEvent(n int) *tcell.EventKey {
|
||||
if n < 0 || keyEvents == nil {
|
||||
return nil
|
||||
}
|
||||
if len(keyEvents) > n {
|
||||
return keyEvents[len(keyEvents)-n-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mapVimKeyEvents(ev *tcell.EventKey, mapMode string) string {
|
||||
var lastEvent *tcell.EventKey
|
||||
command := ""
|
||||
|
||||
keyEvents = append(keyEvents, ev)
|
||||
if len(keyEvents) > 10 {
|
||||
keyEvents = keyEvents[1:]
|
||||
}
|
||||
|
||||
lastEvent = getNLastKeyEvent(1)
|
||||
|
||||
latestKeyCombination = keyEventToString(lastEvent) + keyEventToString(ev)
|
||||
|
||||
command = vimKeyMap[mapMode+" "+latestKeyCombination]
|
||||
if len(command) == 0 {
|
||||
latestKeyCombination = keyEventToString(ev)
|
||||
command = vimKeyMap[mapMode+" "+latestKeyCombination]
|
||||
}
|
||||
if len(command) <= 0 {
|
||||
latestKeyCombination = ""
|
||||
} else {
|
||||
// Since len(command) must be greather than 0 here,
|
||||
// a key mapping did match, therefore we reset keyEvents
|
||||
keyEvents = nil
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
func handleVimMode(ev *tcell.EventKey, mode string) string {
|
||||
if isNormalModeKey(ev) {
|
||||
return "normalMode"
|
||||
} else {
|
||||
return mapVimKeyEvents(ev, mode)
|
||||
}
|
||||
}
|
||||
|
||||
func handleVimControl(ev *tcell.EventKey) {
|
||||
var command string
|
||||
switch currentVimMode {
|
||||
case waitMode:
|
||||
if time.Since(waitModeStartTime) < time.Millisecond*time.Duration(waitModeMaxMilliseconds) {
|
||||
return
|
||||
}
|
||||
changeVimMode(normalMode)
|
||||
fallthrough
|
||||
case normalMode:
|
||||
command = mapVimKeyEvents(ev, "normal")
|
||||
case insertMode:
|
||||
command = handleVimMode(ev, "insert")
|
||||
case insertModeHard:
|
||||
if isNormalModeKey(ev) && isNormalModeKey(getNLastKeyEvent(0)) && isNormalModeKey(getNLastKeyEvent(1)) && isNormalModeKey(getNLastKeyEvent(2)) {
|
||||
command = "normalMode"
|
||||
} else {
|
||||
command = mapVimKeyEvents(ev, "insertHard")
|
||||
}
|
||||
case visualMode:
|
||||
command = handleVimMode(ev, "visual")
|
||||
case caretMode:
|
||||
command = handleVimMode(ev, "caret")
|
||||
case markModeMake:
|
||||
if unicode.IsLower(ev.Rune()) {
|
||||
if localMarkMap[CurrentTab.ID] == nil {
|
||||
localMarkMap[CurrentTab.ID] = make(map[rune]*mark)
|
||||
}
|
||||
localMarkMap[CurrentTab.ID][ev.Rune()] = makeMark()
|
||||
} else if unicode.IsUpper(ev.Rune()) {
|
||||
globalMarkMap[ev.Rune()] = makeMark()
|
||||
}
|
||||
|
||||
command = "normalMode"
|
||||
case markModeGoto:
|
||||
if mark, ok := globalMarkMap[ev.Rune()]; ok {
|
||||
gotoMark(mark)
|
||||
} else if m, ok := localMarkMap[CurrentTab.ID]; unicode.IsLower(ev.Rune()) && ok {
|
||||
if mark, ok := m[ev.Rune()]; ok {
|
||||
gotoMark(mark)
|
||||
}
|
||||
}
|
||||
|
||||
command = "normalMode"
|
||||
case findMode:
|
||||
if isNormalModeKey(ev) {
|
||||
command = "normalMode"
|
||||
findText = ""
|
||||
} else {
|
||||
if ev.Key() == tcell.KeyEnter {
|
||||
changeVimMode(normalMode)
|
||||
command = "findText"
|
||||
break
|
||||
}
|
||||
if ev.Key() == tcell.KeyBackspace || ev.Key() == tcell.KeyBackspace2 {
|
||||
if len(findText) > 0 {
|
||||
findText = findText[:len(findText)-1]
|
||||
}
|
||||
} else {
|
||||
findText += string(ev.Rune())
|
||||
}
|
||||
}
|
||||
case linkMode, linkModeNewTab, linkModeMultipleNewTab, linkModeCopy:
|
||||
if isNormalModeKey(ev) {
|
||||
command = "normalMode"
|
||||
eraseLinkHints()
|
||||
} else {
|
||||
linkText += string(ev.Rune())
|
||||
updateLinkHintDisplay()
|
||||
if linkModeWithHints {
|
||||
if r, ok := linkHintsToRects[linkText]; ok {
|
||||
if r != nil {
|
||||
switch currentVimMode {
|
||||
case linkMode:
|
||||
if (*r).Height == 2 {
|
||||
generateLeftClickYHack((*r).Left, (*r).Top, true)
|
||||
} else {
|
||||
generateLeftClick((*r).Left, (*r).Top)
|
||||
}
|
||||
case linkModeNewTab:
|
||||
sendMessageToWebExtension("/new_tab," + r.Href)
|
||||
case linkModeMultipleNewTab:
|
||||
resetLinkHints()
|
||||
return
|
||||
case linkModeCopy:
|
||||
clipboard.WriteAll(r.Href)
|
||||
}
|
||||
goIntoWaitMode()
|
||||
eraseLinkHints()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
coords := findAndHighlightTextOnScreen(linkText)
|
||||
if len(coords) == 1 {
|
||||
goIntoWaitMode()
|
||||
|
||||
if currentVimMode == linkModeNewTab {
|
||||
generateMiddleClick(coords[0].X, coords[0].Y)
|
||||
} else {
|
||||
generateLeftClick(coords[0].X, coords[0].Y)
|
||||
}
|
||||
linkText = ""
|
||||
return
|
||||
} else if len(coords) == 0 {
|
||||
changeVimMode(normalMode)
|
||||
linkText = ""
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executeVimCommand(command)
|
||||
}
|
||||
|
||||
func executeVimCommand(command string) {
|
||||
if len(command) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
currentCommand := command
|
||||
command = ""
|
||||
switch currentCommand {
|
||||
case "urlUp":
|
||||
sendMessageToWebExtension("/tab_command,/url_up")
|
||||
case "urlRoot":
|
||||
sendMessageToWebExtension("/tab_command,/url_root")
|
||||
case "scrollToTop":
|
||||
doScroll(0, -CurrentTab.frame.domRowCount())
|
||||
case "scrollToBottom":
|
||||
doScroll(0, CurrentTab.frame.domRowCount())
|
||||
case "scrollUp":
|
||||
doScroll(0, -1)
|
||||
case "scrollDown":
|
||||
doScroll(0, 1)
|
||||
case "scrollLeft":
|
||||
doScroll(-1, 0)
|
||||
case "scrollRight":
|
||||
doScroll(1, 0)
|
||||
case "editURL":
|
||||
urlBarFocusToggle()
|
||||
case "editURLInNewTab":
|
||||
createNewEmptyTabWithURI(CurrentTab.URI)
|
||||
case "firstTab":
|
||||
switchToTab(tabsOrder[0])
|
||||
case "lastTab":
|
||||
switchToTab(tabsOrder[len(tabsOrder)-1])
|
||||
case "scrollHalfPageDown":
|
||||
_, height := screen.Size()
|
||||
doScroll(0, (height-uiHeight)/2)
|
||||
case "scrollHalfPageUp":
|
||||
_, height := screen.Size()
|
||||
doScroll(0, -((height - uiHeight) / 2))
|
||||
case "historyBack":
|
||||
sendMessageToWebExtension("/tab_command,/history_back")
|
||||
case "historyForward":
|
||||
sendMessageToWebExtension("/tab_command,/history_forward")
|
||||
case "reload":
|
||||
sendMessageToWebExtension("/tab_command,/reload")
|
||||
case "prevTab":
|
||||
prevTab()
|
||||
case "nextTab":
|
||||
nextTab()
|
||||
case "previouslyVisitedTab":
|
||||
previouslyVisitedTab()
|
||||
case "newTab":
|
||||
createNewEmptyTab()
|
||||
case "removeTab":
|
||||
removeTab(CurrentTab.ID)
|
||||
case "restoreTab":
|
||||
restoreTab()
|
||||
case "duplicateTab":
|
||||
duplicateTab(CurrentTab.ID)
|
||||
case "moveTabLeft":
|
||||
moveTabLeft(CurrentTab.ID)
|
||||
case "moveTabRight":
|
||||
moveTabRight(CurrentTab.ID)
|
||||
case "copyURL":
|
||||
clipboard.WriteAll(CurrentTab.URI)
|
||||
case "openClipboardURL":
|
||||
URI, _ := clipboard.ReadAll()
|
||||
sendMessageToWebExtension("/tab_command,/url," + URI)
|
||||
case "openClipboardURLInNewTab":
|
||||
URI, _ := clipboard.ReadAll()
|
||||
sendMessageToWebExtension("/new_tab," + URI)
|
||||
case "focusFirstTextInput":
|
||||
sendMessageToWebExtension("/tab_command,/focus_first_text_input")
|
||||
case "followLinkLabeledNext":
|
||||
sendMessageToWebExtension("/tab_command,/follow_link_labeled_next")
|
||||
case "followLinkLabeledPrevious":
|
||||
sendMessageToWebExtension("/tab_command,/follow_link_labeled_previous")
|
||||
case "viewHelp":
|
||||
sendMessageToWebExtension("/new_tab,https://www.brow.sh/docs/keybindings/")
|
||||
case "openLinkInCurrentTab":
|
||||
changeVimMode(linkMode)
|
||||
sendMessageToWebExtension("/tab_command,/get_clickable_hints")
|
||||
eraseLinkHints()
|
||||
case "openLinkInNewTab":
|
||||
changeVimMode(linkModeNewTab)
|
||||
sendMessageToWebExtension("/tab_command,/get_link_hints")
|
||||
eraseLinkHints()
|
||||
case "openMultipleLinksInNewTab":
|
||||
changeVimMode(linkModeMultipleNewTab)
|
||||
sendMessageToWebExtension("/tab_command,/get_link_hints")
|
||||
eraseLinkHints()
|
||||
case "copyLinkURL":
|
||||
changeVimMode(linkModeCopy)
|
||||
sendMessageToWebExtension("/tab_command,/get_link_hints")
|
||||
eraseLinkHints()
|
||||
case "findText":
|
||||
fallthrough
|
||||
case "findNext":
|
||||
sendMessageToWebExtension("/tab_command,/find_next," + findText)
|
||||
case "findPrevious":
|
||||
sendMessageToWebExtension("/tab_command,/find_previous," + findText)
|
||||
case "makeMark":
|
||||
changeVimMode(markModeMake)
|
||||
case "gotoMark":
|
||||
changeVimMode(markModeGoto)
|
||||
case "insertMode":
|
||||
changeVimMode(insertMode)
|
||||
case "insertModeHard":
|
||||
changeVimMode(insertModeHard)
|
||||
case "findMode":
|
||||
changeVimMode(findMode)
|
||||
case "normalMode":
|
||||
changeVimMode(normalMode)
|
||||
// Visual mode
|
||||
case "visualMode":
|
||||
changeVimMode(visualMode)
|
||||
case "swapVisualModeCursorPosition":
|
||||
// Stub
|
||||
case "copyVisualModeSelection":
|
||||
// Caret mode
|
||||
case "caretMode":
|
||||
changeVimMode(caretMode)
|
||||
width, height := screen.Size()
|
||||
caretPos.X, caretPos.Y = width/2, height/2
|
||||
case "clickAtCaretPosition":
|
||||
generateLeftClick(caretPos.X, caretPos.Y-uiHeight)
|
||||
case "moveCaretLeft":
|
||||
moveVimCaret(func() bool { return caretPos.X > 0 }, &caretPos.X, -1)
|
||||
case "moveCaretRight":
|
||||
width, _ := screen.Size()
|
||||
moveVimCaret(func() bool { return caretPos.X < width }, &caretPos.X, 1)
|
||||
case "moveCaretUp":
|
||||
_, height := screen.Size()
|
||||
moveVimCaret(func() bool { return caretPos.Y >= uiHeight }, &caretPos.Y, -1)
|
||||
if caretPos.Y < uiHeight {
|
||||
command = "scrollHalfPageUp"
|
||||
if CurrentTab.frame.yScroll == 0 {
|
||||
caretPos.Y = uiHeight
|
||||
} else {
|
||||
caretPos.Y += (height - uiHeight) / 2
|
||||
}
|
||||
}
|
||||
case "moveCaretDown":
|
||||
_, height := screen.Size()
|
||||
moveVimCaret(func() bool { return caretPos.Y <= height-uiHeight }, &caretPos.Y, 1)
|
||||
if caretPos.Y > height-uiHeight {
|
||||
command = "scrollHalfPageDown"
|
||||
caretPos.Y -= (height - uiHeight) / 2
|
||||
}
|
||||
}
|
||||
|
||||
// A command can spawn another
|
||||
executeVimCommand(command)
|
||||
}
|
||||
|
||||
func changeVimMode(mode vimMode) {
|
||||
if currentVimMode == mode {
|
||||
// No change
|
||||
return
|
||||
}
|
||||
|
||||
currentVimMode = mode
|
||||
// Reset keyEvents
|
||||
keyEvents = nil
|
||||
}
|
||||
|
||||
func searchVisibleScreenForText(text string) []Coordinate {
|
||||
var offsets = make([]Coordinate, 0)
|
||||
var splitString []string
|
||||
var r rune
|
||||
var s string
|
||||
width, height := screen.Size()
|
||||
screenText := ""
|
||||
index := 0
|
||||
|
||||
for y := 0; y < height-uiHeight; y++ {
|
||||
screenText = ""
|
||||
for x := 0; x < width; x++ {
|
||||
r = getCell(x, y).character[0]
|
||||
s = string(r)
|
||||
if len(s) == 0 || len(s) > 1 {
|
||||
screenText += " "
|
||||
} else {
|
||||
screenText += string(getCell(x, y).character[0])
|
||||
}
|
||||
}
|
||||
index = 0
|
||||
splitString = strings.Split(strings.ToLower(screenText), strings.ToLower(text))
|
||||
for _, s := range splitString {
|
||||
if index+len(s) >= width {
|
||||
break
|
||||
}
|
||||
|
||||
offsets = append(offsets, Coordinate{index + len(s), y})
|
||||
index += len(s) + len(text)
|
||||
}
|
||||
}
|
||||
return offsets
|
||||
}
|
||||
|
||||
func findAndHighlightTextOnScreen(text string) []Coordinate {
|
||||
var x, y int
|
||||
var styling = tcell.StyleDefault
|
||||
|
||||
offsets := searchVisibleScreenForText(text)
|
||||
for _, offset := range offsets {
|
||||
y = offset.Y
|
||||
x = offset.X
|
||||
for z := 0; z < len(text); z++ {
|
||||
screen.SetContent(x+z, y+uiHeight, rune(text[z]), nil, styling)
|
||||
}
|
||||
}
|
||||
screen.Show()
|
||||
return offsets
|
||||
}
|
||||
|
||||
// Parse incoming link hints
|
||||
func parseJSONLinkHints(jsonString string) {
|
||||
jsonBytes := []byte(jsonString)
|
||||
if err := json.Unmarshal(jsonBytes, &linkHintRects); err != nil {
|
||||
Shutdown(err)
|
||||
}
|
||||
|
||||
// Optimize link hint positions
|
||||
for i := 0; i < len(linkHintRects); i++ {
|
||||
r := &linkHintRects[i]
|
||||
|
||||
// For links that are more than one line high
|
||||
// we want to position the link hint in the vertical middle
|
||||
if r.Height > 2 {
|
||||
if r.Height%2 == 0 {
|
||||
r.Top += r.Height / 2
|
||||
} else {
|
||||
r.Top += r.Height/2 - 1
|
||||
}
|
||||
}
|
||||
|
||||
// For links that are more one character long we try to move
|
||||
// the link hint two characters to the right, if possible.
|
||||
if r.Width > 1 {
|
||||
o := r.Left
|
||||
r.Left += r.Width/2 - 1
|
||||
if r.Left > o+2 {
|
||||
r.Left = o + 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log("Received parseJSONLinkHint")
|
||||
// This is where the display of actual link hints is prepared
|
||||
updateLinkHintDisplay()
|
||||
}
|
|
@ -15,6 +15,7 @@ import (
|
|||
gomega "github.com/onsi/gomega"
|
||||
|
||||
"browsh/interfacer/src/browsh"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
|
@ -144,10 +145,13 @@ func sleepUntilPageLoad(maxTime time.Duration) {
|
|||
|
||||
// GotoURL sends the browsh browser to the specified URL
|
||||
func GotoURL(url string) {
|
||||
SpecialKey(tcell.KeyCtrlL)
|
||||
browsh.URLBarFocus(true)
|
||||
Keyboard(url)
|
||||
SpecialKey(tcell.KeyEnter)
|
||||
WaitForPageLoad()
|
||||
// Hack to force text to be rerendered. Because there's a bug where text sometimes doesn't get
|
||||
// rendered.
|
||||
mouseClick(3, 3)
|
||||
// TODO: Looking for the URL isn't optimal because it could be the same URL
|
||||
// as the previous test.
|
||||
gomega.Expect(url).To(BeInFrameAt(0, 1))
|
||||
|
@ -213,7 +217,7 @@ func GetBgColour(x, y int) [3]int32 {
|
|||
}
|
||||
|
||||
func ensureOnlyOneTab() {
|
||||
if len(browsh.Tabs) > 1 {
|
||||
for len(browsh.Tabs) > 1 {
|
||||
SpecialKey(tcell.KeyCtrlW)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"browsh/interfacer/src/browsh"
|
||||
"testing"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
|
@ -8,12 +9,12 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestIntegration(t *testing.T) {
|
||||
func TestMain(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Integration tests")
|
||||
}
|
||||
|
||||
var _ = Describe("Showing a basic webpage", func() {
|
||||
var _ = Describe("Core functionality", func() {
|
||||
BeforeEach(func() {
|
||||
GotoURL(testSiteURL + "/smorgasbord/")
|
||||
})
|
||||
|
@ -106,10 +107,10 @@ var _ = Describe("Showing a basic webpage", func() {
|
|||
|
||||
It("should enter multiple lines of text", func() {
|
||||
Keyboard(`So here is a lot of text that will hopefully split across lines`)
|
||||
Expect("So here is a lot of").To(BeInFrameAt(1, 3))
|
||||
Expect("text that will").To(BeInFrameAt(1, 4))
|
||||
Expect("hopefully split across").To(BeInFrameAt(1, 5))
|
||||
Expect("lines").To(BeInFrameAt(1, 6))
|
||||
Expect("So here is a lot of").To(BeInFrameAt(1, 2))
|
||||
Expect("text that will").To(BeInFrameAt(1, 3))
|
||||
Expect("hopefully split across").To(BeInFrameAt(1, 4))
|
||||
Expect("lines").To(BeInFrameAt(1, 5))
|
||||
})
|
||||
|
||||
It("should scroll multiple lines of text", func() {
|
||||
|
@ -120,37 +121,33 @@ var _ = Describe("Showing a basic webpage", func() {
|
|||
for i := 1; i <= 6; i++ {
|
||||
SpecialKey(tcell.KeyUp)
|
||||
}
|
||||
Expect("lines").To(BeInFrameAt(1, 6))
|
||||
Expect("lines").To(BeInFrameAt(1, 5))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Tabs", func() {
|
||||
BeforeEach(func() {
|
||||
SpecialKey(tcell.KeyCtrlT)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
ensureOnlyOneTab()
|
||||
})
|
||||
|
||||
It("should create a new tab", func() {
|
||||
Expect("New Tab").To(BeInFrameAt(21, 0))
|
||||
SpecialKey(tcell.KeyCtrlT)
|
||||
Expect(len(browsh.Tabs)).To(Equal(2))
|
||||
})
|
||||
|
||||
It("should be able to goto a new URL", func() {
|
||||
Keyboard(testSiteURL + "/smorgasbord/another.html")
|
||||
SpecialKey(tcell.KeyEnter)
|
||||
Expect("Another").To(BeInFrameAt(21, 0))
|
||||
SpecialKey(tcell.KeyCtrlT)
|
||||
GotoURL(testSiteURL + "/smorgasbord/another.html")
|
||||
Expect("Another▄webpage").To(BeInFrameAt(1, 3))
|
||||
})
|
||||
|
||||
It("should cycle to the next tab", func() {
|
||||
Expect(" ").To(BeInFrameAt(0, 1))
|
||||
SpecialKey(tcell.KeyCtrlL)
|
||||
GotoURL(testSiteURL + "/smorgasbord/")
|
||||
SpecialKey(tcell.KeyCtrlT)
|
||||
GotoURL(testSiteURL + "/smorgasbord/another.html")
|
||||
triggerUserKeyFor("tty.keys.next-tab")
|
||||
URL := testSiteURL + "/smorgasbord/ "
|
||||
Expect(URL).To(BeInFrameAt(0, 1))
|
||||
Expect("Smörgåsbord").To(BeInFrameAt(0, 0))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -191,7 +188,7 @@ var _ = Describe("Showing a basic webpage", func() {
|
|||
})
|
||||
|
||||
Describe("Text positioning", func() {
|
||||
It("should position the left/right-aligned coloumns", func() {
|
||||
It("should position the left/right-aligned columns", func() {
|
||||
Expect("Smörgåsbord▄(Swedish:").To(BeInFrameAt(12, 10))
|
||||
Expect("The▄Swedish▄word").To(BeInFrameAt(42, 10))
|
||||
})
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestVim(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Integration tests")
|
||||
}
|
||||
|
||||
var _ = Describe("Vim tests", func() {
|
||||
BeforeEach(func() {
|
||||
GotoURL(testSiteURL + "/smorgasbord/")
|
||||
})
|
||||
|
||||
It("should navigate to a new page by using a link hint", func() {
|
||||
Expect("Another▄page").To(BeInFrameAt(12, 18))
|
||||
Keyboard("f")
|
||||
Keyboard("a")
|
||||
Expect("Another").To(BeInFrameAt(0, 0))
|
||||
})
|
||||
|
||||
It("should scroll the page by one line", func() {
|
||||
Expect("[ˈsmœrɡɔsˌbuːɖ])▄is▄a").To(BeInFrameAt(12, 11))
|
||||
Keyboard("j")
|
||||
Expect("type▄of▄Scandinavian▄").To(BeInFrameAt(12, 11))
|
||||
})
|
||||
|
||||
Describe("Tabs", func() {
|
||||
BeforeEach(func() {
|
||||
ensureOnlyOneTab()
|
||||
})
|
||||
|
||||
It("should create a new tab", func() {
|
||||
Keyboard("t")
|
||||
Expect("New Tab").To(BeInFrameAt(21, 0))
|
||||
})
|
||||
|
||||
It("should cycle to the next tab", func() {
|
||||
GotoURL(testSiteURL + "/smorgasbord/")
|
||||
Keyboard("t")
|
||||
GotoURL(testSiteURL + "/smorgasbord/another.html")
|
||||
Keyboard("J")
|
||||
URL := testSiteURL + "/smorgasbord/ "
|
||||
Expect(URL).To(BeInFrameAt(0, 1))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -32,6 +32,7 @@
|
|||
"<all_urls>",
|
||||
"webRequest",
|
||||
"webRequestBlocking",
|
||||
"tabs"
|
||||
"tabs",
|
||||
"sessions"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@ export default MixinBase =>
|
|||
case "/frame_pixels":
|
||||
this.sendToTerminal(`/frame_pixels,${message.slice(14)}`);
|
||||
break;
|
||||
case "/link_hints":
|
||||
this.sendToTerminal(`/link_hints,${message.slice(12)}`);
|
||||
break;
|
||||
case "/tab_info":
|
||||
incoming = JSON.parse(utils.rebuildArgsToSingleArg(parts));
|
||||
this._updateTabInfo(incoming);
|
||||
|
|
|
@ -29,9 +29,15 @@ export default MixinBase =>
|
|||
case "/switch_to_tab":
|
||||
this.switchToTab(parts.slice(1).join(","));
|
||||
break;
|
||||
case "/duplicate_tab":
|
||||
this.duplicateTab(parts.slice(1).join(","));
|
||||
break;
|
||||
case "/remove_tab":
|
||||
this.removeTab(parts.slice(1).join(","));
|
||||
break;
|
||||
case "/restore_tab":
|
||||
this.restoreTab();
|
||||
break;
|
||||
case "/raw_text_request":
|
||||
this._rawTextRequest(parts[1], parts[2], parts.slice(3).join(","));
|
||||
break;
|
||||
|
@ -175,6 +181,24 @@ export default MixinBase =>
|
|||
this.tabs[id] = null;
|
||||
}
|
||||
|
||||
duplicateTab(id) {
|
||||
browser.tabs.duplicate(parseInt(id));
|
||||
}
|
||||
|
||||
restoreTab() {
|
||||
var sessionsInfo = browser.sessions.getRecentlyClosed({ maxResults: 1 });
|
||||
sessionsInfo.then(this._restoreTab);
|
||||
}
|
||||
|
||||
_restoreTab(sessionsInfo) {
|
||||
var mySessionInfo = sessionsInfo[0];
|
||||
if (mySessionInfo.tab) {
|
||||
browser.sessions.restore(mySessionInfo.tab.sessionId);
|
||||
} else {
|
||||
browser.sessions.restore(mySessionInfo.window.sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
// We use the `browser` object here rather than going into the actual content script
|
||||
// because the content script may have crashed, even never loaded.
|
||||
screenshotActiveTab() {
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import utils from "utils";
|
||||
import { Rect } from "vimium";
|
||||
import { DomUtils } from "vimium";
|
||||
import { LocalHints } from "vimium";
|
||||
import { VimiumNormal } from "vimium";
|
||||
import { MiscVimium } from "vimium";
|
||||
MiscVimium();
|
||||
|
||||
export default MixinBase =>
|
||||
class extends MixinBase {
|
||||
|
@ -35,19 +41,149 @@ export default MixinBase =>
|
|||
break;
|
||||
case "/url":
|
||||
url = utils.rebuildArgsToSingleArg(parts);
|
||||
document.location.href = url;
|
||||
window.location.href = url;
|
||||
break;
|
||||
case "/url_up":
|
||||
this.urlUp();
|
||||
break;
|
||||
case "/url_root":
|
||||
window.location.href = window.location.origin;
|
||||
break;
|
||||
case "/history_back":
|
||||
history.go(-1);
|
||||
break;
|
||||
case "/history_forward":
|
||||
history.go(1);
|
||||
break;
|
||||
case "/reload":
|
||||
window.location.reload();
|
||||
break;
|
||||
case "/window_stop":
|
||||
window.stop();
|
||||
break;
|
||||
case "/find_next":
|
||||
this.findNext(parts[1]);
|
||||
break;
|
||||
case "/find_previous":
|
||||
window.find(parts[1], false, true, false, false, true, true);
|
||||
break;
|
||||
case "/get_link_hints":
|
||||
this.getLinkHints(false);
|
||||
break;
|
||||
case "/get_clickable_hints":
|
||||
this.getLinkHints(true);
|
||||
break;
|
||||
case "/focus_first_text_input":
|
||||
this.focusFirstTextInput();
|
||||
break;
|
||||
case "/follow_link_labeled_next":
|
||||
this._followLinkLabeledNext();
|
||||
break;
|
||||
case "/follow_link_labeled_previous":
|
||||
this._followLinkLabeledPrevious();
|
||||
break;
|
||||
default:
|
||||
this.log("Unknown command sent to tab", message);
|
||||
}
|
||||
}
|
||||
|
||||
focusFirstTextInput() {
|
||||
VimiumNormal.focusInput(1);
|
||||
}
|
||||
|
||||
//adapted vimium code
|
||||
followLinkLabeledNext() {
|
||||
var nextPatterns = "next,more,newer,>,›,→,»,≫,>>,weiter" || "";
|
||||
var nextStrings = nextPatterns.split(",").filter(function(s) {
|
||||
return s.trim().length;
|
||||
});
|
||||
return (
|
||||
VimiumNormal.findAndFollowRel("next") ||
|
||||
VimiumNormal.findAndFollowLink(nextStrings)
|
||||
);
|
||||
}
|
||||
|
||||
_followLinkLabeledNext() {
|
||||
this.followLinkLabeledNext();
|
||||
}
|
||||
|
||||
//adapted vimium code
|
||||
followLinkLabeledPrevious() {
|
||||
var previousPatterns =
|
||||
"prev,previous,back,older,<,‹,←,«,≪,<<,zurück" || "";
|
||||
var previousStrings = previousPatterns.split(",").filter(function(s) {
|
||||
return s.trim().length;
|
||||
});
|
||||
return (
|
||||
VimiumNormal.findAndFollowRel("prev") ||
|
||||
VimiumNormal.findAndFollowLink(previousStrings)
|
||||
);
|
||||
}
|
||||
|
||||
_followLinkLabeledPrevious() {
|
||||
this.followLinkLabeledPrevious();
|
||||
}
|
||||
|
||||
// Eg; This goes from www.domain.com/topic/suptopic/ to www.domain.com/topic/
|
||||
urlUp() {
|
||||
// this is taken from vimium's code
|
||||
var url = window.location.href;
|
||||
if (url[url.length - 1] === "/") {
|
||||
url = url.substring(0, url.length - 1);
|
||||
}
|
||||
var urlsplit = url.split("/");
|
||||
// make sure we haven't hit the base domain yet
|
||||
if (urlsplit.length > 3) {
|
||||
urlsplit = urlsplit.slice(0, Math.max(3, urlsplit.length - 1));
|
||||
window.location.href = urlsplit.join("/");
|
||||
}
|
||||
}
|
||||
|
||||
getLinkHints(clickable) {
|
||||
var hints = LocalHints.getLocalHints(!clickable);
|
||||
var rect, bottom, top, left, right, width, height, results, result, href;
|
||||
results = [];
|
||||
for (let idx in hints) {
|
||||
if (!hints[idx].hasOwnProperty("rect")) {
|
||||
continue;
|
||||
}
|
||||
href = hints[idx]["href"];
|
||||
rect = hints[idx]["rect"];
|
||||
bottom = Math.round(
|
||||
((rect["bottom"] - window.scrollY) *
|
||||
this.dimensions.scale_factor.height) /
|
||||
2
|
||||
);
|
||||
top = Math.round(
|
||||
((rect["top"] - window.scrollY) *
|
||||
this.dimensions.scale_factor.height) /
|
||||
2
|
||||
);
|
||||
left = Math.round(rect["left"] * this.dimensions.scale_factor.width);
|
||||
right = Math.round(rect["right"] * this.dimensions.scale_factor.width);
|
||||
result = Rect.create(left, top, right, bottom);
|
||||
result.href = href;
|
||||
results.push(result);
|
||||
}
|
||||
this.sendMessage(`/link_hints,${JSON.stringify(results)}`);
|
||||
}
|
||||
|
||||
findNext(text) {
|
||||
window.find(text, false, false, false, false, true, true);
|
||||
//var s = window.getSelection();
|
||||
//var oRange = s.getRangeAt(0); //get the text range
|
||||
//var oRect = oRange.getBoundingClientRect();
|
||||
//window.scrollTo(400, 20000);
|
||||
this.dimensions.y_scroll = Math.round(
|
||||
window.scrollY * this.dimensions.scale_factor.height
|
||||
);
|
||||
this.dimensions.x_scroll = Math.round(
|
||||
window.scrollX * this.dimensions.scale_factor.width
|
||||
);
|
||||
this.dimensions.update();
|
||||
this._mightSendBigFrames();
|
||||
}
|
||||
|
||||
_launch() {
|
||||
const mode = this.config.http_server_mode_type;
|
||||
if (mode.includes("raw_text_")) {
|
||||
|
@ -119,9 +255,25 @@ export default MixinBase =>
|
|||
_handleMouse(input) {
|
||||
switch (input.button) {
|
||||
case 1:
|
||||
this._mouseAction("mousemove", input.mouse_x, input.mouse_y);
|
||||
var y_hack = false;
|
||||
if (input.hasOwnProperty("y_hack")) {
|
||||
y_hack = true;
|
||||
}
|
||||
this._mouseAction(
|
||||
"mousemove",
|
||||
input.mouse_x,
|
||||
input.mouse_y,
|
||||
0,
|
||||
y_hack
|
||||
);
|
||||
if (!this._mousedown) {
|
||||
this._mouseAction("mousedown", input.mouse_x, input.mouse_y);
|
||||
this._mouseAction(
|
||||
"mousedown",
|
||||
input.mouse_x,
|
||||
input.mouse_y,
|
||||
0,
|
||||
y_hack
|
||||
);
|
||||
setTimeout(() => {
|
||||
this.sendSmallTextFrame();
|
||||
}, 500);
|
||||
|
@ -129,10 +281,26 @@ export default MixinBase =>
|
|||
this._mousedown = true;
|
||||
break;
|
||||
case 0:
|
||||
this._mouseAction("mousemove", input.mouse_x, input.mouse_y);
|
||||
var y_hack = false;
|
||||
if (input.hasOwnProperty("y_hack")) {
|
||||
y_hack = true;
|
||||
}
|
||||
this._mouseAction(
|
||||
"mousemove",
|
||||
input.mouse_x,
|
||||
input.mouse_y,
|
||||
0,
|
||||
y_hack
|
||||
);
|
||||
if (this._mousedown) {
|
||||
this._mouseAction("click", input.mouse_x, input.mouse_y);
|
||||
this._mouseAction("mouseup", input.mouse_x, input.mouse_y);
|
||||
this._mouseAction("click", input.mouse_x, input.mouse_y, 0, y_hack);
|
||||
this._mouseAction(
|
||||
"mouseup",
|
||||
input.mouse_x,
|
||||
input.mouse_y,
|
||||
0,
|
||||
y_hack
|
||||
);
|
||||
}
|
||||
this._mousedown = false;
|
||||
break;
|
||||
|
@ -186,8 +354,12 @@ export default MixinBase =>
|
|||
}
|
||||
}
|
||||
|
||||
_mouseAction(type, x, y) {
|
||||
const [dom_x, dom_y] = this._getDOMCoordsFromMouseCoords(x, y);
|
||||
_mouseAction(type, x, y, button, y_hack = false) {
|
||||
let [dom_x, dom_y] = this._getDOMCoordsFromMouseCoords(x, y);
|
||||
if (y_hack) {
|
||||
const [dom_x2, dom_y2] = this._getDOMCoordsFromMouseCoords(x, y + 1);
|
||||
dom_y = (dom_y + dom_y2) / 2;
|
||||
}
|
||||
const element = document.elementFromPoint(
|
||||
dom_x - window.scrollX,
|
||||
dom_y - window.scrollY
|
||||
|
@ -208,7 +380,7 @@ export default MixinBase =>
|
|||
false,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
button,
|
||||
null
|
||||
);
|
||||
element.dispatchEvent(clickEvent);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue