Skip to content

Commit

Permalink
Add support for tabs
Browse files Browse the repository at this point in the history
Merge branch 'tabbar'

This branch adds support for having multiple tabs open, each viewing one
file. Use CtrlT to open a new tab empty tab and then CtrlO to open a
file in that tab. Use can also just open multiple files from the command
line: `micro file1.txt file2.txt ...`. Use Ctrl-] and Ctrl-\ to move
between the tabs, or simply click them with the mouse.
  • Loading branch information
zyedidia committed Jun 8, 2016
2 parents a8de4bc + 2c73e1c commit 82c7994
Show file tree
Hide file tree
Showing 11 changed files with 295 additions and 88 deletions.
66 changes: 59 additions & 7 deletions cmd/micro/bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ var bindingActions = map[string]func(*View) bool{
"ShellMode": (*View).ShellMode,
"CommandMode": (*View).CommandMode,
"Quit": (*View).Quit,
"AddTab": (*View).AddTab,
"PreviousTab": (*View).PreviousTab,
"NextTab": (*View).NextTab,
}

var bindingKeys = map[string]tcell.Key{
Expand Down Expand Up @@ -380,6 +383,9 @@ func DefaultBindings() map[string]string {
"CtrlD": "DuplicateLine",
"CtrlV": "Paste",
"CtrlA": "SelectAll",
"CtrlT": "AddTab",
"CtrlRightSq": "PreviousTab",
"CtrlBackslash": "NextTab",
"Home": "Start",
"End": "End",
"PageUp": "CursorPageUp",
Expand Down Expand Up @@ -902,20 +908,21 @@ func (v *View) OpenFile() bool {
if v.CanClose("Continue? (yes, no, save) ") {
filename, canceled := messenger.Prompt("File to open: ", "Open")
if canceled {
return true
return false
}
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := ioutil.ReadFile(filename)

if err != nil {
messenger.Error(err.Error())
return true
return false
}
buf := NewBuffer(file, filename)
v.OpenBuffer(buf)
return true
}
return true
return false
}

// Start moves the viewport to the start of the buffer
Expand Down Expand Up @@ -1080,10 +1087,55 @@ func (v *View) Quit() bool {
return v.ToggleHelp()
}
// Make sure not to quit if there are unsaved changes
if views[mainView].CanClose("Quit anyway? (yes, no, save) ") {
views[mainView].CloseBuffer()
screen.Fini()
os.Exit(0)
if v.CanClose("Quit anyway? (yes, no, save) ") {
v.CloseBuffer()
if len(tabs) > 1 {
if len(tabs[v.TabNum].views) == 1 {
tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
for i, t := range tabs {
t.SetNum(i)
}
if curTab >= len(tabs) {
curTab--
}
if curTab == 0 {
CurView().Resize(screen.Size())
CurView().matches = Match(CurView())
}
}
} else {
screen.Fini()
os.Exit(0)
}
}
return false
}

func (v *View) AddTab() bool {
tab := NewTabFromView(NewView(NewBuffer([]byte{}, "")))
tab.SetNum(len(tabs))
tabs = append(tabs, tab)
curTab++
if len(tabs) == 2 {
for _, t := range tabs {
for _, v := range t.views {
v.Resize(screen.Size())
}
}
}
return true
}

func (v *View) PreviousTab() bool {
if curTab > 0 {
curTab--
}
return false
}

func (v *View) NextTab() bool {
if curTab < len(tabs)-1 {
curTab++
}
return false
}
Expand Down
5 changes: 5 additions & 0 deletions cmd/micro/buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,14 @@ type SerializedBuffer struct {
func NewBuffer(txt []byte, path string) *Buffer {
b := new(Buffer)
b.LineArray = NewLineArray(txt)

b.Path = path
b.Name = path

if path == "" {
b.Name = "No name"
}

b.ModTime, _ = GetModTime(b.Path)

b.EventHandler = NewEventHandler(b)
Expand Down
12 changes: 7 additions & 5 deletions cmd/micro/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ func DefaultCommands() map[string]string {
// Set sets an option
func Set(args []string) {
// Set an option and we have to set it for every view
for _, view := range views {
SetOption(view, args)
for _, tab := range tabs {
for _, view := range tab.views {
SetOption(view, args)
}
}
}

Expand All @@ -85,13 +87,13 @@ func Run(args []string) {
// Quit closes the main view
func Quit(args []string) {
// Close the main view
views[mainView].Quit()
CurView().Quit()
}

// Save saves the buffer in the main view
func Save(args []string) {
// Save the main view
views[mainView].Save()
CurView().Save()
}

// Replace runs search and replace
Expand Down Expand Up @@ -138,7 +140,7 @@ func Replace(args []string) {
return
}

view := views[mainView]
view := CurView()

found := false
for {
Expand Down
63 changes: 37 additions & 26 deletions cmd/micro/micro.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ var (
L *lua.LState

// The list of views
views []*View
// This is the currently open view
// It's just an index to the view in the views array
mainView int
tabs []*Tab
// This is the currently open tab
// It's just an index to the tab in the tabs array
curTab int
)

// LoadInput loads the file input for the editor
func LoadInput() (string, []byte, error) {
func LoadInput() []*Buffer {
// There are a number of ways micro should start given its input

// 1. If it is given a file in os.Args, it should open that
Expand All @@ -72,23 +72,34 @@ func LoadInput() (string, []byte, error) {
var filename string
var input []byte
var err error
var buffers []*Buffer

if len(os.Args) > 1 {
// Option 1
filename = os.Args[1]
// Check that the file exists
if _, e := os.Stat(filename); e == nil {
input, err = ioutil.ReadFile(filename)
for i := 1; i < len(os.Args); i++ {
filename = os.Args[i]
// Check that the file exists
if _, e := os.Stat(filename); e == nil {
input, err = ioutil.ReadFile(filename)
if err != nil {
TermMessage(err)
continue
}
}
buffers = append(buffers, NewBuffer(input, filename))
}
} else if !isatty.IsTerminal(os.Stdin.Fd()) {
// Option 2
// The input is not a terminal, so something is being piped in
// and we should read from stdin
input, err = ioutil.ReadAll(os.Stdin)
buffers = append(buffers, NewBuffer(input, filename))
} else {
// Option 3, just open an empty buffer
buffers = append(buffers, NewBuffer(input, filename))
}

// Option 3, or just return whatever we got
return filename, input, err
return buffers
}

// InitConfigDir finds the configuration directory for micro according to the XDG spec.
Expand Down Expand Up @@ -170,9 +181,10 @@ func InitScreen() {
// RedrawAll redraws everything -- all the views and the messenger
func RedrawAll() {
messenger.Clear()
for _, v := range views {
for _, v := range tabs[curTab].views {
v.Display()
}
DisplayTabs()
messenger.Display()
screen.Show()
}
Expand All @@ -186,12 +198,6 @@ func main() {
os.Exit(0)
}

filename, input, err := LoadInput()
if err != nil {
fmt.Println(err)
os.Exit(1)
}

L = lua.NewState()
defer L.Close()

Expand All @@ -210,8 +216,6 @@ func main() {
// Load the help files
LoadHelp()

buf := NewBuffer(input, filename)

InitScreen()

// This is just so if we have an error, we can exit cleanly and not completely
Expand All @@ -229,17 +233,21 @@ func main() {

messenger = new(Messenger)
messenger.history = make(map[string][]string)
views = make([]*View, 1)
views[0] = NewView(buf)

buffers := LoadInput()
for _, buf := range buffers {
tabs = append(tabs, NewTabFromView(NewView(buf)))
}

L.SetGlobal("OS", luar.New(L, runtime.GOOS))
L.SetGlobal("views", luar.New(L, views))
L.SetGlobal("mainView", luar.New(L, mainView))
L.SetGlobal("tabs", luar.New(L, tabs))
L.SetGlobal("curTab", luar.New(L, curTab))
L.SetGlobal("messenger", luar.New(L, messenger))
L.SetGlobal("GetOption", luar.New(L, GetOption))
L.SetGlobal("AddOption", luar.New(L, AddOption))
L.SetGlobal("BindKey", luar.New(L, BindKey))
L.SetGlobal("MakeCommand", luar.New(L, MakeCommand))
L.SetGlobal("CurView", luar.New(L, CurView))

LoadPlugins()

Expand All @@ -249,14 +257,17 @@ func main() {

// Wait for the user's action
event := screen.PollEvent()
if TabbarHandleMouseEvent(event) {
continue
}

if searching {
// Since searching is done in real time, we need to redraw every time
// there is a new event in the search bar
HandleSearchEvent(event, views[mainView])
HandleSearchEvent(event, CurView())
} else {
// Send it to the view
views[mainView].HandleEvent(event)
CurView().HandleEvent(event)
}
}
}
32 changes: 16 additions & 16 deletions cmd/micro/runtime.go

Large diffs are not rendered by default.

6 changes: 1 addition & 5 deletions cmd/micro/statusline.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,9 @@ type Statusline struct {
// Display draws the statusline to the screen
func (sline *Statusline) Display() {
// We'll draw the line at the lowest line in the view
y := sline.view.height
y := sline.view.height + sline.view.y

file := sline.view.Buf.Name
// If the name is empty, use 'No name'
if file == "" {
file = "No name"
}

// If the buffer is dirty (has been modified) write a little '+'
if sline.view.Buf.IsModified {
Expand Down

0 comments on commit 82c7994

Please sign in to comment.