Add local settings for each buffer

This commit is contained in:
Zachary Yedidia
2016-08-24 16:55:44 -07:00
parent 60b84c7aba
commit 261748bd56
12 changed files with 159 additions and 94 deletions

View File

@@ -447,7 +447,7 @@ func (v *View) InsertNewline(usePlugin bool) bool {
ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
v.Cursor.Right()
if settings["autoindent"].(bool) {
if v.Buf.Settings["autoindent"].(bool) {
v.Buf.Insert(v.Cursor.Loc, ws)
for i := 0; i < len(ws); i++ {
v.Cursor.Right()
@@ -487,8 +487,8 @@ func (v *View) Backspace(usePlugin bool) bool {
// whitespace at the start of the line, we should delete as if its a
// tab (tabSize number of spaces)
lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
tabSize := int(settings["tabsize"].(float64))
if settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
tabSize := int(v.Buf.Settings["tabsize"].(float64))
if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
loc := v.Cursor.Loc
v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
cx, cy := v.Cursor.X, v.Cursor.Y
@@ -581,8 +581,8 @@ func (v *View) IndentSelection(usePlugin bool) bool {
end := v.Cursor.CurSelection[1].Move(-1, v.Buf).Y
endX := v.Cursor.CurSelection[1].Move(-1, v.Buf).X
for i := start; i <= end; i++ {
if settings["tabstospaces"].(bool) {
tabsize := int(settings["tabsize"].(float64))
if v.Buf.Settings["tabstospaces"].(bool) {
tabsize := int(v.Buf.Settings["tabsize"].(float64))
v.Buf.Insert(Loc{0, i}, Spaces(tabsize))
if i == start {
if v.Cursor.CurSelection[0].X > 0 {
@@ -626,8 +626,8 @@ func (v *View) OutdentSelection(usePlugin bool) bool {
endX := v.Cursor.CurSelection[1].Move(-1, v.Buf).X
for i := start; i <= end; i++ {
if len(GetLeadingWhitespace(v.Buf.Line(i))) > 0 {
if settings["tabstospaces"].(bool) {
tabsize := int(settings["tabsize"].(float64))
if v.Buf.Settings["tabstospaces"].(bool) {
tabsize := int(v.Buf.Settings["tabsize"].(float64))
for j := 0; j < tabsize; j++ {
if len(GetLeadingWhitespace(v.Buf.Line(i))) == 0 {
break
@@ -675,8 +675,8 @@ func (v *View) InsertTab(usePlugin bool) bool {
return false
}
// Insert a tab
if settings["tabstospaces"].(bool) {
tabSize := int(settings["tabsize"].(float64))
if v.Buf.Settings["tabstospaces"].(bool) {
tabSize := int(v.Buf.Settings["tabsize"].(float64))
v.Buf.Insert(v.Cursor.Loc, Spaces(tabSize))
for i := 0; i < tabSize; i++ {
v.Cursor.Right()
@@ -1162,11 +1162,11 @@ func (v *View) ToggleRuler(usePlugin bool) bool {
return false
}
if settings["ruler"] == false {
settings["ruler"] = true
if v.Buf.Settings["ruler"] == false {
v.Buf.Settings["ruler"] = true
messenger.Message("Enabled ruler")
} else {
settings["ruler"] = false
v.Buf.Settings["ruler"] = false
messenger.Message("Disabled ruler")
}

View File

@@ -91,7 +91,7 @@ func HelpComplete(input string) (string, []string) {
// OptionComplete autocompletes options
func OptionComplete(input string) (string, []string) {
var suggestions []string
for option := range settings {
for option := range globalSettings {
if strings.HasPrefix(option, input) {
suggestions = append(suggestions, option)
}

View File

@@ -41,6 +41,9 @@ type Buffer struct {
rules []SyntaxRule
// The buffer's filetype
FileType string
// Buffer local settings
Settings map[string]interface{}
}
// The SerializedBuffer holds the types that get serialized when a buffer is saved
@@ -56,6 +59,11 @@ func NewBuffer(txt []byte, path string) *Buffer {
b := new(Buffer)
b.LineArray = NewLineArray(txt)
b.Settings = make(map[string]interface{})
for k, v := range globalSettings {
b.Settings[k] = v
}
b.Path = path
b.Name = path
@@ -85,7 +93,7 @@ func NewBuffer(txt []byte, path string) *Buffer {
buf: b,
}
if settings["savecursor"].(bool) || settings["saveundo"].(bool) {
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
// If either savecursor or saveundo is turned on, we need to load the serialized information
// from ~/.config/micro/buffers
absPath, _ := filepath.Abs(b.Path)
@@ -98,13 +106,13 @@ func NewBuffer(txt []byte, path string) *Buffer {
if err != nil {
TermMessage(err.Error(), "\n", "You may want to remove the files in ~/.config/micro/buffers (these files store the information for the 'saveundo' and 'savecursor' options) if this problem persists.")
}
if settings["savecursor"].(bool) {
if b.Settings["savecursor"].(bool) {
b.Cursor = buffer.Cursor
b.Cursor.buf = b
b.Cursor.Relocate()
}
if settings["saveundo"].(bool) {
if b.Settings["saveundo"].(bool) {
// We should only use last time's eventhandler if the file wasn't by someone else in the meantime
if b.ModTime == buffer.ModTime {
b.EventHandler = buffer.EventHandler
@@ -184,7 +192,7 @@ func (b *Buffer) SaveWithSudo() error {
// Serialize serializes the buffer to configDir/buffers
func (b *Buffer) Serialize() error {
if settings["savecursor"].(bool) || settings["saveundo"].(bool) {
if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
absPath, _ := filepath.Abs(b.Path)
file, err := os.Create(configDir + "/buffers/" + EscapePath(absPath))
if err == nil {

View File

@@ -24,7 +24,7 @@ func InitColorscheme() {
// LoadDefaultColorscheme loads the default colorscheme from $(configDir)/colorschemes
func LoadDefaultColorscheme() {
LoadColorscheme(settings["colorscheme"].(string), configDir+"/colorschemes")
LoadColorscheme(globalSettings["colorscheme"].(string), configDir+"/colorschemes")
}
// LoadColorscheme loads the given colorscheme from a directory

View File

@@ -25,16 +25,17 @@ type StrCommand struct {
var commands map[string]Command
var commandActions = map[string]func([]string){
"Set": Set,
"Run": Run,
"Bind": Bind,
"Quit": Quit,
"Save": Save,
"Replace": Replace,
"VSplit": VSplit,
"HSplit": HSplit,
"Tab": NewTab,
"Help": Help,
"Set": Set,
"SetLocal": SetLocal,
"Run": Run,
"Bind": Bind,
"Quit": Quit,
"Save": Save,
"Replace": Replace,
"VSplit": VSplit,
"HSplit": HSplit,
"Tab": NewTab,
"Help": Help,
}
// InitCommands initializes the default commands
@@ -67,16 +68,17 @@ func MakeCommand(name, function string, completions ...Completion) {
// DefaultCommands returns a map containing micro's default commands
func DefaultCommands() map[string]StrCommand {
return map[string]StrCommand{
"set": StrCommand{"Set", []Completion{OptionCompletion, NoCompletion}},
"bind": StrCommand{"Bind", []Completion{NoCompletion}},
"run": StrCommand{"Run", []Completion{NoCompletion}},
"quit": StrCommand{"Quit", []Completion{NoCompletion}},
"save": StrCommand{"Save", []Completion{NoCompletion}},
"replace": StrCommand{"Replace", []Completion{NoCompletion}},
"vsplit": StrCommand{"VSplit", []Completion{FileCompletion, NoCompletion}},
"hsplit": StrCommand{"HSplit", []Completion{FileCompletion, NoCompletion}},
"tab": StrCommand{"Tab", []Completion{FileCompletion, NoCompletion}},
"help": StrCommand{"Help", []Completion{HelpCompletion, NoCompletion}},
"set": StrCommand{"Set", []Completion{OptionCompletion, NoCompletion}},
"setlocal": StrCommand{"SetLocal", []Completion{OptionCompletion, NoCompletion}},
"bind": StrCommand{"Bind", []Completion{NoCompletion}},
"run": StrCommand{"Run", []Completion{NoCompletion}},
"quit": StrCommand{"Quit", []Completion{NoCompletion}},
"save": StrCommand{"Save", []Completion{NoCompletion}},
"replace": StrCommand{"Replace", []Completion{NoCompletion}},
"vsplit": StrCommand{"VSplit", []Completion{FileCompletion, NoCompletion}},
"hsplit": StrCommand{"HSplit", []Completion{FileCompletion, NoCompletion}},
"tab": StrCommand{"Tab", []Completion{FileCompletion, NoCompletion}},
"help": StrCommand{"Help", []Completion{HelpCompletion, NoCompletion}},
}
}
@@ -175,6 +177,17 @@ func Set(args []string) {
SetOptionAndSettings(option, value)
}
func SetLocal(args []string) {
if len(args) < 2 {
return
}
option := strings.TrimSpace(args[0])
value := strings.TrimSpace(args[1])
SetLocalOption(option, value, CurView())
}
// Bind creates a new keybinding
func Bind(args []string) {
if len(args) != 2 {
@@ -259,7 +272,7 @@ func Replace(args []string) {
// The 'check' flag was used
Search(search, view, true)
view.Relocate()
if settings["syntax"].(bool) {
if view.Buf.Settings["syntax"].(bool) {
view.matches = Match(view)
}
RedrawAll()

View File

@@ -294,12 +294,12 @@ func (c *Cursor) Start() {
// GetCharPosInLine gets the char position of a visual x y coordinate (this is necessary because tabs are 1 char but 4 visual spaces)
func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
// Get the tab size
tabSize := int(settings["tabsize"].(float64))
visualLineLen := StringWidth(c.buf.Line(lineNum))
tabSize := int(c.buf.Settings["tabsize"].(float64))
visualLineLen := StringWidth(c.buf.Line(lineNum), tabSize)
if visualPos > visualLineLen {
visualPos = visualLineLen
}
width := WidthOfLargeRunes(c.buf.Line(lineNum))
width := WidthOfLargeRunes(c.buf.Line(lineNum), tabSize)
if visualPos >= width {
return visualPos - width
}
@@ -309,7 +309,8 @@ func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
// GetVisualX returns the x value of the cursor in visual spaces
func (c *Cursor) GetVisualX() int {
runes := []rune(c.buf.Line(c.Y))
return StringWidth(string(runes[:c.X]))
tabSize := int(c.buf.Settings["tabsize"].(float64))
return StringWidth(string(runes[:c.X]), tabSize)
}
// Relocate makes sure that the cursor is inside the bounds of the buffer

View File

@@ -248,6 +248,14 @@ func main() {
tab := NewTabFromView(NewView(buf))
tab.SetNum(len(tabs))
tabs = append(tabs, tab)
for _, t := range tabs {
for _, v := range t.views {
v.Center(false)
if globalSettings["syntax"].(bool) {
v.matches = Match(v)
}
}
}
}
// Load all the plugin stuff
@@ -258,7 +266,7 @@ func main() {
L.SetGlobal("messenger", luar.New(L, messenger))
L.SetGlobal("GetOption", luar.New(L, GetOption))
L.SetGlobal("AddOption", luar.New(L, AddOption))
L.SetGlobal("SetOption", luar.New(L, SetOption))
L.SetGlobal("SetOption", luar.New(L, SetGlobalOption))
L.SetGlobal("BindKey", luar.New(L, BindKey))
L.SetGlobal("MakeCommand", luar.New(L, MakeCommand))
L.SetGlobal("CurView", luar.New(L, CurView))
@@ -283,7 +291,7 @@ func main() {
if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
TermMessage(err)
}
if settings["syntax"].(bool) {
if v.Buf.Settings["syntax"].(bool) {
v.matches = Match(v)
}
}

View File

@@ -90,7 +90,7 @@ func Search(searchStr string, v *View, down bool) {
str = string([]rune(text)[:searchStart])
}
r, err := regexp.Compile(searchStr)
if settings["ignorecase"].(bool) {
if v.Buf.Settings["ignorecase"].(bool) {
r, err = regexp.Compile("(?i)" + searchStr)
}
if err != nil {

View File

@@ -10,7 +10,7 @@ import (
)
// The options that the user can set
var settings map[string]interface{}
var globalSettings map[string]interface{}
// InitSettings initializes the options map and sets all options to their default values
func InitSettings() {
@@ -31,12 +31,12 @@ func InitSettings() {
}
}
settings = make(map[string]interface{})
globalSettings = make(map[string]interface{})
for k, v := range defaults {
settings[k] = v
globalSettings[k] = v
}
for k, v := range parsed {
settings[k] = v
globalSettings[k] = v
}
err := WriteSettings(filename)
@@ -49,7 +49,7 @@ func InitSettings() {
func WriteSettings(filename string) error {
var err error
if _, e := os.Stat(configDir); e == nil {
txt, _ := json.MarshalIndent(settings, "", " ")
txt, _ := json.MarshalIndent(globalSettings, "", " ")
err = ioutil.WriteFile(filename, txt, 0644)
}
return err
@@ -57,7 +57,7 @@ func WriteSettings(filename string) error {
// AddOption creates a new option. This is meant to be called by plugins to add options.
func AddOption(name string, value interface{}) {
settings[name] = value
globalSettings[name] = value
err := WriteSettings(configDir + "/settings.json")
if err != nil {
TermMessage("Error writing settings.json file: " + err.Error())
@@ -65,8 +65,19 @@ func AddOption(name string, value interface{}) {
}
// GetOption returns the specified option. This is meant to be called by plugins to add options.
func GetGlobalOption(name string) interface{} {
return globalSettings[name]
}
func GetLocalOption(name string, buf *Buffer) interface{} {
return buf.Settings[name]
}
func GetOption(name string) interface{} {
return settings[name]
if GetLocalOption(name, CurView().Buf) != nil {
return GetLocalOption(name, CurView().Buf)
}
return GetGlobalOption(name)
}
// DefaultSettings returns the default settings for micro
@@ -90,48 +101,72 @@ func DefaultSettings() map[string]interface{} {
}
// SetOption attempts to set the given option to the value
func SetOption(option, value string) error {
if _, ok := settings[option]; !ok {
func SetGlobalOption(option, value string) error {
if _, ok := globalSettings[option]; !ok {
return errors.New("Invalid option")
}
kind := reflect.TypeOf(settings[option]).Kind()
kind := reflect.TypeOf(globalSettings[option]).Kind()
if kind == reflect.Bool {
b, err := ParseBool(value)
if err != nil {
return errors.New("Invalid value")
}
settings[option] = b
globalSettings[option] = b
} else if kind == reflect.String {
settings[option] = value
globalSettings[option] = value
} else if kind == reflect.Float64 {
i, err := strconv.Atoi(value)
if err != nil {
return errors.New("Invalid value")
}
settings[option] = float64(i)
globalSettings[option] = float64(i)
}
for _, tab := range tabs {
for _, view := range tab.views {
SetLocalOption(option, value, view)
}
}
return nil
}
func SetLocalOption(option, value string, view *View) error {
buf := view.Buf
if _, ok := buf.Settings[option]; !ok {
return errors.New("Invalid option")
}
kind := reflect.TypeOf(buf.Settings[option]).Kind()
if kind == reflect.Bool {
b, err := ParseBool(value)
if err != nil {
return errors.New("Invalid value")
}
buf.Settings[option] = b
} else if kind == reflect.String {
buf.Settings[option] = value
} else if kind == reflect.Float64 {
i, err := strconv.Atoi(value)
if err != nil {
return errors.New("Invalid value")
}
buf.Settings[option] = float64(i)
}
if option == "colorscheme" {
LoadSyntaxFiles()
for _, tab := range tabs {
for _, view := range tab.views {
view.Buf.UpdateRules()
if settings["syntax"].(bool) {
view.matches = Match(view)
}
}
buf.UpdateRules()
if buf.Settings["syntax"].(bool) {
view.matches = Match(view)
}
}
if option == "statusline" {
for _, tab := range tabs {
for _, view := range tab.views {
view.ToggleStatusLine()
if settings["syntax"].(bool) {
view.matches = Match(view)
}
}
view.ToggleStatusLine()
if buf.Settings["syntax"].(bool) {
view.matches = Match(view)
}
}
@@ -142,7 +177,7 @@ func SetOption(option, value string) error {
func SetOptionAndSettings(option, value string) {
filename := configDir + "/settings.json"
err := SetOption(option, value)
err := SetGlobalOption(option, value)
if err != nil {
messenger.Message(err.Error())

View File

@@ -148,7 +148,7 @@ func (s *SplitTree) ResizeSplits() {
}
// n.view.ToggleStatusLine()
_, screenH := screen.Size()
if settings["statusline"].(bool) || (n.view.y+n.view.height) != screenH-1 {
if n.view.Buf.Settings["statusline"].(bool) || (n.view.y+n.view.height) != screenH-1 {
n.view.height--
}

View File

@@ -152,20 +152,20 @@ func GetModTime(path string) (time.Time, bool) {
}
// StringWidth returns the width of a string where tabs count as `tabsize` width
func StringWidth(str string) int {
func StringWidth(str string, tabsize int) int {
sw := runewidth.StringWidth(str)
sw += NumOccurences(str, '\t') * (int(settings["tabsize"].(float64)) - 1)
sw += NumOccurences(str, '\t') * (tabsize - 1)
return sw
}
// WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
// that have a width larger than 1 (this also counts tabs as `tabsize` width)
func WidthOfLargeRunes(str string) int {
func WidthOfLargeRunes(str string, tabsize int) int {
count := 0
for _, ch := range str {
var w int
if ch == '\t' {
w = int(settings["tabsize"].(float64))
w = tabsize
} else {
w = runewidth.RuneWidth(ch)
}

View File

@@ -107,7 +107,7 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
view: v,
}
if settings["statusline"].(bool) {
if v.Buf.Settings["statusline"].(bool) {
v.height--
}
@@ -115,7 +115,7 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View {
}
func (v *View) ToggleStatusLine() {
if settings["statusline"].(bool) {
if v.Buf.Settings["statusline"].(bool) {
v.height--
} else {
v.height++
@@ -236,7 +236,7 @@ func (v *View) VSplit(buf *Buffer) bool {
func (v *View) Relocate() bool {
ret := false
cy := v.Cursor.Y
scrollmargin := int(settings["scrollmargin"].(float64))
scrollmargin := int(v.Buf.Settings["scrollmargin"].(float64))
if cy < v.Topline+scrollmargin && cy > scrollmargin-1 {
v.Topline = cy - scrollmargin
ret = true
@@ -408,11 +408,11 @@ func (v *View) HandleEvent(event tcell.Event) {
}
case tcell.WheelUp:
// Scroll up
scrollspeed := int(settings["scrollspeed"].(float64))
scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
v.ScrollUp(scrollspeed)
case tcell.WheelDown:
// Scroll down
scrollspeed := int(settings["scrollspeed"].(float64))
scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
v.ScrollDown(scrollspeed)
}
}
@@ -420,7 +420,7 @@ func (v *View) HandleEvent(event tcell.Event) {
if relocate {
v.Relocate()
}
if settings["syntax"].(bool) {
if v.Buf.Settings["syntax"].(bool) {
v.matches = Match(v)
}
}
@@ -486,7 +486,7 @@ func (v *View) DisplayView() {
// We are going to have to offset by that amount
maxLineLength := len(strconv.Itoa(v.Buf.NumLines))
if settings["ruler"] == true {
if v.Buf.Settings["ruler"] == true {
// + 1 for the little space after the line number
v.lineNumOffset = maxLineLength + 1
} else {
@@ -585,7 +585,7 @@ func (v *View) DisplayView() {
}
}
if settings["ruler"] == true {
if v.Buf.Settings["ruler"] == true {
// Write the line number
lineNumStyle := defStyle
if style, ok := colorscheme["line-number"]; ok {
@@ -620,7 +620,7 @@ func (v *View) DisplayView() {
for _, ch := range line {
lineStyle := defStyle
if settings["syntax"].(bool) {
if v.Buf.Settings["syntax"].(bool) {
// Syntax highlighting is enabled
highlightStyle = v.matches[viewLine][colN]
}
@@ -640,7 +640,7 @@ func (v *View) DisplayView() {
// We need to display the background of the linestyle with the correct color if cursorline is enabled
// and this is the current view and there is no selection on this line and the cursor is on this line
if settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
if style, ok := colorscheme["cursor-line"]; ok {
fg, _, _ := style.Decompose()
lineStyle = lineStyle.Background(fg)
@@ -666,19 +666,19 @@ func (v *View) DisplayView() {
lineIndentStyle = style
}
}
if settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
if style, ok := colorscheme["cursor-line"]; ok {
fg, _, _ := style.Decompose()
lineIndentStyle = lineIndentStyle.Background(fg)
}
}
// Here we get the indent char
indentChar := []rune(settings["indentchar"].(string))
indentChar := []rune(v.Buf.Settings["indentchar"].(string))
if screenX-v.x-v.leftCol >= v.lineNumOffset {
v.drawCell(screenX-v.leftCol, screenY, indentChar[0], nil, lineIndentStyle)
}
// Now the tab has to be displayed as a bunch of spaces
tabSize := int(settings["tabsize"].(float64))
tabSize := int(v.Buf.Settings["tabsize"].(float64))
for i := 0; i < tabSize-1; i++ {
screenX++
if screenX-v.x-v.leftCol >= v.lineNumOffset {
@@ -725,7 +725,7 @@ func (v *View) DisplayView() {
for i := 0; i < v.width; i++ {
lineStyle := defStyle
if settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].curView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
if style, ok := colorscheme["cursor-line"]; ok {
fg, _, _ := style.Decompose()
lineStyle = lineStyle.Background(fg)
@@ -755,7 +755,7 @@ func (v *View) Display() {
v.DisplayCursor()
}
_, screenH := screen.Size()
if settings["statusline"].(bool) {
if v.Buf.Settings["statusline"].(bool) {
v.sline.Display()
} else if (v.y + v.height) != screenH-1 {
for x := 0; x < v.width; x++ {