Merge pull request #185 from zyedidia/splits

Splits
This commit is contained in:
Zachary Yedidia
2016-07-09 11:41:53 -04:00
committed by GitHub
7 changed files with 247 additions and 36 deletions

View File

@@ -76,6 +76,8 @@ var bindingActions = map[string]func(*View) bool{
"AddTab": (*View).AddTab,
"PreviousTab": (*View).PreviousTab,
"NextTab": (*View).NextTab,
"NextSplit": (*View).NextSplit,
"PreviousSplit": (*View).PreviousSplit,
}
var bindingKeys = map[string]tcell.Key{
@@ -398,6 +400,7 @@ func DefaultBindings() map[string]string {
"CtrlB": "ShellMode",
"CtrlQ": "Quit",
"CtrlE": "CommandMode",
"CtrlW": "NextSplit",
// Emacs-style keybindings
"Alt-f": "WordRight",
@@ -915,11 +918,13 @@ func (v *View) OpenFile() bool {
filename = strings.Replace(filename, "~", home, 1)
file, err := ioutil.ReadFile(filename)
var buf *Buffer
if err != nil {
messenger.Error(err.Error())
return false
// File does not exist -- create an empty buffer with that name
buf = NewBuffer([]byte{}, filename)
} else {
buf = NewBuffer(file, filename)
}
buf := NewBuffer(file, filename)
v.OpenBuffer(buf)
return true
}
@@ -1090,7 +1095,27 @@ func (v *View) Quit() bool {
// Make sure not to quit if there are unsaved changes
if v.CanClose("Quit anyway? (yes, no, save) ") {
v.CloseBuffer()
if len(tabs) > 1 {
if len(tabs[curTab].views) > 1 {
var view *View
if v.splitChild != nil {
view = v.splitChild
view.splitParent = v.splitParent
} else if v.splitParent != nil {
view = v.splitParent
v.splitParent.splitChild = nil
}
view.x, view.y = view.splitOrigPos[0], view.splitOrigPos[1]
view.widthPercent, view.heightPercent = view.splitOrigDimensions[0], view.splitOrigDimensions[1]
view.Resize(screen.Size())
if settings["syntax"].(bool) {
view.matches = Match(view)
}
tabs[curTab].views = tabs[curTab].views[:v.Num+copy(tabs[curTab].views[v.Num:], tabs[curTab].views[v.Num+1:])]
for i, v := range tabs[curTab].views {
v.Num = i
}
tabs[curTab].curView = view.Num
} else 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 {
@@ -1148,6 +1173,28 @@ func (v *View) NextTab() bool {
return false
}
// Changes the view to the next split
func (v *View) NextSplit() bool {
tab := tabs[curTab]
if tab.curView < len(tab.views)-1 {
tab.curView++
} else {
tab.curView = 0
}
return false
}
// Changes the view to the previous split
func (v *View) PreviousSplit() bool {
tab := tabs[curTab]
if tab.curView > 0 {
tab.curView--
} else {
tab.curView = len(tab.views) - 1
}
return false
}
// None is no action
func None() bool {
return false

View File

@@ -2,11 +2,14 @@ package main
import (
"bytes"
"io/ioutil"
"os"
"os/exec"
"os/signal"
"regexp"
"strings"
"github.com/mitchellh/go-homedir"
)
var commands map[string]func([]string)
@@ -18,6 +21,8 @@ var commandActions = map[string]func([]string){
"Quit": Quit,
"Save": Save,
"Replace": Replace,
"VSplit": VSplit,
"HSplit": HSplit,
}
// InitCommands initializes the default commands
@@ -56,6 +61,52 @@ func DefaultCommands() map[string]string {
"quit": "Quit",
"save": "Save",
"replace": "Replace",
"vsplit": "VSplit",
"hsplit": "HSplit",
}
}
// VSplit opens a vertical split with file given in the first argument
// If no file is given, it opens an empty buffer in a new split
func VSplit(args []string) {
if len(args) == 0 {
CurView().VSplit(NewBuffer([]byte{}, ""))
} else {
filename := args[0]
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := ioutil.ReadFile(filename)
var buf *Buffer
if err != nil {
// File does not exist -- create an empty buffer with that name
buf = NewBuffer([]byte{}, filename)
} else {
buf = NewBuffer(file, filename)
}
CurView().VSplit(buf)
}
}
// HSplit opens a horizontal split with file given in the first argument
// If no file is given, it opens an empty buffer in a new split
func HSplit(args []string) {
if len(args) == 0 {
CurView().HSplit(NewBuffer([]byte{}, ""))
} else {
filename := args[0]
home, _ := homedir.Dir()
filename = strings.Replace(filename, "~", home, 1)
file, err := ioutil.ReadFile(filename)
var buf *Buffer
if err != nil {
// File does not exist -- create an empty buffer with that name
buf = NewBuffer([]byte{}, filename)
} else {
buf = NewBuffer(file, filename)
}
CurView().HSplit(buf)
}
}

View File

@@ -152,6 +152,9 @@ func LoadSyntaxFilesFromDir(dir string) {
if style, ok := colorscheme["default"]; ok {
defStyle = style
}
if screen != nil {
screen.SetStyle(defStyle)
}
syntaxFiles = make(map[[2]*regexp.Regexp]FileTypeRules)
files, _ := ioutil.ReadDir(dir)

View File

@@ -174,11 +174,11 @@ func InitScreen() {
// RedrawAll redraws everything -- all the views and the messenger
func RedrawAll() {
messenger.Clear()
DisplayTabs()
messenger.Display()
for _, v := range tabs[curTab].views {
v.Display()
}
DisplayTabs()
messenger.Display()
screen.Show()
}
@@ -281,11 +281,17 @@ func main() {
case *tcell.EventMouse:
if e.Buttons() == tcell.Button1 {
_, h := screen.Size()
_, y := e.Position()
x, y := e.Position()
if y == h-1 && messenger.message != "" {
clipboard.WriteAll(messenger.message)
continue
}
for _, v := range tabs[curTab].views {
if x >= v.x && x < v.x+v.width && y >= v.y && y < v.y+v.height {
tabs[curTab].curView = v.Num
}
}
}
}

View File

@@ -48,13 +48,18 @@ func (sline *Statusline) Display() {
// Maybe there is a unicode filename?
fileRunes := []rune(file)
viewX := sline.view.x
if viewX != 0 {
screen.SetContent(viewX, y, ' ', nil, statusLineStyle)
viewX++
}
for x := 0; x < sline.view.width; x++ {
if x < len(fileRunes) {
screen.SetContent(x, y, fileRunes[x], nil, statusLineStyle)
screen.SetContent(viewX+x, y, fileRunes[x], nil, statusLineStyle)
} else if x >= sline.view.width-len(rightText) && x < len(rightText)+sline.view.width-len(rightText) {
screen.SetContent(x, y, []rune(rightText)[x-sline.view.width+len(rightText)], nil, statusLineStyle)
screen.SetContent(viewX+x, y, []rune(rightText)[x-sline.view.width+len(rightText)], nil, statusLineStyle)
} else {
screen.SetContent(x, y, ' ', nil, statusLineStyle)
screen.SetContent(viewX+x, y, ' ', nil, statusLineStyle)
}
}
}

View File

@@ -87,6 +87,11 @@ type View struct {
matches SyntaxMatches
// The matches from the last frame
lastMatches SyntaxMatches
splitParent *View
splitChild *View
splitOrigDimensions [2]int
splitOrigPos [2]int
}
// NewView returns a new fullscreen view
@@ -124,15 +129,26 @@ func (v *View) Resize(w, h int) {
// Always include 1 line for the command line at the bottom
h--
if len(tabs) > 1 {
// Include one line for the tab bar at the top
h--
v.y = 1
if v.y == 0 {
// Include one line for the tab bar at the top
h--
v.y = 1
}
} else {
v.y = 0
if v.y == 1 {
v.y = 0
}
}
v.width = int(float32(w) * float32(v.widthPercent) / 100)
// We subtract 1 for the statusline
v.height = int(float32(h) * float32(v.heightPercent) / 100)
if w%2 == 0 && v.x > 1 && v.widthPercent < 100 {
v.width++
}
if h%2 == 1 && v.y > 1 && v.heightPercent < 100 {
v.height++
}
if settings["statusline"].(bool) {
// Make room for the status line if it is enabled
v.height--
@@ -218,6 +234,62 @@ func (v *View) ReOpen() {
}
}
// HSplit opens a horizontal split with the given buffer
func (v *View) HSplit(buf *Buffer) bool {
origDimensions := [2]int{v.widthPercent, v.heightPercent}
origPos := [2]int{v.x, v.y}
v.heightPercent /= 2
v.Resize(screen.Size())
newView := NewViewWidthHeight(buf, v.widthPercent, v.heightPercent)
v.splitOrigDimensions = origDimensions
v.splitOrigPos = origPos
newView.splitOrigDimensions = origDimensions
newView.splitOrigPos = origPos
newView.TabNum = v.TabNum
newView.y = v.y + v.height + 1
newView.x = v.x
tab := tabs[v.TabNum]
tab.curView++
newView.Num = len(tab.views)
newView.splitParent = v
v.splitChild = newView
tab.views = append(tab.views, newView)
newView.Resize(screen.Size())
return false
}
// VSplit opens a vertical split with the given buffer
func (v *View) VSplit(buf *Buffer) bool {
origDimensions := [2]int{v.widthPercent, v.heightPercent}
origPos := [2]int{v.x, v.y}
v.widthPercent /= 2
v.Resize(screen.Size())
newView := NewViewWidthHeight(buf, v.widthPercent, v.heightPercent)
v.splitOrigDimensions = origDimensions
v.splitOrigPos = origPos
newView.splitOrigDimensions = origDimensions
newView.splitOrigPos = origPos
newView.TabNum = v.TabNum
newView.y = v.y
newView.x = v.x + v.width
tab := tabs[v.TabNum]
tab.curView++
newView.Num = len(tab.views)
newView.splitParent = v
v.splitChild = newView
tab.views = append(tab.views, newView)
newView.Resize(screen.Size())
return false
}
// Relocate moves the view window so that the cursor is in view
// This is useful if the user has scrolled far away, and then starts typing
func (v *View) Relocate() bool {
@@ -340,7 +412,7 @@ func (v *View) HandleEvent(event tcell.Event) {
v.freshClip = false
case *tcell.EventMouse:
x, y := e.Position()
x -= v.lineNumOffset - v.leftCol
x -= v.lineNumOffset - v.leftCol + v.x
y += v.Topline - v.y
// Don't relocate for mouse events
relocate = false
@@ -457,8 +529,15 @@ func (v *View) ClearAllGutterMessages() {
}
}
func (v *View) drawCell(x, y int, ch rune, combc []rune, style tcell.Style) {
if x >= v.x && x < v.x+v.width && y >= v.y && y < v.y+v.height {
screen.SetContent(x, y, ch, combc, style)
}
}
// DisplayView renders the view to the screen
func (v *View) DisplayView() {
// The character number of the character in the top left of the screen
charNum := Loc{0, v.Topline}
@@ -483,13 +562,23 @@ func (v *View) DisplayView() {
v.lineNumOffset += 2
}
if v.x != 0 {
// One space for the extra split divider
v.lineNumOffset++
}
for lineN := 0; lineN < v.height; lineN++ {
x := v.x
if v.x != 0 {
// Draw the split divider
v.drawCell(x, lineN+v.y, ' ', nil, defStyle.Reverse(true))
x++
}
// If the buffer is smaller than the view height
if lineN+v.Topline >= v.Buf.NumLines {
// We have to clear all this space
for i := 0; i < v.width; i++ {
screen.SetContent(i, lineN+v.y, ' ', nil, defStyle)
for i := x; i < v.x+v.width; i++ {
v.drawCell(i, lineN+v.y, ' ', nil, defStyle)
}
continue
@@ -517,9 +606,9 @@ func (v *View) DisplayView() {
gutterStyle = style
}
}
screen.SetContent(x, lineN+v.y, '>', nil, gutterStyle)
v.drawCell(x, lineN+v.y, '>', nil, gutterStyle)
x++
screen.SetContent(x, lineN+v.y, '>', nil, gutterStyle)
v.drawCell(x, lineN+v.y, '>', nil, gutterStyle)
x++
if v.Cursor.Y == lineN+v.Topline {
messenger.Message(msg.msg)
@@ -529,9 +618,9 @@ func (v *View) DisplayView() {
}
}
if !msgOnLine {
screen.SetContent(x, lineN+v.y, ' ', nil, defStyle)
v.drawCell(x, lineN+v.y, ' ', nil, defStyle)
x++
screen.SetContent(x, lineN+v.y, ' ', nil, defStyle)
v.drawCell(x, lineN+v.y, ' ', nil, defStyle)
x++
if v.Cursor.Y == lineN+v.Topline && messenger.gutterMessage {
messenger.Reset()
@@ -550,18 +639,18 @@ func (v *View) DisplayView() {
if settings["ruler"] == true {
lineNum = strconv.Itoa(lineN + v.Topline + 1)
for i := 0; i < maxLineLength-len(lineNum); i++ {
screen.SetContent(x, lineN+v.y, ' ', nil, lineNumStyle)
v.drawCell(x, lineN+v.y, ' ', nil, lineNumStyle)
x++
}
// Write the actual line number
for _, ch := range lineNum {
screen.SetContent(x, lineN+v.y, ch, nil, lineNumStyle)
v.drawCell(x, lineN+v.y, ch, nil, lineNumStyle)
x++
}
if settings["ruler"] == true {
// Write the extra space
screen.SetContent(x, lineN+v.y, ' ', nil, lineNumStyle)
v.drawCell(x, lineN+v.y, ' ', nil, lineNumStyle)
x++
}
}
@@ -587,7 +676,7 @@ func (v *View) DisplayView() {
lineStyle = highlightStyle
}
if settings["cursorline"].(bool) && !v.Cursor.HasSelection() && v.Cursor.Y == lineN+v.Topline {
if settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == lineN+v.Topline {
if style, ok := colorscheme["cursor-line"]; ok {
fg, _, _ := style.Decompose()
lineStyle = lineStyle.Background(fg)
@@ -609,7 +698,7 @@ func (v *View) DisplayView() {
lineIndentStyle = style
}
}
if settings["cursorline"].(bool) && !v.Cursor.HasSelection() && v.Cursor.Y == lineN+v.Topline {
if settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == lineN+v.Topline {
if style, ok := colorscheme["cursor-line"]; ok {
fg, _, _ := style.Decompose()
lineIndentStyle = lineIndentStyle.Background(fg)
@@ -617,28 +706,28 @@ func (v *View) DisplayView() {
}
indentChar := []rune(settings["indentchar"].(string))
if x-v.leftCol >= v.lineNumOffset {
screen.SetContent(x-v.leftCol, lineN+v.y, indentChar[0], nil, lineIndentStyle)
v.drawCell(x-v.leftCol, lineN+v.y, indentChar[0], nil, lineIndentStyle)
}
tabSize := int(settings["tabsize"].(float64))
for i := 0; i < tabSize-1; i++ {
x++
if x-v.leftCol >= v.lineNumOffset {
screen.SetContent(x-v.leftCol, lineN+v.y, ' ', nil, lineStyle)
v.drawCell(x-v.leftCol, lineN+v.y, ' ', nil, lineStyle)
}
}
} else if runewidth.RuneWidth(ch) > 1 {
if x-v.leftCol >= v.lineNumOffset {
screen.SetContent(x-v.leftCol, lineN, ch, nil, lineStyle)
v.drawCell(x-v.leftCol, lineN+v.y, ch, nil, lineStyle)
}
for i := 0; i < runewidth.RuneWidth(ch)-1; i++ {
x++
if x-v.leftCol >= v.lineNumOffset {
screen.SetContent(x-v.leftCol, lineN, ' ', nil, lineStyle)
v.drawCell(x-v.leftCol, lineN+v.y, ' ', nil, lineStyle)
}
}
} else {
if x-v.leftCol >= v.lineNumOffset {
screen.SetContent(x-v.leftCol, lineN+v.y, ch, nil, lineStyle)
v.drawCell(x-v.leftCol, lineN+v.y, ch, nil, lineStyle)
}
}
charNum = charNum.Move(1, v.Buf)
@@ -657,22 +746,22 @@ func (v *View) DisplayView() {
if style, ok := colorscheme["selection"]; ok {
selectStyle = style
}
screen.SetContent(x-v.leftCol, lineN+v.y, ' ', nil, selectStyle)
v.drawCell(x-v.leftCol, lineN+v.y, ' ', nil, selectStyle)
x++
}
charNum = charNum.Move(1, v.Buf)
for i := 0; i < v.width-(x-v.leftCol); i++ {
for i := 0; i < v.width-((x-v.x)-v.leftCol); i++ {
lineStyle := defStyle
if settings["cursorline"].(bool) && !v.Cursor.HasSelection() && v.Cursor.Y == lineN+v.Topline {
if settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == lineN+v.Topline {
if style, ok := colorscheme["cursor-line"]; ok {
fg, _, _ := style.Decompose()
lineStyle = lineStyle.Background(fg)
}
}
if !(x-v.leftCol < v.lineNumOffset) {
screen.SetContent(x+i, lineN+v.y, ' ', nil, lineStyle)
v.drawCell(x+i, lineN+v.y, ' ', nil, lineStyle)
}
}
}
@@ -691,7 +780,9 @@ func (v *View) DisplayCursor() {
// Display renders the view, the cursor, and statusline
func (v *View) Display() {
v.DisplayView()
v.DisplayCursor()
if v.Num == tabs[curTab].curView {
v.DisplayCursor()
}
if settings["statusline"].(bool) {
v.sline.Display()
}

View File

@@ -78,6 +78,7 @@ you can rebind them to your liking.
"CtrlB": "ShellMode",
"CtrlQ": "Quit",
"CtrlE": "CommandMode",
"CtrlW": "NextSplit",
// Emacs-style keybindings
"Alt-f": "WordRight",
@@ -114,6 +115,7 @@ You can execute an editor command by pressing `Ctrl-e` followed by the command.
Here are the possible commands that you can use.
* `quit`: Quits micro.
* `save`: Saves the current buffer.
* `replace "search" "value" flags`: This will replace `search` with `value`.
@@ -133,6 +135,12 @@ Here are the possible commands that you can use.
* `bind key action`: creates a keybinding from key to action. See the sections on
keybindings above for more info about what keys and actions are available.
* `vsplit filename`: opens a vertical split with `filename`. If no filename is
provided, a vertical split is opened with an empty buffer
* `hsplit filename`: same as `vsplit` but opens a horizontal split instead of
a vertical split
### Options
Micro stores all of the user configuration in its configuration directory.