Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vim Mode 2022 #439

Open
wants to merge 89 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
bdd0df1
Added new vimium.js with code taken from vimium
tobimensch Oct 7, 2018
ced1379
Created overlayVimMode function for displaying vim navigation state
tobimensch Oct 7, 2018
6bd4c96
gofmt code
tobimensch Oct 7, 2018
a78d98b
added command for links hints
tobimensch Oct 7, 2018
8b35a68
Set default DISPLAY environment variable for xclipboard functionality
tobimensch Oct 7, 2018
3c8afed
ignore manifest.json backup file
tobimensch Oct 7, 2018
a04bdac
ignore debug log in interfacer/ directory
tobimensch Oct 18, 2018
0d2dfee
Fixed typo in comment
tobimensch Oct 24, 2018
9329c9f
Added sessions permission
tobimensch Oct 29, 2018
c40c724
Added initial configuration for vim like keybindings.
tobimensch Oct 29, 2018
f730983
Added duplicate_tab, restore_tab commands.
tobimensch Oct 29, 2018
b78d896
Refactored code using switchToTab and added new features.
tobimensch Oct 29, 2018
052aecd
Added features needed for vim like navigation to the webextension.
tobimensch Oct 29, 2018
4099f51
Added vim like navigation. This is still in an early stage.
tobimensch Oct 29, 2018
721b2c8
Instead of adding 1 to Y coord, add 1 to height. This fixes an issues
tobimensch Oct 29, 2018
d037732
Merge branch 'master' of https://github.com/browsh-org/browsh into vi…
tobimensch Oct 29, 2018
61cd7e1
Prettified js files
tobimensch Oct 29, 2018
ae1df35
Fixed typo
tobimensch Oct 29, 2018
034d9c4
Update Go `dep` to v0.5.0
tombh Nov 6, 2018
11f746b
Newer NVM formats package.lock differently
tombh Nov 6, 2018
d17cb59
Update FF Marionette commands
tombh Nov 6, 2018
bf44f91
Update JS and Go deps. Bump Browsh to v1.5.0
tombh Nov 6, 2018
0bda8f1
Adds Golang clipboard dep
tombh Nov 9, 2018
6f998be
Refactors Vim code from tty.go into its own file
tombh Nov 9, 2018
e3568cd
Adds some Vim-specific integration tests
tombh Nov 9, 2018
3beeb76
Fixes tests for Vim mode
tombh Nov 11, 2018
26e9c61
Vim mode: convert unexported symbols to lowercase
tombh Nov 11, 2018
af487ae
Vim mode: Small updates from PR review
tombh Nov 12, 2018
15f541c
Travis CI: Bash timeout for integration tests
tombh Nov 13, 2018
565e6f4
Fixed bug where keyEvents variable was initialized wrongly. This led …
tobimensch Nov 13, 2018
9c668e8
Travis: upload logs to text host
tombh Nov 14, 2018
748bf9d
Gofmt: some minor capitalisation
tombh Nov 14, 2018
6882327
Allow for using Escape to leave input boxes
tobimensch Nov 15, 2018
fbb1cfc
Travis: upload logs to text host
tombh Nov 14, 2018
2bf920b
Gofmt: some minor capitalisation
tombh Nov 14, 2018
d034497
Merge branch 'vim-mode-experimental' of https://github.com/browsh-org…
tobimensch Nov 15, 2018
e10510f
Added vim feature for editing URL in new tab
tobimensch Nov 15, 2018
3449ec1
Fixed bug in key event handling between vim modes, where the same key…
tobimensch Nov 23, 2018
0c0b907
Merge branch 'vim-mode-experimental' of https://github.com/browsh-org…
tobimensch Nov 23, 2018
e88b42a
Create FUNDING.yml
tombh Jun 13, 2019
ffa586c
Merge remote-tracking branch 'origin/vim-mode-experimental'
Jun 15, 2019
76e7eb1
use test script from master
Jun 16, 2019
36ac818
move vim test into tty test seems helpful
Jun 17, 2019
f290601
Fix for Viper's lowercasing of config keys :/
tombh Jun 18, 2019
8363581
Merge remote-tracking branch 'origin/master' into vim-mode-experimental
Jun 19, 2019
a937e46
test
Jun 19, 2019
9797f40
clarify dev path
Jun 15, 2019
d9251ec
timestamp, wait for body
Jun 20, 2019
7a39926
add test delay for tab
Jun 23, 2019
0c57d3c
better logging and improve tab test stability
Jun 24, 2019
fae952b
Merge branch 'vim-mode-experimental' into localtest
Jun 24, 2019
b9b6270
merge vim mode code
Jun 24, 2019
fdf57cd
changed by prettier
Jun 24, 2019
bfdc1d1
get another version of prettier
Jun 24, 2019
a1bbf9b
Merge remote-tracking branch 'ed2k/vim-mode-experimental' into localtest
Jun 24, 2019
2206efb
get another version of prettier
Jun 24, 2019
ee1291b
Added new vimium.js with code taken from vimium
tobimensch Oct 7, 2018
3b246ff
Created overlayVimMode function for displaying vim navigation state
tobimensch Oct 7, 2018
baf808f
gofmt code
tobimensch Oct 7, 2018
ca30b77
added command for links hints
tobimensch Oct 7, 2018
dc95339
Set default DISPLAY environment variable for xclipboard functionality
tobimensch Oct 7, 2018
ebc8de9
ignore manifest.json backup file
tobimensch Oct 7, 2018
8fc15f3
ignore debug log in interfacer/ directory
tobimensch Oct 18, 2018
fac1af7
Fixed typo in comment
tobimensch Oct 24, 2018
c794f10
Added sessions permission
tobimensch Oct 29, 2018
631483b
Added initial configuration for vim like keybindings.
tobimensch Oct 29, 2018
86acac6
Added duplicate_tab, restore_tab commands.
tobimensch Oct 29, 2018
7b7e6bc
Refactored code using switchToTab and added new features.
tobimensch Oct 29, 2018
15c7b45
Added features needed for vim like navigation to the webextension.
tobimensch Oct 29, 2018
0b7d1dc
Added vim like navigation. This is still in an early stage.
tobimensch Oct 29, 2018
bd5c306
Instead of adding 1 to Y coord, add 1 to height. This fixes an issues
tobimensch Oct 29, 2018
d3fff67
Prettified js files
tobimensch Oct 29, 2018
49eebee
Fixed typo
tobimensch Oct 29, 2018
e039233
Refactors Vim code from tty.go into its own file
tombh Nov 9, 2018
eae72e9
Adds some Vim-specific integration tests
tombh Nov 9, 2018
59d2c31
Fixes tests for Vim mode
tombh Nov 11, 2018
8161ea3
Vim mode: convert unexported symbols to lowercase
tombh Nov 11, 2018
ed79db0
Vim mode: Small updates from PR review
tombh Nov 12, 2018
7a622b2
Fixed bug where keyEvents variable was initialized wrongly. This led …
tobimensch Nov 13, 2018
714cad8
Travis: upload logs to text host
tombh Nov 14, 2018
b780a79
Gofmt: some minor capitalisation
tombh Nov 14, 2018
9359837
Added vim feature for editing URL in new tab
tobimensch Nov 15, 2018
b2ade39
Fixed bug in key event handling between vim modes, where the same key…
tobimensch Nov 23, 2018
5dc678e
Merge branch 'vim-mode-experimental' into vim-mode-experimental
ed2k Jun 25, 2019
21081ad
merge vim_test into tty_test
Jun 25, 2019
5dbb731
Vim Mode 🥳
tombh Jul 21, 2022
ed2a7e0
Merge branch 'master' into vim-mode-2022
tombh Jul 26, 2022
889263a
Linting
tombh Jul 26, 2022
a075246
Merge branch 'master' into vim-mode-2022
tombh Feb 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ interfacer/vendor
interfacer/dist
interfacer/interfacer
interfacer/browsh
interfacer/debug
webextension.go
webext/node_modules
webext/dist/*
webext/manifest.json~
dist
*.xpi

Expand Down
1 change: 1 addition & 0 deletions interfacer/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.18

require (
github.com/NYTimes/gziphandler v1.1.1
github.com/atotto/clipboard v0.1.4
github.com/gdamore/tcell v1.4.0
github.com/go-errors/errors v1.4.2
github.com/gorilla/websocket v1.5.0
Expand Down
2 changes: 2 additions & 0 deletions interfacer/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
Expand Down
12 changes: 10 additions & 2 deletions interfacer/src/browsh/browsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"runtime"
"strconv"
"strings"
"time"

// TCell seems to be one of the best projects in any language for handling terminal
// standards across the major OSs.
Expand All @@ -23,7 +24,7 @@ import (

var (
logo = `
//// ////
//// ////
/ / / /
// //
// // ,,,,,,,,
Expand Down Expand Up @@ -73,7 +74,7 @@ func Log(msg string) {
}
defer f.Close()

msg = msg + "\n"
msg = time.Now().Format("01-02T15:04:05.999 ") + msg + "\n"
if _, wErr := f.WriteString(msg); wErr != nil {
Shutdown(wErr)
}
Expand Down Expand Up @@ -159,6 +160,7 @@ func TTYStart(injectedScreen tcell.Screen) {
Log("Starting Browsh CLI client")
go readStdin()
startWebSocketServer()
setupLinkHints()
}

func toInt(char string) int {
Expand All @@ -185,6 +187,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)
Expand Down
7 changes: 4 additions & 3 deletions interfacer/src/browsh/comms.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func webSocketReader(ws *websocket.Conn) {
triggerSocketWriterClose()
return
}
Shutdown(err)
Shutdown(errors.New(err.Error()))
}
}
}
Expand All @@ -88,8 +88,10 @@ func handleWebextensionCommand(message []byte) {
}
case "/screenshot":
saveScreenshot(parts[1])
case "/link_hints":
parseJSONLinkHints(strings.Join(parts[1:], ","))
default:
Log("WEBEXT: " + string(message))
Log("IGNORE " + string(message))
}
}

Expand Down Expand Up @@ -128,7 +130,6 @@ func webSocketWriter(ws *websocket.Conn) {
defer ws.Close()
for {
message = <-stdinChannel
Log(fmt.Sprintf("TTY sending: %s", message))
if err := ws.WriteMessage(websocket.TextMessage, []byte(message)); err != nil {
if err == websocket.ErrCloseSent {
Log("Socket writer detected that the browser closed the websocket")
Expand Down
60 changes: 60 additions & 0 deletions interfacer/src/browsh/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,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() {
Expand Down
4 changes: 2 additions & 2 deletions interfacer/src/browsh/firefox.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func startHeadlessFirefox() {
}
in := bufio.NewScanner(stdout)
for in.Scan() {
Log("FF-CONSOLE: " + in.Text())
Log("start headless FF-CONSOLE: " + in.Text())
}
}

Expand Down Expand Up @@ -185,7 +185,7 @@ func startWERFirefox() {
strings.Contains(in.Text(), "dbus") {
continue
}
Log("FF-CONSOLE: " + in.Text())
Log("start WER FF-CONSOLE: " + in.Text())
}
Log("WER Firefox unexpectedly closed")
}
Expand Down
14 changes: 11 additions & 3 deletions interfacer/src/browsh/frame_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ func (f *frame) buildFrameText(incoming incomingFrameText) {
if !f.isIncomingFrameTextValid(incoming) {
return
}

var s = "/frame_text "
for _, c := range incoming.Text {
if c != "" {
s = s + c
}
}
Log(s)
f.updateInputBoxes(incoming)
f.populateFrameText(incoming)
}
Expand Down Expand Up @@ -160,9 +168,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
Expand Down Expand Up @@ -312,7 +320,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
}
Expand Down
7 changes: 5 additions & 2 deletions interfacer/src/browsh/input_box.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -237,6 +237,9 @@ func handleInputBoxInput(ev *tcell.EventKey) {
case tcell.KeyEnter:
activeInputBox.removeSelectedText()
activeInputBox.handleEnterKey(ev.Modifiers())
case tcell.KeyEscape:
activeInputBox.isActive = false
activeInputBox = nil
case tcell.KeyRune:
activeInputBox.removeSelectedText()
activeInputBox.cursorInsertRune(ev.Rune())
Expand Down
4 changes: 3 additions & 1 deletion interfacer/src/browsh/input_multiline.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ func (m *multiLine) convert() []rune {
}
if m.isInsideWord() {
// TODO: This sometimes causes a panic :/
m.currentWordish += m.currentCharacter
if m.currentCharacter != "" {
m.currentWordish += m.currentCharacter
}
} else {
m.addWhitespace()
}
Expand Down
85 changes: 79 additions & 6 deletions interfacer/src/browsh/tab.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -61,6 +64,10 @@ func newTab(id int) {
}
}

func restoreTab() {
sendMessageToWebExtension("/restore_tab")
}

func removeTab(id int) {
if len(Tabs) == 1 {
quitBrowsh()
Expand All @@ -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()
}

Expand All @@ -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 {
Expand Down
Loading