diff --git a/cmd/micro/bindings.go b/cmd/micro/bindings.go index dd028d34..59584e07 100644 --- a/cmd/micro/bindings.go +++ b/cmd/micro/bindings.go @@ -2,10 +2,8 @@ package main import ( "encoding/json" - "errors" "io/ioutil" "os" - "os/exec" "strconv" "strings" "time" @@ -296,242 +294,242 @@ func DefaultBindings() map[string]string { // CursorUp moves the cursor up func (v *View) CursorUp() bool { - if v.cursor.HasSelection() { - v.cursor.SetLoc(v.cursor.curSelection[0]) - v.cursor.ResetSelection() + if v.Cursor.HasSelection() { + v.Cursor.SetLoc(v.Cursor.curSelection[0]) + v.Cursor.ResetSelection() } - v.cursor.Up() + v.Cursor.Up() return true } // CursorDown moves the cursor down func (v *View) CursorDown() bool { - if v.cursor.HasSelection() { - v.cursor.SetLoc(v.cursor.curSelection[1]) - v.cursor.ResetSelection() + if v.Cursor.HasSelection() { + v.Cursor.SetLoc(v.Cursor.curSelection[1]) + v.Cursor.ResetSelection() } - v.cursor.Down() + v.Cursor.Down() return true } // CursorLeft moves the cursor left func (v *View) CursorLeft() bool { - if v.cursor.HasSelection() { - v.cursor.SetLoc(v.cursor.curSelection[0]) - v.cursor.ResetSelection() + if v.Cursor.HasSelection() { + v.Cursor.SetLoc(v.Cursor.curSelection[0]) + v.Cursor.ResetSelection() } else { - v.cursor.Left() + v.Cursor.Left() } return true } // CursorRight moves the cursor right func (v *View) CursorRight() bool { - if v.cursor.HasSelection() { - v.cursor.SetLoc(v.cursor.curSelection[1] - 1) - v.cursor.ResetSelection() + if v.Cursor.HasSelection() { + v.Cursor.SetLoc(v.Cursor.curSelection[1] - 1) + v.Cursor.ResetSelection() } else { - v.cursor.Right() + v.Cursor.Right() } return true } // WordRight moves the cursor one word to the right func (v *View) WordRight() bool { - v.cursor.WordRight() + v.Cursor.WordRight() return true } // WordLeft moves the cursor one word to the left func (v *View) WordLeft() bool { - v.cursor.WordLeft() + v.Cursor.WordLeft() return true } // SelectUp selects up one line func (v *View) SelectUp() bool { - loc := v.cursor.Loc() - if !v.cursor.HasSelection() { - v.cursor.origSelection[0] = loc + loc := v.Cursor.Loc() + if !v.Cursor.HasSelection() { + v.Cursor.origSelection[0] = loc } - v.cursor.Up() - v.cursor.SelectTo(v.cursor.Loc()) + v.Cursor.Up() + v.Cursor.SelectTo(v.Cursor.Loc()) return true } // SelectDown selects down one line func (v *View) SelectDown() bool { - loc := v.cursor.Loc() - if !v.cursor.HasSelection() { - v.cursor.origSelection[0] = loc + loc := v.Cursor.Loc() + if !v.Cursor.HasSelection() { + v.Cursor.origSelection[0] = loc } - v.cursor.Down() - v.cursor.SelectTo(v.cursor.Loc()) + v.Cursor.Down() + v.Cursor.SelectTo(v.Cursor.Loc()) return true } // SelectLeft selects the character to the left of the cursor func (v *View) SelectLeft() bool { - loc := v.cursor.Loc() - count := v.buf.Len() - 1 + loc := v.Cursor.Loc() + count := v.Buf.Len() - 1 if loc > count { loc = count } - if !v.cursor.HasSelection() { - v.cursor.origSelection[0] = loc + if !v.Cursor.HasSelection() { + v.Cursor.origSelection[0] = loc } - v.cursor.Left() - v.cursor.SelectTo(v.cursor.Loc()) + v.Cursor.Left() + v.Cursor.SelectTo(v.Cursor.Loc()) return true } // SelectRight selects the character to the right of the cursor func (v *View) SelectRight() bool { - loc := v.cursor.Loc() - count := v.buf.Len() - 1 + loc := v.Cursor.Loc() + count := v.Buf.Len() - 1 if loc > count { loc = count } - if !v.cursor.HasSelection() { - v.cursor.origSelection[0] = loc + if !v.Cursor.HasSelection() { + v.Cursor.origSelection[0] = loc } - v.cursor.Right() - v.cursor.SelectTo(v.cursor.Loc()) + v.Cursor.Right() + v.Cursor.SelectTo(v.Cursor.Loc()) return true } // SelectWordRight selects the word to the right of the cursor func (v *View) SelectWordRight() bool { - loc := v.cursor.Loc() - if !v.cursor.HasSelection() { - v.cursor.origSelection[0] = loc + loc := v.Cursor.Loc() + if !v.Cursor.HasSelection() { + v.Cursor.origSelection[0] = loc } - v.cursor.WordRight() - v.cursor.SelectTo(v.cursor.Loc()) + v.Cursor.WordRight() + v.Cursor.SelectTo(v.Cursor.Loc()) return true } // SelectWordLeft selects the word to the left of the cursor func (v *View) SelectWordLeft() bool { - loc := v.cursor.Loc() - if !v.cursor.HasSelection() { - v.cursor.origSelection[0] = loc + loc := v.Cursor.Loc() + if !v.Cursor.HasSelection() { + v.Cursor.origSelection[0] = loc } - v.cursor.WordLeft() - v.cursor.SelectTo(v.cursor.Loc()) + v.Cursor.WordLeft() + v.Cursor.SelectTo(v.Cursor.Loc()) return true } // StartOfLine moves the cursor to the start of the line func (v *View) StartOfLine() bool { - v.cursor.Start() + v.Cursor.Start() return true } // EndOfLine moves the cursor to the end of the line func (v *View) EndOfLine() bool { - v.cursor.End() + v.Cursor.End() return true } // SelectToStartOfLine selects to the start of the current line func (v *View) SelectToStartOfLine() bool { - loc := v.cursor.Loc() - if !v.cursor.HasSelection() { - v.cursor.origSelection[0] = loc + loc := v.Cursor.Loc() + if !v.Cursor.HasSelection() { + v.Cursor.origSelection[0] = loc } - v.cursor.Start() - v.cursor.SelectTo(v.cursor.Loc()) + v.Cursor.Start() + v.Cursor.SelectTo(v.Cursor.Loc()) return true } // SelectToEndOfLine selects to the end of the current line func (v *View) SelectToEndOfLine() bool { - loc := v.cursor.Loc() - if !v.cursor.HasSelection() { - v.cursor.origSelection[0] = loc + loc := v.Cursor.Loc() + if !v.Cursor.HasSelection() { + v.Cursor.origSelection[0] = loc } - v.cursor.End() - v.cursor.SelectTo(v.cursor.Loc()) + v.Cursor.End() + v.Cursor.SelectTo(v.Cursor.Loc()) return true } // CursorStart moves the cursor to the start of the buffer func (v *View) CursorStart() bool { - v.cursor.x = 0 - v.cursor.y = 0 + v.Cursor.x = 0 + v.Cursor.y = 0 return true } // CursorEnd moves the cursor to the end of the buffer func (v *View) CursorEnd() bool { - v.cursor.SetLoc(v.buf.Len()) + v.Cursor.SetLoc(v.Buf.Len()) return true } // SelectToStart selects the text from the cursor to the start of the buffer func (v *View) SelectToStart() bool { - loc := v.cursor.Loc() - if !v.cursor.HasSelection() { - v.cursor.origSelection[0] = loc + loc := v.Cursor.Loc() + if !v.Cursor.HasSelection() { + v.Cursor.origSelection[0] = loc } v.CursorStart() - v.cursor.SelectTo(0) + v.Cursor.SelectTo(0) return true } // SelectToEnd selects the text from the cursor to the end of the buffer func (v *View) SelectToEnd() bool { - loc := v.cursor.Loc() - if !v.cursor.HasSelection() { - v.cursor.origSelection[0] = loc + loc := v.Cursor.Loc() + if !v.Cursor.HasSelection() { + v.Cursor.origSelection[0] = loc } v.CursorEnd() - v.cursor.SelectTo(v.buf.Len()) + v.Cursor.SelectTo(v.Buf.Len()) return true } // InsertSpace inserts a space func (v *View) InsertSpace() bool { // Insert a space - if v.cursor.HasSelection() { - v.cursor.DeleteSelection() - v.cursor.ResetSelection() + if v.Cursor.HasSelection() { + v.Cursor.DeleteSelection() + v.Cursor.ResetSelection() } - v.eh.Insert(v.cursor.Loc(), " ") - v.cursor.Right() + v.eh.Insert(v.Cursor.Loc(), " ") + v.Cursor.Right() return true } // InsertEnter inserts a newline plus possible some whitespace if autoindent is on func (v *View) InsertEnter() bool { // Insert a newline - if v.cursor.HasSelection() { - v.cursor.DeleteSelection() - v.cursor.ResetSelection() + if v.Cursor.HasSelection() { + v.Cursor.DeleteSelection() + v.Cursor.ResetSelection() } - v.eh.Insert(v.cursor.Loc(), "\n") - ws := GetLeadingWhitespace(v.buf.lines[v.cursor.y]) - v.cursor.Right() + v.eh.Insert(v.Cursor.Loc(), "\n") + ws := GetLeadingWhitespace(v.Buf.Lines[v.Cursor.y]) + v.Cursor.Right() if settings["autoindent"].(bool) { - v.eh.Insert(v.cursor.Loc(), ws) + v.eh.Insert(v.Cursor.Loc(), ws) for i := 0; i < len(ws); i++ { - v.cursor.Right() + v.Cursor.Right() } } - v.cursor.lastVisualX = v.cursor.GetVisualX() + v.Cursor.lastVisualX = v.Cursor.GetVisualX() return true } // Backspace deletes the previous character func (v *View) Backspace() bool { // Delete a character - if v.cursor.HasSelection() { - v.cursor.DeleteSelection() - v.cursor.ResetSelection() - } else if v.cursor.Loc() > 0 { + if v.Cursor.HasSelection() { + v.Cursor.DeleteSelection() + v.Cursor.ResetSelection() + } else if v.Cursor.Loc() > 0 { // We have to do something a bit hacky here because we want to // delete the line by first moving left and then deleting backwards // but the undo redo would place the cursor in the wrong place @@ -541,36 +539,36 @@ func (v *View) Backspace() bool { // If the user is using spaces instead of tabs and they are deleting // whitespace at the start of the line, we should delete as if its a // tab (tabSize number of spaces) - lineStart := v.buf.lines[v.cursor.y][:v.cursor.x] + lineStart := v.Buf.Lines[v.Cursor.y][:v.Cursor.x] tabSize := int(settings["tabsize"].(float64)) if settings["tabsToSpaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 { - loc := v.cursor.Loc() - v.cursor.SetLoc(loc - tabSize) - cx, cy := v.cursor.x, v.cursor.y - v.cursor.SetLoc(loc) + loc := v.Cursor.Loc() + v.Cursor.SetLoc(loc - tabSize) + cx, cy := v.Cursor.x, v.Cursor.y + v.Cursor.SetLoc(loc) v.eh.Remove(loc-tabSize, loc) - v.cursor.x, v.cursor.y = cx, cy + v.Cursor.x, v.Cursor.y = cx, cy } else { - v.cursor.Left() - cx, cy := v.cursor.x, v.cursor.y - v.cursor.Right() - loc := v.cursor.Loc() + v.Cursor.Left() + cx, cy := v.Cursor.x, v.Cursor.y + v.Cursor.Right() + loc := v.Cursor.Loc() v.eh.Remove(loc-1, loc) - v.cursor.x, v.cursor.y = cx, cy + v.Cursor.x, v.Cursor.y = cx, cy } } - v.cursor.lastVisualX = v.cursor.GetVisualX() + v.Cursor.lastVisualX = v.Cursor.GetVisualX() return true } // Delete deletes the next character func (v *View) Delete() bool { - if v.cursor.HasSelection() { - v.cursor.DeleteSelection() - v.cursor.ResetSelection() + if v.Cursor.HasSelection() { + v.Cursor.DeleteSelection() + v.Cursor.ResetSelection() } else { - loc := v.cursor.Loc() - if loc < v.buf.Len() { + loc := v.Cursor.Loc() + if loc < v.Buf.Len() { v.eh.Remove(loc, loc+1) } } @@ -580,19 +578,19 @@ func (v *View) Delete() bool { // InsertTab inserts a tab or spaces func (v *View) InsertTab() bool { // Insert a tab - if v.cursor.HasSelection() { - v.cursor.DeleteSelection() - v.cursor.ResetSelection() + if v.Cursor.HasSelection() { + v.Cursor.DeleteSelection() + v.Cursor.ResetSelection() } if settings["tabsToSpaces"].(bool) { tabSize := int(settings["tabsize"].(float64)) - v.eh.Insert(v.cursor.Loc(), Spaces(tabSize)) + v.eh.Insert(v.Cursor.Loc(), Spaces(tabSize)) for i := 0; i < tabSize; i++ { - v.cursor.Right() + v.Cursor.Right() } } else { - v.eh.Insert(v.cursor.Loc(), "\t") - v.cursor.Right() + v.eh.Insert(v.Cursor.Loc(), "\t") + v.Cursor.Right() } return true } @@ -600,61 +598,30 @@ func (v *View) InsertTab() bool { // Save the buffer to disk func (v *View) Save() bool { // If this is an empty buffer, ask for a filename - if v.buf.path == "" { + if v.Buf.Path == "" { filename, canceled := messenger.Prompt("Filename: ") if !canceled { - v.buf.path = filename - v.buf.name = filename + v.Buf.Path = filename + v.Buf.Name = filename } else { return true } } - err := v.buf.Save() + err := v.Buf.Save() if err != nil { messenger.Error(err.Error()) } else { - messenger.Message("Saved " + v.buf.path) - switch v.buf.filetype { - case "Go": - v.GoSave() - } + messenger.Message("Saved " + v.Buf.Path) } return true } -// GoSave saves the current file (must be a go file) and runs goimports or gofmt -// depending on the user's configuration -func (v *View) GoSave() { - if settings["goimports"] == true { - messenger.Message("Running goimports...") - err := goimports(v.buf.path) - if err != nil { - messenger.Error(err) - } else { - messenger.Message("Saved " + v.buf.path) - } - v.reOpen() - } else if settings["gofmt"] == true { - messenger.Message("Running gofmt...") - err := gofmt(v.buf.path) - if err != nil { - messenger.Error(err) - } else { - messenger.Message("Saved " + v.buf.path) - } - v.reOpen() - return - } - - return -} - // Find opens a prompt and searches forward for the input func (v *View) Find() bool { - if v.cursor.HasSelection() { - searchStart = v.cursor.curSelection[1] + if v.Cursor.HasSelection() { + searchStart = v.Cursor.curSelection[1] } else { - searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf) + searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf) } BeginSearch() return true @@ -662,10 +629,10 @@ func (v *View) Find() bool { // FindNext searches forwards for the last used search term func (v *View) FindNext() bool { - if v.cursor.HasSelection() { - searchStart = v.cursor.curSelection[1] + if v.Cursor.HasSelection() { + searchStart = v.Cursor.curSelection[1] } else { - searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf) + searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf) } messenger.Message("Finding: " + lastSearch) Search(lastSearch, v, true) @@ -674,10 +641,10 @@ func (v *View) FindNext() bool { // FindPrevious searches backwards for the last used search term func (v *View) FindPrevious() bool { - if v.cursor.HasSelection() { - searchStart = v.cursor.curSelection[0] + if v.Cursor.HasSelection() { + searchStart = v.Cursor.curSelection[0] } else { - searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf) + searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf) } messenger.Message("Finding: " + lastSearch) Search(lastSearch, v, false) @@ -698,8 +665,8 @@ func (v *View) Redo() bool { // Copy the selection to the system clipboard func (v *View) Copy() bool { - if v.cursor.HasSelection() { - clipboard.WriteAll(v.cursor.GetSelection()) + if v.Cursor.HasSelection() { + clipboard.WriteAll(v.Cursor.GetSelection()) v.freshClip = true } return true @@ -707,14 +674,14 @@ func (v *View) Copy() bool { // CutLine cuts the current line to the clipboard func (v *View) CutLine() bool { - v.cursor.SelectLine() + v.Cursor.SelectLine() if v.freshClip == true { - if v.cursor.HasSelection() { + if v.Cursor.HasSelection() { if clip, err := clipboard.ReadAll(); err != nil { messenger.Error(err) } else { - clipboard.WriteAll(clip + v.cursor.GetSelection()) + clipboard.WriteAll(clip + v.Cursor.GetSelection()) } } } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false { @@ -722,17 +689,17 @@ func (v *View) CutLine() bool { } v.freshClip = true v.lastCutTime = time.Now() - v.cursor.DeleteSelection() - v.cursor.ResetSelection() + v.Cursor.DeleteSelection() + v.Cursor.ResetSelection() return true } // Cut the selection to the system clipboard func (v *View) Cut() bool { - if v.cursor.HasSelection() { - clipboard.WriteAll(v.cursor.GetSelection()) - v.cursor.DeleteSelection() - v.cursor.ResetSelection() + if v.Cursor.HasSelection() { + clipboard.WriteAll(v.Cursor.GetSelection()) + v.Cursor.DeleteSelection() + v.Cursor.ResetSelection() v.freshClip = true } return true @@ -741,24 +708,24 @@ func (v *View) Cut() bool { // Paste whatever is in the system clipboard into the buffer // Delete and paste if the user has a selection func (v *View) Paste() bool { - if v.cursor.HasSelection() { - v.cursor.DeleteSelection() - v.cursor.ResetSelection() + if v.Cursor.HasSelection() { + v.Cursor.DeleteSelection() + v.Cursor.ResetSelection() } clip, _ := clipboard.ReadAll() - v.eh.Insert(v.cursor.Loc(), clip) - v.cursor.SetLoc(v.cursor.Loc() + Count(clip)) + v.eh.Insert(v.Cursor.Loc(), clip) + v.Cursor.SetLoc(v.Cursor.Loc() + Count(clip)) v.freshClip = false return true } // SelectAll selects the entire buffer func (v *View) SelectAll() bool { - v.cursor.curSelection[1] = 0 - v.cursor.curSelection[0] = v.buf.Len() + v.Cursor.curSelection[1] = 0 + v.Cursor.curSelection[0] = v.Buf.Len() // Put the cursor at the beginning - v.cursor.x = 0 - v.cursor.y = 0 + v.Cursor.x = 0 + v.Cursor.y = 0 return true } @@ -785,57 +752,57 @@ func (v *View) OpenFile() bool { // Start moves the viewport to the start of the buffer func (v *View) Start() bool { - v.topline = 0 + v.Topline = 0 return false } // End moves the viewport to the end of the buffer func (v *View) End() bool { - if v.height > v.buf.numLines { - v.topline = 0 + if v.height > v.Buf.NumLines { + v.Topline = 0 } else { - v.topline = v.buf.numLines - v.height + v.Topline = v.Buf.NumLines - v.height } return false } // PageUp scrolls the view up a page func (v *View) PageUp() bool { - if v.topline > v.height { + if v.Topline > v.height { v.ScrollUp(v.height) } else { - v.topline = 0 + v.Topline = 0 } return false } // PageDown scrolls the view down a page func (v *View) PageDown() bool { - if v.buf.numLines-(v.topline+v.height) > v.height { + if v.Buf.NumLines-(v.Topline+v.height) > v.height { v.ScrollDown(v.height) - } else if v.buf.numLines >= v.height { - v.topline = v.buf.numLines - v.height + } else if v.Buf.NumLines >= v.height { + v.Topline = v.Buf.NumLines - v.height } return false } // HalfPageUp scrolls the view up half a page func (v *View) HalfPageUp() bool { - if v.topline > v.height/2 { + if v.Topline > v.height/2 { v.ScrollUp(v.height / 2) } else { - v.topline = 0 + v.Topline = 0 } return false } // HalfPageDown scrolls the view down half a page func (v *View) HalfPageDown() bool { - if v.buf.numLines-(v.topline+v.height) > v.height/2 { + if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 { v.ScrollDown(v.height / 2) } else { - if v.buf.numLines >= v.height { - v.topline = v.buf.numLines - v.height + if v.Buf.NumLines >= v.height { + v.Topline = v.Buf.NumLines - v.height } } return false @@ -865,12 +832,12 @@ func (v *View) JumpLine() bool { return false } // Move cursor and view if possible. - if lineint < v.buf.numLines { - v.cursor.x = 0 - v.cursor.y = lineint + if lineint < v.Buf.NumLines { + v.Cursor.x = 0 + v.Cursor.y = lineint return true } - messenger.Error("Only ", v.buf.numLines, " lines to jump") + messenger.Error("Only ", v.Buf.NumLines, " lines to jump") return false } @@ -878,25 +845,3 @@ func (v *View) JumpLine() bool { func None() bool { return false } - -// gofmt runs gofmt on a file -func gofmt(file string) error { - cmd := exec.Command("gofmt", "-w", file) - cmd.Start() - err := cmd.Wait() - if err != nil { - return errors.New("Check syntax ") //TODO: highlight or display locations - } - return nil -} - -// goimports runs goimports on a file -func goimports(file string) error { - cmd := exec.Command("goimports", "-w", file) - cmd.Start() - err := cmd.Wait() - if err != nil { - return errors.New("Check syntax ") //TODO: highlight or display locations - } - return nil -} diff --git a/cmd/micro/buffer.go b/cmd/micro/buffer.go index 34ef3d1a..0a044341 100644 --- a/cmd/micro/buffer.go +++ b/cmd/micro/buffer.go @@ -15,9 +15,9 @@ type Buffer struct { r *rope.Rope // Path to the file on disk - path string + Path string // Name of the buffer on the status line - name string + Name string // This is the text stored every time the buffer is saved to check if the buffer is modified savedText [16]byte @@ -27,13 +27,13 @@ type Buffer struct { // Provide efficient and easy access to text and lines so the rope String does not // need to be constantly recalculated // These variables are updated in the update() function - lines []string - numLines int + Lines []string + NumLines int // Syntax highlighting rules rules []SyntaxRule // The buffer's filetype - filetype string + Filetype string } // NewBuffer creates a new buffer from `txt` with path and name `path` @@ -44,8 +44,8 @@ func NewBuffer(txt, path string) *Buffer { } else { b.r = rope.New(txt) } - b.path = path - b.name = path + b.Path = path + b.Name = path b.savedText = md5.Sum([]byte(txt)) b.Update() @@ -57,7 +57,7 @@ func NewBuffer(txt, path string) *Buffer { // UpdateRules updates the syntax rules and filetype for this buffer // This is called when the colorscheme changes func (b *Buffer) UpdateRules() { - b.rules, b.filetype = GetRules(b) + b.rules, b.Filetype = GetRules(b) } func (b *Buffer) String() string { @@ -70,13 +70,13 @@ func (b *Buffer) String() string { // Update fetches the string from the rope and updates the `text` and `lines` in the buffer func (b *Buffer) Update() { - b.lines = strings.Split(b.String(), "\n") - b.numLines = len(b.lines) + b.Lines = strings.Split(b.String(), "\n") + b.NumLines = len(b.Lines) } // Save saves the buffer to its default path func (b *Buffer) Save() error { - return b.SaveAs(b.path) + return b.SaveAs(b.Path) } // SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist diff --git a/cmd/micro/command.go b/cmd/micro/command.go index 67fcfeaa..1e41a7b2 100644 --- a/cmd/micro/command.go +++ b/cmd/micro/command.go @@ -141,7 +141,7 @@ func HandleCommand(input string, view *View) { found := false for { - match := regex.FindStringIndex(view.buf.String()) + match := regex.FindStringIndex(view.Buf.String()) if match == nil { break } @@ -153,23 +153,23 @@ func HandleCommand(input string, view *View) { Redraw(view) choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)") if canceled { - if view.cursor.HasSelection() { - view.cursor.SetLoc(view.cursor.curSelection[0]) - view.cursor.ResetSelection() + if view.Cursor.HasSelection() { + view.Cursor.SetLoc(view.Cursor.curSelection[0]) + view.Cursor.ResetSelection() } messenger.Reset() return } if choice { - view.cursor.DeleteSelection() + view.Cursor.DeleteSelection() view.eh.Insert(match[0], replace) - view.cursor.ResetSelection() + view.Cursor.ResetSelection() messenger.Reset() } else { - if view.cursor.HasSelection() { - searchStart = view.cursor.curSelection[1] + if view.Cursor.HasSelection() { + searchStart = view.Cursor.curSelection[1] } else { - searchStart = ToCharPos(view.cursor.x, view.cursor.y, view.buf) + searchStart = ToCharPos(view.Cursor.x, view.Cursor.y, view.Buf) } continue } diff --git a/cmd/micro/cursor.go b/cmd/micro/cursor.go index 88287ac4..e48abb7f 100644 --- a/cmd/micro/cursor.go +++ b/cmd/micro/cursor.go @@ -14,11 +14,11 @@ func FromCharPosStart(startLoc, startX, startY, loc int, buf *Buffer) (int, int) charNum := startLoc x, y := startX, startY - lineLen := Count(buf.lines[y]) + 1 + lineLen := Count(buf.Lines[y]) + 1 for charNum+lineLen <= loc { charNum += lineLen y++ - lineLen = Count(buf.lines[y]) + 1 + lineLen = Count(buf.Lines[y]) + 1 } x = loc - charNum @@ -30,7 +30,7 @@ func ToCharPos(x, y int, buf *Buffer) int { loc := 0 for i := 0; i < y; i++ { // + 1 for the newline - loc += Count(buf.lines[i]) + 1 + loc += Count(buf.Lines[i]) + 1 } loc += x return loc @@ -64,7 +64,7 @@ type Cursor struct { // and not x, y location // It's just a simple wrapper of FromCharPos func (c *Cursor) SetLoc(loc int) { - c.x, c.y = FromCharPos(loc, c.v.buf) + c.x, c.y = FromCharPos(loc, c.v.Buf) c.lastVisualX = c.GetVisualX() } @@ -72,7 +72,7 @@ func (c *Cursor) SetLoc(loc int) { // of x, y location // It's just a simple wrapper of ToCharPos func (c *Cursor) Loc() int { - return ToCharPos(c.x, c.y, c.v.buf) + return ToCharPos(c.x, c.y, c.v.Buf) } // ResetSelection resets the user's selection @@ -102,9 +102,9 @@ func (c *Cursor) DeleteSelection() { // GetSelection returns the cursor's selection func (c *Cursor) GetSelection() string { if c.curSelection[0] > c.curSelection[1] { - return c.v.buf.r.Report(c.curSelection[1]+1, c.curSelection[0]-c.curSelection[1]) + return c.v.Buf.r.Report(c.curSelection[1]+1, c.curSelection[0]-c.curSelection[1]) } - return c.v.buf.r.Report(c.curSelection[0]+1, c.curSelection[1]-c.curSelection[0]) + return c.v.Buf.r.Report(c.curSelection[0]+1, c.curSelection[1]-c.curSelection[0]) } // SelectLine selects the current line @@ -112,7 +112,7 @@ func (c *Cursor) SelectLine() { c.Start() c.curSelection[0] = c.Loc() c.End() - if c.v.buf.numLines-1 > c.y { + if c.v.Buf.NumLines-1 > c.y { c.curSelection[1] = c.Loc() + 1 } else { c.curSelection[1] = c.Loc() @@ -143,7 +143,7 @@ func (c *Cursor) AddLineToSelection() { // SelectWord selects the word the cursor is currently on func (c *Cursor) SelectWord() { - if len(c.v.buf.lines[c.y]) == 0 { + if len(c.v.Buf.Lines[c.y]) == 0 { return } @@ -161,14 +161,14 @@ func (c *Cursor) SelectWord() { backward-- } - c.curSelection[0] = ToCharPos(backward, c.y, c.v.buf) + c.curSelection[0] = ToCharPos(backward, c.y, c.v.Buf) c.origSelection[0] = c.curSelection[0] - for forward < Count(c.v.buf.lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) { + for forward < Count(c.v.Buf.Lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) { forward++ } - c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + 1 + c.curSelection[1] = ToCharPos(forward, c.y, c.v.Buf) + 1 c.origSelection[1] = c.curSelection[1] c.SetLoc(c.curSelection[1]) } @@ -189,18 +189,18 @@ func (c *Cursor) AddWordToSelection() { backward-- } - c.curSelection[0] = ToCharPos(backward, c.y, c.v.buf) + c.curSelection[0] = ToCharPos(backward, c.y, c.v.Buf) c.curSelection[1] = c.origSelection[1] } if loc > c.origSelection[1] { forward := c.x - for forward < Count(c.v.buf.lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) { + for forward < Count(c.v.Buf.Lines[c.y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) { forward++ } - c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + 1 + c.curSelection[1] = ToCharPos(forward, c.y, c.v.Buf) + 1 c.curSelection[0] = c.origSelection[0] } @@ -222,13 +222,13 @@ func (c *Cursor) SelectTo(loc int) { func (c *Cursor) WordRight() { c.Right() for IsWhitespace(c.RuneUnder(c.x)) { - if c.x == Count(c.v.buf.lines[c.y]) { + if c.x == Count(c.v.Buf.Lines[c.y]) { return } c.Right() } for !IsWhitespace(c.RuneUnder(c.x)) { - if c.x == Count(c.v.buf.lines[c.y]) { + if c.x == Count(c.v.Buf.Lines[c.y]) { return } c.Right() @@ -255,7 +255,7 @@ func (c *Cursor) WordLeft() { // RuneUnder returns the rune under the given x position func (c *Cursor) RuneUnder(x int) rune { - line := []rune(c.v.buf.lines[c.y]) + line := []rune(c.v.Buf.Lines[c.y]) if len(line) == 0 { return '\n' } @@ -272,7 +272,7 @@ func (c *Cursor) Up() { if c.y > 0 { c.y-- - runes := []rune(c.v.buf.lines[c.y]) + runes := []rune(c.v.Buf.Lines[c.y]) c.x = c.GetCharPosInLine(c.y, c.lastVisualX) if c.x > len(runes) { c.x = len(runes) @@ -282,10 +282,10 @@ func (c *Cursor) Up() { // Down moves the cursor down one line (if possible) func (c *Cursor) Down() { - if c.y < c.v.buf.numLines-1 { + if c.y < c.v.Buf.NumLines-1 { c.y++ - runes := []rune(c.v.buf.lines[c.y]) + runes := []rune(c.v.Buf.Lines[c.y]) c.x = c.GetCharPosInLine(c.y, c.lastVisualX) if c.x > len(runes) { c.x = len(runes) @@ -309,10 +309,10 @@ func (c *Cursor) Left() { // Right moves the cursor right one cell (if possible) or to the next line if it is at the end func (c *Cursor) Right() { - if c.Loc() == c.v.buf.Len() { + if c.Loc() == c.v.Buf.Len() { return } - if c.x < Count(c.v.buf.lines[c.y]) { + if c.x < Count(c.v.Buf.Lines[c.y]) { c.x++ } else { c.Down() @@ -323,7 +323,7 @@ func (c *Cursor) Right() { // End moves the cursor to the end of the line it is on func (c *Cursor) End() { - c.x = Count(c.v.buf.lines[c.y]) + c.x = Count(c.v.Buf.Lines[c.y]) c.lastVisualX = c.GetVisualX() } @@ -338,7 +338,7 @@ func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int { // Get the tab size tabSize := int(settings["tabsize"].(float64)) // This is the visual line -- every \t replaced with the correct number of spaces - visualLine := strings.Replace(c.v.buf.lines[lineNum], "\t", "\t"+Spaces(tabSize-1), -1) + visualLine := strings.Replace(c.v.Buf.Lines[lineNum], "\t", "\t"+Spaces(tabSize-1), -1) if visualPos > Count(visualLine) { visualPos = Count(visualLine) } @@ -351,7 +351,7 @@ 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.v.buf.lines[c.y]) + runes := []rune(c.v.Buf.Lines[c.y]) tabSize := int(settings["tabsize"].(float64)) return c.x + NumOccurences(string(runes[:c.x]), '\t')*(tabSize-1) } @@ -361,23 +361,23 @@ func (c *Cursor) GetVisualX() int { func (c *Cursor) Relocate() { if c.y < 0 { c.y = 0 - } else if c.y >= c.v.buf.numLines { - c.y = c.v.buf.numLines - 1 + } else if c.y >= c.v.Buf.NumLines { + c.y = c.v.Buf.NumLines - 1 } if c.x < 0 { c.x = 0 - } else if c.x > Count(c.v.buf.lines[c.y]) { - c.x = Count(c.v.buf.lines[c.y]) + } else if c.x > Count(c.v.Buf.Lines[c.y]) { + c.x = Count(c.v.Buf.Lines[c.y]) } } // Display draws the cursor to the screen at the correct position func (c *Cursor) Display() { // Don't draw the cursor if it is out of the viewport or if it has a selection - if (c.y-c.v.topline < 0 || c.y-c.v.topline > c.v.height-1) || c.HasSelection() { + if (c.y-c.v.Topline < 0 || c.y-c.v.Topline > c.v.height-1) || c.HasSelection() { screen.HideCursor() } else { - screen.ShowCursor(c.GetVisualX()+c.v.lineNumOffset-c.v.leftCol, c.y-c.v.topline) + screen.ShowCursor(c.GetVisualX()+c.v.lineNumOffset-c.v.leftCol, c.y-c.v.Topline) } } diff --git a/cmd/micro/eventhandler.go b/cmd/micro/eventhandler.go index a19cf7e7..4904a84e 100644 --- a/cmd/micro/eventhandler.go +++ b/cmd/micro/eventhandler.go @@ -58,7 +58,7 @@ func NewEventHandler(v *View) *EventHandler { // Insert creates an insert text event and executes it func (eh *EventHandler) Insert(start int, text string) { e := &TextEvent{ - c: eh.v.cursor, + c: eh.v.Cursor, eventType: TextEventInsert, text: text, start: start, @@ -71,7 +71,7 @@ func (eh *EventHandler) Insert(start int, text string) { // Remove creates a remove text event and executes it func (eh *EventHandler) Remove(start, end int) { e := &TextEvent{ - c: eh.v.cursor, + c: eh.v.Cursor, eventType: TextEventRemove, start: start, end: end, @@ -92,7 +92,7 @@ func (eh *EventHandler) Execute(t *TextEvent) { eh.redo = new(Stack) } eh.undo.Push(t) - ExecuteTextEvent(t, eh.v.buf) + ExecuteTextEvent(t, eh.v.Buf) } // Undo the first event in the undo stack @@ -135,12 +135,12 @@ func (eh *EventHandler) UndoOneEvent() { te := t.(*TextEvent) // Undo it // Modifies the text event - UndoTextEvent(te, eh.v.buf) + UndoTextEvent(te, eh.v.Buf) // Set the cursor in the right place teCursor := te.c - te.c = eh.v.cursor - eh.v.cursor = teCursor + te.c = eh.v.Cursor + eh.v.Cursor = teCursor // Push it to the redo stack eh.redo.Push(te) @@ -183,11 +183,11 @@ func (eh *EventHandler) RedoOneEvent() { te := t.(*TextEvent) // Modifies the text event - UndoTextEvent(te, eh.v.buf) + UndoTextEvent(te, eh.v.Buf) teCursor := te.c - te.c = eh.v.cursor - eh.v.cursor = teCursor + te.c = eh.v.Cursor + eh.v.Cursor = teCursor eh.undo.Push(te) } diff --git a/cmd/micro/highlighter.go b/cmd/micro/highlighter.go index bcf0aabd..8ec16de3 100644 --- a/cmd/micro/highlighter.go +++ b/cmd/micro/highlighter.go @@ -363,10 +363,10 @@ func LoadRulesFromFile(text, filename string) []SyntaxRule { // and returns them. It also returns the filetype of the file func GetRules(buf *Buffer) ([]SyntaxRule, string) { for r := range syntaxFiles { - if r[0] != nil && r[0].MatchString(buf.path) { + if r[0] != nil && r[0].MatchString(buf.Path) { // Check if the syntax statement matches the extension return LoadRulesFromFile(syntaxFiles[r].text, syntaxFiles[r].filename), syntaxFiles[r].filetype - } else if r[1] != nil && r[1].MatchString(buf.lines[0]) { + } else if r[1] != nil && r[1].MatchString(buf.Lines[0]) { // Check if the header statement matches the first line return LoadRulesFromFile(syntaxFiles[r].text, syntaxFiles[r].filename), syntaxFiles[r].filetype } @@ -381,16 +381,16 @@ type SyntaxMatches [][]tcell.Style // Match takes a buffer and returns the syntax matches: a 2d array specifying how it should be syntax highlighted // We match the rules from up `synLinesUp` lines and down `synLinesDown` lines func Match(v *View) SyntaxMatches { - buf := v.buf - rules := v.buf.rules + buf := v.Buf + rules := v.Buf.rules - viewStart := v.topline - viewEnd := v.topline + v.height - if viewEnd > buf.numLines { - viewEnd = buf.numLines + viewStart := v.Topline + viewEnd := v.Topline + v.height + if viewEnd > buf.NumLines { + viewEnd = buf.NumLines } - lines := buf.lines[viewStart:viewEnd] + lines := buf.Lines[viewStart:viewEnd] matches := make(SyntaxMatches, len(lines)) for i, line := range lines { @@ -398,19 +398,19 @@ func Match(v *View) SyntaxMatches { } // We don't actually check the entire buffer, just from synLinesUp to synLinesDown - totalStart := v.topline - synLinesUp - totalEnd := v.topline + v.height + synLinesDown + totalStart := v.Topline - synLinesUp + totalEnd := v.Topline + v.height + synLinesDown if totalStart < 0 { totalStart = 0 } - if totalEnd > buf.numLines { - totalEnd = buf.numLines + if totalEnd > buf.NumLines { + totalEnd = buf.NumLines } - str := strings.Join(buf.lines[totalStart:totalEnd], "\n") - startNum := ToCharPos(0, totalStart, v.buf) + str := strings.Join(buf.Lines[totalStart:totalEnd], "\n") + startNum := ToCharPos(0, totalStart, v.Buf) - toplineNum := ToCharPos(0, v.topline, v.buf) + toplineNum := ToCharPos(0, v.Topline, v.Buf) for _, rule := range rules { if rule.startend { @@ -422,7 +422,7 @@ func Match(v *View) SyntaxMatches { if i < toplineNum { continue } - colNum, lineNum := FromCharPosStart(toplineNum, 0, v.topline, i, buf) + colNum, lineNum := FromCharPosStart(toplineNum, 0, v.Topline, i, buf) if lineNum == -1 || colNum == -1 { continue } diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index a05761fb..718d220d 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -5,10 +5,13 @@ import ( "fmt" "io/ioutil" "os" + "runtime" "github.com/go-errors/errors" + "github.com/layeh/gopher-luar" "github.com/mattn/go-isatty" "github.com/mitchellh/go-homedir" + "github.com/yuin/gopher-lua" "github.com/zyedidia/tcell" "github.com/zyedidia/tcell/encoding" ) @@ -41,6 +44,9 @@ var ( // Is the help screen open helpOpen = false + + // L is the lua state + L *lua.LState ) // LoadInput loads the file input for the editor @@ -175,6 +181,9 @@ func main() { os.Exit(1) } + L = lua.NewState() + defer L.Close() + encoding.Register() tcell.SetEncodingFallback(tcell.EncodingFallbackASCII) @@ -207,6 +216,14 @@ func main() { messenger = new(Messenger) view := NewView(buf) + L.SetGlobal("OS", luar.New(L, runtime.GOOS)) + L.SetGlobal("view", luar.New(L, view)) + L.SetGlobal("messenger", luar.New(L, messenger)) + L.SetGlobal("GetOption", luar.New(L, GetOption)) + L.SetGlobal("AddOption", luar.New(L, AddOption)) + + LoadPlugins() + for { // Display everything Redraw(view) @@ -245,7 +262,7 @@ func main() { case tcell.KeyCtrlG: if !helpOpen { helpBuffer := NewBuffer(helpTxt, "help.md") - helpBuffer.name = "Help" + helpBuffer.Name = "Help" helpOpen = true view.OpenBuffer(helpBuffer) } else { diff --git a/cmd/micro/plugin.go b/cmd/micro/plugin.go new file mode 100644 index 00000000..4138e3a0 --- /dev/null +++ b/cmd/micro/plugin.go @@ -0,0 +1,63 @@ +package main + +import ( + "github.com/yuin/gopher-lua" + "io/ioutil" +) + +var loadedPlugins []string + +var preInstalledPlugins = []string{ + "go", + "linter", +} + +// Call calls the lua function 'function' +// If it does not exist nothing happens, if there is an error, +// the error is returned +func Call(function string) error { + luaFunc := L.GetGlobal(function) + if luaFunc.String() == "nil" { + return nil + } + err := L.CallByParam(lua.P{ + Fn: luaFunc, + NRet: 0, + Protect: true, + }) + return err +} + +// LoadPlugins loads the pre-installed plugins and the plugins located in ~/.config/micro/plugins +func LoadPlugins() { + files, _ := ioutil.ReadDir(configDir + "/plugins") + for _, plugin := range files { + if plugin.IsDir() { + pluginName := plugin.Name() + files, _ := ioutil.ReadDir(configDir + "/plugins/" + pluginName) + for _, f := range files { + if f.Name() == pluginName+".lua" { + if err := L.DoFile(configDir + "/plugins/" + pluginName + "/" + f.Name()); err != nil { + TermMessage(err) + continue + } + loadedPlugins = append(loadedPlugins, pluginName) + } + } + } + } + + for _, pluginName := range preInstalledPlugins { + plugin := "runtime/plugins/" + pluginName + "/" + pluginName + ".lua" + data, err := Asset(plugin) + if err != nil { + TermMessage("Error loading pre-installed plugin: " + pluginName) + continue + } + if err := L.DoString(string(data)); err != nil { + TermMessage(err) + continue + } + loadedPlugins = append(loadedPlugins, pluginName) + } +} diff --git a/cmd/micro/runtime.go b/cmd/micro/runtime.go index bb2cc188..4fe072ce 100644 --- a/cmd/micro/runtime.go +++ b/cmd/micro/runtime.go @@ -5,6 +5,8 @@ // runtime/colorschemes/solarized-tc.micro // runtime/colorschemes/solarized.micro // runtime/help/help.md +// runtime/plugins/go/go.lua +// runtime/plugins/linter/linter.lua // runtime/syntax/Dockerfile.micro // runtime/syntax/LICENSE // runtime/syntax/README.md @@ -237,7 +239,7 @@ func runtimeColorschemesSolarizedMicro() (*asset, error) { return a, nil } -var _runtimeHelpHelpMd = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x94\x58\x59\x73\xdb\x38\x12\x7e\x0e\x7f\x05\x4a\x99\xaa\x49\x66\x15\xb9\x76\x6b\x9f\xf4\xe6\x71\x9c\xa3\x26\x89\xbd\x8e\xbd\x3b\x99\x97\x10\x22\x41\x09\x63\x92\x60\x00\xd0\xb6\xb2\xc7\x6f\xdf\xaf\x1b\x00\x0f\xc9\xde\xc3\x2f\x16\xbb\x1b\x7d\x5f\xc0\x73\xf1\x51\x17\xd6\x88\x9d\xaa\x3b\xe1\xd5\x83\xcf\xb2\x00\xd0\x4e\x48\x00\x6c\xa3\x5b\x59\xbf\xda\x48\xa7\x4a\xc6\x0b\x55\x6a\x6f\xac\xf0\x3b\xe9\x85\xd4\x8d\x13\xde\x88\x8d\x12\x4a\xba\x3d\xfd\xec\x9d\x12\xb2\x2d\x85\x6e\x7d\xaf\xbd\xbe\x53\x4b\x91\xdd\xef\x74\x0d\x68\xed\x8c\xf0\xf2\x56\xb7\x5b\x21\xcb\x3b\xd9\x7a\xb9\x55\xc2\x54\x60\xa5\x44\xd5\xd7\xb5\x28\x64\x27\x37\xba\xc6\x31\xe5\x08\xd1\x98\x52\xd9\x76\xd0\xc2\xad\xb2\xec\xf9\xf3\xe7\xe2\xc6\xe1\x60\x96\x5d\xb4\x85\x12\x7b\xd3\x8b\x9d\xbc\x53\x62\xd3\xeb\xda\x33\xab\xa0\xe0\x52\x38\xdd\x74\xf5\x5e\x38\x2f\xad\x17\xda\x8b\xcd\x5e\xd8\xbe\x6d\x49\x7c\x96\x37\x6c\x64\x27\xfd\xee\xc4\x9b\x93\x0a\xfa\xad\xfc\x83\xcf\x05\x2c\x8b\xe7\x02\x49\x4e\x36\x99\x4e\xb5\x30\x4a\xa8\xa6\xf3\x7b\x48\xaa\x2a\x65\x57\xc9\x51\x6c\x96\xeb\xbb\xce\x58\xef\x44\x61\x95\xf4\x24\x22\x50\x39\x51\x59\xd3\x40\x87\x52\xb7\xeb\x2c\xcb\xf3\x3c\xfb\x41\xe8\xaa\x30\x6d\xa5\xb7\xe2\x1f\x82\x65\x30\x38\xfb\x02\x4b\x0a\x08\x69\x0c\xac\x21\x3b\x8a\xde\x3a\xa8\x23\xad\xe9\xe1\xcf\x7b\xed\x77\x0c\x96\xd6\x9a\x7b\x71\xab\xf6\x8e\xfd\xdc\x18\x78\x3c\x3a\xe6\x17\xb5\xdf\xe8\x16\xa2\xb6\x2e\xcb\xae\x77\x8a\x42\x61\x03\xb3\x52\x55\xb2\x87\x83\x6e\x47\x92\x25\x54\x37\xd0\x34\x71\xd6\x90\x55\x78\x6d\x5a\xf2\x33\xf1\x7b\x2e\xce\x43\xac\x47\xa6\x3f\x89\x33\x6f\xeb\x57\xdf\xd6\x42\x88\xbf\x20\xbe\x09\xa0\x08\x70\xfe\xa0\x8a\xde\x43\xa6\x28\x4c\xd3\x40\xb9\x84\xdd\x12\xf6\xda\x6c\xb7\xc8\x82\x31\xd1\x22\x72\x43\xc8\xab\x1e\x0e\x16\x0e\xc8\x7a\x38\x1c\x54\xf8\x99\xfd\x78\xac\x82\xa3\x63\x9f\x11\xf9\x04\x30\x04\xb8\xa0\x48\x51\x34\x13\xf4\x3b\x41\x6f\xda\xd2\x24\xc0\x9e\xc5\xa9\x11\x50\x11\xe0\x8d\x1e\xb5\x6d\x13\x40\xb4\x13\x35\xbb\x01\xda\x59\x75\xa7\xe1\xf5\x84\x91\xac\x89\xaa\x55\x81\x82\xa8\xeb\x04\x2e\x08\x7c\x66\xba\x7d\x02\x3c\x30\xa0\x1f\x38\xde\xc6\x6f\x51\xeb\x76\xd0\xf7\x8e\x80\x97\xd2\xf9\x01\xd2\x13\xe4\x9d\xac\x2b\x64\x2b\xea\xa5\xef\x12\xa2\x9c\x23\x4a\x73\xdf\x02\x75\x89\xdf\x37\x5d\xe0\x92\xc8\xe9\xd7\x6b\xa0\xd7\x01\x16\x29\xdf\x99\x86\xc3\x26\xc4\x5b\x13\x8a\x78\xab\x43\x79\xa0\xf4\xa2\x4e\xe7\x6d\x19\x48\x22\x8d\x82\xfd\x23\x96\xd5\xb0\x93\xe0\x12\x5c\xb4\x7d\xb3\x41\xe6\x8f\x19\x4d\x2d\x81\x33\x37\xe4\x9f\x13\x7f\x08\x49\xcc\xad\x83\xd3\xfd\xde\xd8\x92\xea\x93\xfe\xaf\x32\x62\x2b\x6a\x55\x79\x4e\x70\xab\xb7\x3b\x7f\x54\x15\x38\x49\x5f\xa1\xb8\x89\x2c\x6a\x46\x40\xd2\x62\x49\xc0\xac\x20\x4e\x7d\xc7\x04\x64\xf5\x31\x9b\xa7\x78\x0c\x45\x9e\x8c\xd8\x99\xba\x44\x82\x6a\x68\xc5\x05\x83\x48\x47\x5a\x58\x47\x6c\x1b\xd5\xfa\x54\x40\xa4\x9e\x0b\x19\x11\x7a\x1f\x08\xe0\xd8\x15\x57\x65\xe4\x3d\xa4\xb4\x68\xe4\x9e\x3a\xa8\x55\x1b\xae\xf4\xde\x51\x0c\x48\x89\xfc\x5f\x27\xab\xd0\x2a\x4e\xb8\x51\x9c\xa4\x23\xab\xdf\x9d\x69\x73\x91\x71\xdf\x12\xe7\xb2\xd8\x91\x63\xa9\x67\x07\x16\x10\x0f\x95\x83\x32\x10\xfa\x06\x96\xaa\x07\x89\xb6\x06\xbf\x50\xa4\x29\x8d\xf3\x50\x0e\xdc\xe0\x70\xc6\xb0\x07\x02\xf0\x3b\x03\x2d\x8a\x64\xc9\x0d\xb6\x30\x3d\x8c\xef\xfa\xd0\x5f\xb3\xca\xd4\xb5\xb9\x27\x25\x75\x1b\xf4\x3c\xd0\x8b\xd5\xe2\x6e\x47\xdf\xd9\xdf\x33\x4a\xa0\x05\xb1\xfe\xb2\x58\x8b\x05\x95\xe3\x62\x39\x02\x7f\x23\x20\x95\xe4\x22\xfb\x67\xe8\x85\xef\x94\x3d\x6a\x5d\x6e\x3d\xe5\xf8\xec\xd9\xe2\xa6\x5b\xc4\xdc\x8c\x7f\x8b\x33\x0e\x2a\x65\x3a\xd8\x83\x82\x7f\xad\x8f\x29\x70\x92\xf1\x57\x94\x5a\x53\x82\x88\x0f\x70\x26\xf9\x80\x34\x7c\x8c\x05\xc3\x99\xe2\x33\xe5\xc4\x54\x97\x45\xe8\x05\x49\x08\xe3\xa7\x9a\x44\xfc\xa8\x26\x53\x4c\x05\x45\x8a\x03\x19\x53\x6d\x23\xc5\x44\xd1\xd3\xda\xcf\x75\x5d\xfc\x0d\xc5\x34\xb2\x00\x7e\x6e\x2e\xe3\xe7\x0c\xe6\x62\xa2\x8c\x27\xc8\x06\x61\x13\xb2\x51\x1a\x85\x75\xa6\xce\xe2\x33\x55\xd9\x45\xf5\x01\xb5\x39\x92\xcc\x34\x5a\xa0\xd9\x1c\x12\xcc\x44\x45\x49\xd7\xe6\x51\x5e\x33\xe5\x07\xd2\x47\x78\xce\xf2\x26\x86\x93\x39\x8e\x24\xb3\xc4\x89\x24\xe0\x74\x20\x2c\x31\x9a\xeb\x75\x40\x94\x58\x4d\x35\x0a\x24\xe7\x2d\x16\x9b\x59\xf6\xbd\x6f\x9d\xb2\x3e\xc0\x43\xdc\x3b\x59\xa8\x47\x48\x02\x9c\x49\x7e\x96\xc5\xad\x9b\x92\x4d\x20\x73\x82\x3f\xa5\xdc\x39\x20\xb8\x96\x9b\x79\x21\x45\x21\x04\x1f\x6c\xb9\x98\x69\x41\x63\xf6\x0d\x8a\x7c\x62\xeb\x0c\x4f\x73\x79\xc4\xbd\x99\xe1\x68\x86\x8e\xb8\x4f\x47\xb8\x4f\x18\xba\x23\xfe\xf2\x08\x7f\x19\xc7\xef\x48\xf3\xdb\x8c\x26\x76\x97\x88\xfb\x32\xc3\x71\x93\x19\x70\x67\xf3\xd2\xc7\xa8\x1e\x71\xbf\x1e\xb4\x85\x89\x4a\xbf\x1c\xa2\xe6\xe9\xf5\xd7\x19\x9a\x47\xf9\x88\x3c\x9d\xbb\x89\x53\xe2\xb4\xae\x03\x01\x0d\xe4\x79\xaf\x99\x24\x14\xa5\xcd\x3c\x4a\x43\x22\x5d\x6e\x0f\x5a\xe1\x22\x2c\x01\x09\xfb\xba\x3d\xc6\x8e\xdd\x87\x4b\x62\xa6\x16\xed\x14\x53\x0e\x5c\x11\x8f\x52\xcc\xb9\x5c\xcd\x68\xc2\x4a\x70\xd5\xd7\x29\x99\x5f\xc3\x5a\x3f\x35\x30\x41\xd0\xf5\x43\xdb\xe7\x25\xf6\xd2\x38\xa7\x37\x98\x99\x71\x0f\x9c\x2c\x12\x2a\x2d\x98\x6d\xba\x85\x44\x1a\xda\x1d\xb0\x96\x39\x9e\x9c\x61\x86\x29\x8c\x21\x9e\x53\x8a\xb1\x3c\xf3\x03\xf1\x6a\x3e\x5e\xba\x43\x79\xe1\x6a\xb3\x1f\x97\x97\x15\x2d\x9d\xf9\x37\xac\xbb\xf9\x9a\xb7\x5e\x17\xf6\xf6\x15\x81\x1d\x52\x1d\x60\xca\x78\x97\x36\x0b\x4b\xab\xc0\xb0\x41\x80\xc8\xaa\xae\x46\xbd\x89\x85\x53\xd2\x16\xbb\x85\x58\xdc\xc9\xba\x57\x0b\x51\xd5\x72\xeb\x70\xfc\x7a\x87\xd9\x7d\xaf\xb1\x53\x24\xd2\x3c\x90\xe6\x61\xdb\xc8\x99\x3e\x5f\x09\x9a\x9a\xb4\x43\xe4\xe1\x24\x5b\x61\x3a\x9a\xf3\xb2\x5e\x11\xf2\x94\x86\x34\x98\x75\x06\x37\xb0\x25\x69\x04\x0a\x7c\x9b\x16\x77\x1a\x83\xf5\x8c\x0e\xae\x45\x5e\xe4\x4b\xda\x4d\xb0\x39\xa8\x56\xc2\x7c\x07\xd0\x4e\x15\xb7\x39\xdf\xba\x58\x4e\x40\x4b\x77\xeb\x70\x65\x21\x87\xfc\x58\x62\xb7\xba\x55\xb4\x1c\x74\xca\x56\xc6\x36\x6c\x71\x54\x99\x17\x20\x45\xbb\x88\xd7\x0d\xee\x67\xe0\xf0\xc9\x78\x15\xdc\x39\x98\xd3\xf4\xce\xd3\xb6\x23\x05\x4c\xd2\x58\xef\xd4\x56\x3d\xac\x84\x78\x5f\xb1\x76\x71\xfd\x92\x76\xdb\x13\x3f\x47\x5c\x4a\x03\xed\x5a\xe3\xc3\x45\x4f\xb6\xb8\xd3\x51\xf7\x72\xb4\x7e\x68\x1f\x56\x14\xda\xa1\x4c\xa3\xc3\x86\xf2\xad\x87\x5c\x17\x5c\xef\x94\x8f\x0e\x12\xc1\x87\x6b\x6c\x66\x3e\x84\x2a\xc2\x61\x0e\xa3\x56\xe2\xb2\xc6\x35\x16\x1b\xa1\x0a\xa9\x41\xfb\x3f\x3e\x78\x8d\x42\x36\x59\x52\x46\xc2\x05\xb0\x00\x7a\x86\xd3\x6e\xc8\x14\xb0\x8d\xd1\xc6\x65\xc6\xed\x5e\xc5\x7c\x82\x40\x00\x82\xc0\x2d\xee\xc3\xed\xfc\x9a\x93\x76\xa8\x0d\x9a\xf2\x96\xaf\x7a\x2b\x0e\x30\xc9\x8a\x24\x3f\x22\x7a\xbd\xa7\xf5\x8b\x33\x04\xce\x2b\xb5\x83\xcb\xf7\x8a\x4f\x93\xdb\x78\xf1\xbe\xdf\x29\x72\x08\xd6\xaf\x56\x43\x86\x4b\xd7\xdd\x78\x3b\xbc\x08\xfa\xa6\x7b\xab\x43\xfd\x80\x66\xdc\x63\x29\xd5\xa9\xa2\x68\xdf\xec\xad\x64\xa3\xd9\xc3\xee\x00\x58\x6a\x0b\x9f\x18\xbb\x1f\xee\xc0\x38\x19\x0c\xcc\x7f\xf8\xf5\xf5\xdb\xaf\x67\x17\x9f\xde\xbc\x7f\xfb\xf5\xdd\xc5\xc7\xf3\x93\x78\x8b\x96\xb1\x38\x9e\x60\x24\x4e\x1d\xa5\x54\x46\x34\xe0\x80\x00\xab\x62\x49\x59\x77\xc4\x30\xa7\x64\xa6\x64\x80\xbb\x97\x47\x1b\x32\x63\x7b\x7a\xab\x80\xc0\x6c\x94\x38\xd3\x79\x56\xfe\x29\x8a\xb3\xa2\x07\xef\x35\x87\xb2\x30\xb5\xb1\x0e\x85\xd1\x50\xe2\xd4\x46\x96\xc9\x8e\x01\x1e\x1c\xc9\x91\xa0\x98\xfd\xf0\x22\x48\x7c\xad\xed\xcb\x93\x09\x99\x3b\xc9\x83\xa8\x7c\x15\x2e\xfd\xd9\xb3\x74\x27\xe7\xe4\x43\x49\xc6\xef\x3c\x7b\x36\xd6\xcd\xf4\xee\x3e\xe5\x26\x5e\x44\xe8\x52\x38\x53\x4b\xab\xbf\xab\x92\xef\x3c\xe3\xe7\x2b\x5f\xbc\xe4\x25\x9b\x4c\x25\x8f\xd5\xa6\x90\x3e\x68\x3a\xe8\xb8\x44\x3a\x15\x32\xde\xd0\xf6\x4c\xaa\x70\x7d\x2b\x4b\x35\x64\x66\x78\x2a\xc1\x8a\x2f\xed\x9e\xab\x9a\xf3\x73\xea\x01\x72\xd9\x46\xc5\x1b\x0f\x0e\xf2\x9b\x07\x25\x16\xbf\xec\xe8\x3a\x94\xaa\x8f\x59\x2d\x0e\x6f\x35\x33\x2f\x4d\x93\x22\x24\x17\xaa\x00\x0c\xd0\xc3\x93\x1f\xe2\x83\x85\x55\x8a\xd9\x4d\x8f\xaf\x83\x82\x3f\x25\xda\x75\x68\x86\xda\x3d\xe5\xc8\x55\xa4\x1f\xbc\x36\x3f\x31\x80\x67\xf6\xbe\xe0\x0c\x8b\x26\xb9\x02\x8a\xb4\x6e\x67\xfc\xcb\xd0\x9e\xf1\x47\x03\x0b\x10\xba\x35\x71\xcf\x7a\x84\x15\x2e\xed\x18\x7e\x08\x33\xd8\x20\xed\xec\xf0\xca\x95\x1e\xd1\xb4\x3f\xd2\x0d\x11\xfd\x5f\xd4\xab\xe8\x62\x6b\xfb\x18\xa3\xa5\xf8\x9d\x5a\x6e\xd4\xac\x91\xe8\xdf\xae\xb7\xea\x40\xe8\xf0\x80\x35\x1e\x44\x48\xc1\x49\xc5\xfb\xa8\x0e\x57\xe4\x21\x29\x23\xbf\x8f\xef\xcf\xae\x2e\xbe\x5e\x5f\xdd\x9c\x9f\x5d\x7c\xb8\xb8\xc2\x30\xb9\xd3\xd6\xb4\x3c\x0b\xee\xa0\x1b\x8d\x16\xd2\x96\xda\x30\x0c\xfb\x63\x62\xca\x97\x6e\xe2\x1b\x27\x29\x57\x9b\x97\x1b\x07\x6b\xa6\x2d\x1a\x20\x41\x30\x3a\x9c\x0a\xe8\x91\xd2\xf9\x73\x1e\x9a\xfd\xbe\xf5\xf2\x01\xe7\x7d\x6f\x51\xd3\xe1\x13\x0d\x92\x9e\xf5\x4c\x55\x3d\x72\x90\xd9\x45\xd1\x58\xe0\x79\xae\xe0\x3c\x05\x60\x18\x32\xd8\xe0\x64\x78\x18\x00\xd1\x63\x3c\xaa\x2a\x30\x91\xbd\xc7\xd4\x2d\x61\x3b\x58\x70\x37\x1e\x9e\x03\x25\x86\xc9\x7d\x68\xd3\xe9\x39\xc4\xc9\x86\xa2\x4f\xe4\xa1\x1d\xc6\x1e\x49\x9e\x4d\x6f\x4c\xe1\xa5\x85\x7d\xfd\x94\xe6\x96\x36\x2c\xc8\x8b\x33\xe1\xe0\x0d\xe6\x3f\x9d\xdc\x9a\xaa\x21\x4d\xe9\xf1\x2d\x7e\xb0\xd6\xea\x4e\xd9\xa1\x74\x39\x78\xc8\xe1\x52\xbc\xe0\xcc\xe3\x3d\x42\x76\x5d\x4d\x6f\xb4\x14\x94\xd5\x16\x5d\x97\xe4\x70\xa1\xbf\x7c\x42\x64\x72\xd1\xd6\xe8\x86\x13\x2d\x8c\xc5\x29\xe0\xff\x94\x4d\x72\x06\xf9\xff\x5d\xf6\x29\xd6\x86\x38\xef\xa9\xcb\x53\x3e\xc6\x02\x8e\xab\x64\x9a\xad\x41\x60\x7c\x5f\x62\x2d\xb2\xc3\x86\x85\xc3\x7e\x78\xee\x40\x11\x2e\x89\x95\xc2\xba\x57\x84\x6d\xe4\x70\x8a\x32\x97\xc4\x3f\xe3\x9c\xa0\x2e\x09\xa1\x20\x5e\xd1\xea\x33\x5f\xae\x3c\x15\xe9\x23\x7c\xb8\xf1\x91\xf6\xfc\xbe\x63\x68\xb7\xcb\x1a\x2c\x5c\xfc\xde\x15\x9f\xad\x0b\x5c\x63\x42\x7a\xcd\x94\x8c\xf6\xf0\x19\x11\xcf\xa0\xea\xde\xb7\xf1\xf5\x1d\x05\xa3\x96\x61\x84\xc7\x4c\xe0\xe7\xa0\x69\x74\xa2\xfe\x16\xee\x2d\xc2\xc8\x0b\x9d\x41\x8a\xae\xee\xb7\x34\xfe\xdc\x1e\xa5\xd2\xac\xc4\x3b\x6c\xdd\x88\xe3\x32\xed\xc2\xf5\x3e\x4c\x17\x6e\x3e\x9a\x96\x44\xa7\x15\xbf\xe1\x75\xd6\x6c\xad\x6c\xe2\x8c\x01\x8f\xd8\x0e\xfe\x1d\x00\x00\xff\xff\xdd\xaf\xa3\xc8\x97\x18\x00\x00") +var _runtimeHelpHelpMd = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xa4\x58\x4b\x73\xdc\xb8\x11\x3e\x9b\xbf\x02\x35\x76\xd5\xda\x9b\xd1\xa8\x92\xca\x69\x6e\x5a\x49\x7e\x64\x6d\x4b\x91\xe5\x64\xbd\x17\x13\x43\x82\x33\x58\x91\x04\x0d\x80\x92\xc6\x79\xfc\xf6\x7c\xdd\x00\xf8\x18\x49\x49\xaa\x56\x17\x0d\xbb\x1b\xfd\x42\x3f\xf1\x5c\x7c\xd0\x85\x35\x62\xa7\xea\x4e\x78\x75\xef\xb3\x2c\x00\xb4\x13\x12\x00\xdb\xe8\x56\xd6\x47\x1b\xe9\x54\xc9\x78\xa1\x4a\xed\x8d\x15\x7e\x27\xbd\x90\xba\x71\xc2\x1b\xb1\x51\x42\x49\xb7\xa7\x9f\xbd\x53\x42\xb6\xa5\xd0\xad\xef\xb5\xd7\xb7\x6a\x29\xb2\xbb\x9d\xae\x01\xad\x9d\x11\x5e\xde\xe8\x76\x2b\x64\x79\x2b\x5b\x2f\xb7\x4a\x98\x0a\xac\x94\xa8\xfa\xba\x16\x85\xec\xe4\x46\xd7\x38\xa6\x1c\x21\x1a\x53\x2a\xdb\x0e\x5a\xb8\x55\x96\x3d\x7f\xfe\x5c\x7c\x76\x38\x98\x65\x17\x6d\xa1\xc4\xde\xf4\x62\x27\x6f\x95\xd8\xf4\xba\xf6\xcc\x2a\x28\xb8\x14\x4e\x37\x5d\xbd\x17\xce\x4b\xeb\x85\xf6\x62\xb3\x17\xb6\x6f\x5b\x12\x9f\xe5\x0d\x1b\xd9\x49\xbf\x3b\xf6\xe6\xb8\x82\x7e\x2b\x7f\xef\x73\x01\xcb\xe2\xb9\x40\x92\x93\x4d\xa6\x53\x2d\x8c\x12\xaa\xe9\xfc\x1e\x92\xaa\x4a\xd9\x55\x72\x14\x9b\xe5\xfa\xae\x33\xd6\x3b\x51\x58\x25\x3d\x89\x08\x54\x4e\x54\xd6\x34\xd0\xa1\xd4\xed\x3a\xcb\xf2\x3c\xcf\x5e\x08\x5d\x15\xa6\xad\xf4\x56\xfc\x53\xb0\x0c\x06\x67\x5f\x60\x49\x01\x21\x8d\x81\x35\x64\x47\xd1\x5b\x07\x75\xa4\x35\x3d\xfc\x79\xa7\xfd\x8e\xc1\xd2\x5a\x73\x27\x6e\xd4\xde\xb1\x9f\x1b\x03\x8f\x47\xc7\xfc\xac\xf6\x1b\xdd\x42\xd4\xd6\x65\xd9\xf5\x4e\xd1\x55\xd8\xc0\xac\x54\x95\xec\xe1\xa0\x9b\x91\x64\x09\xd5\x0d\x34\x4d\x9c\x35\x64\x15\x5e\x9b\x96\xfc\x4c\xfc\x9e\x8b\xf3\x70\xd7\x23\xd3\x1f\xc5\xa9\xb7\xf5\xd1\xb7\xb5\x10\xe2\xaf\xb8\xdf\x04\x50\x04\x38\xbf\x57\x45\xef\x21\x53\x14\xa6\x69\xa0\x5c\xc2\x6e\x09\x7b\x6d\xb6\x5b\x44\xc1\x18\x68\x11\xb9\x21\xe4\x55\x0f\x07\x0b\x07\x64\x3d\x1c\x0e\x2a\xfc\xc4\x7e\x7c\xa8\x82\xa3\x63\x9f\x70\xf3\x09\x60\x08\x70\x41\x37\x45\xb7\x99\xa0\xdf\x09\xfa\xb9\x2d\x4d\x02\xec\x59\x9c\x1a\x01\x15\x01\x5e\xeb\x51\xdb\x36\x01\x44\x3b\x51\xb3\x1b\xa0\x9d\x55\xb7\x1a\x5e\x4f\x18\xc9\x9a\xa8\x5a\x15\x48\x88\xba\x4e\xe0\x82\xc0\xa7\xa6\xdb\x27\xc0\x3d\x03\xfa\x81\xe3\x4d\xfc\x16\xb5\x6e\x07\x7d\x6f\x09\x78\x29\x9d\x1f\x20\x3d\x41\xde\xca\xba\x42\xb4\x22\x5f\xfa\x2e\x21\xca\x39\xa2\x34\x77\x2d\x50\x97\xf8\xfd\xb9\x0b\x5c\x12\x39\xfd\x3a\x03\x7a\x1d\x60\x91\xf2\xad\x69\xf8\xda\x84\x78\x63\x42\x12\x6f\x75\x48\x0f\xa4\x5e\xd4\xe9\xbc\x2d\x03\x49\xa4\x51\xb0\x7f\xc4\xb2\x1a\x76\x72\xb9\x04\x17\x6d\xdf\x6c\x10\xf9\x63\x44\x53\x49\xe0\xc8\x0d\xf1\xe7\xc4\x1f\x42\x10\x73\xe9\xe0\x70\xbf\x33\xb6\xa4\xfc\xa4\xff\xab\x8c\xd8\x8a\x5a\x55\x9e\x03\xdc\xea\xed\xce\x3f\xc8\x0a\x9c\xa4\xaf\x90\xdc\x44\x16\x35\x23\x20\x69\xb1\x24\x60\x56\x10\xa7\xbe\x63\x02\xb2\xfa\x21\x9b\xa7\x78\x0c\x49\x9e\x8c\xd8\x99\xba\x44\x80\x6a\x68\xc5\x09\x83\x9b\x8e\xb4\xb0\x8e\xd8\x36\xaa\xf5\x29\x81\x48\x3d\x17\x22\x22\xd4\x3e\x10\xc0\xb1\x2b\xce\xca\xc8\x7b\x08\x69\xd1\xc8\x3d\x55\x50\xab\x36\x9c\xe9\xbd\xa3\x3b\x20\x25\xf2\x7f\x1f\xaf\x42\xa9\x38\xe6\x42\x71\x9c\x8e\xac\x7e\x73\xa6\xcd\x45\xc6\x75\x4b\x9c\xcb\x62\x47\x8e\xa5\x9a\x1d\x58\x40\x3c\x54\x0e\xca\x40\xe8\x6b\x58\xaa\xee\x25\xca\x1a\xfc\x42\x37\x4d\x61\x9c\x87\x74\xe0\x02\x87\x33\x86\x3d\x10\x80\xdf\x19\x68\x91\x24\x4b\x2e\xb0\x85\xe9\x61\x7c\xd7\x87\xfa\x9a\x55\xa6\xae\xcd\x1d\x29\xa9\xdb\xa0\xe7\x81\x5e\xac\x16\x57\x3b\xfa\xce\xfe\x91\x51\x00\x2d\x88\xf5\x97\xc5\x5a\x2c\x28\x1d\x17\xcb\x11\xf8\x2b\x01\x29\x25\x17\xd9\xbf\x42\x2d\x7c\xab\xec\x83\xd2\xe5\xd6\x53\x8e\xcf\x9e\x2d\x3e\x77\x8b\x18\x9b\xf1\x6f\x71\xca\x97\x4a\x91\x0e\xf6\xa0\xe0\x5f\xeb\x87\x14\x38\xc9\xf8\x2b\x0a\xad\x29\x41\xc4\x07\x38\x93\xbc\x47\x18\x3e\xc6\x82\xe1\x4c\xf1\x89\x62\x62\xaa\xcb\x22\xd4\x82\x24\x84\xf1\x53\x4d\x22\x7e\x54\x93\x29\xa6\x82\x22\xc5\x81\x8c\xa9\xb6\x91\x62\xa2\xe8\x49\xed\xe7\xba\x2e\xfe\x8e\x64\x1a\x59\x00\x3f\x37\x97\xf1\x73\x06\x73\x31\x51\xc6\x13\x64\x83\xb0\x09\xd9\x28\x8d\xae\x75\xa6\xce\xe2\x13\x65\xd9\x45\xf5\x1e\xb9\x39\x92\xcc\x34\x5a\xa0\xd8\x1c\x12\xcc\x44\x45\x49\xd7\xe6\x51\x5e\x33\xe5\x07\xd2\x47\x78\xce\xe2\x26\x5e\x27\x73\x1c\x49\x66\x81\x13\x49\xc0\xe9\x40\x58\x62\x34\xd7\xeb\x80\x28\xb1\x9a\x6a\x14\x48\xce\x5b\x0c\x36\xb3\xe8\x7b\xd7\x3a\x65\x7d\x80\x87\x7b\xef\x64\xa1\x1e\x21\x09\x70\x26\xf9\x49\x16\x37\x6e\x4a\x36\x81\xcc\x09\xfe\x94\x62\xe7\x80\xe0\x5a\x6e\xe6\x89\x14\x85\x10\x7c\xb0\xe5\x62\xa6\x05\xb5\xd9\xd7\x48\xf2\x89\xad\x33\x3c\xf5\xe5\x11\xf7\x7a\x86\xa3\x1e\x3a\xe2\x3e\x3e\xc0\x7d\x44\xd3\x1d\xf1\x97\x0f\xf0\x97\xb1\xfd\x8e\x34\xbf\xce\x68\x62\x75\x89\xb8\x2f\x33\x1c\x17\x99\x01\x77\x3a\x4f\x7d\xb4\xea\x11\xf7\xcb\x41\x59\x98\xa8\xf4\xf3\x21\x6a\x1e\x5e\x7f\x9b\xa1\xb9\x95\x8f\xc8\x93\xb9\x9b\x38\x24\x4e\xea\x3a\x10\x50\x43\x9e\xd7\x9a\x49\x40\x51\xd8\xcc\x6f\x69\x08\xa4\xcb\xed\x41\x29\x5c\x84\x21\x20\x61\xcf\xda\x87\xd8\xb1\xfa\x70\x4a\xcc\xd4\xa2\x99\x62\xca\x81\x33\xe2\x51\x8a\x39\x97\xab\x19\x4d\x18\x09\xae\xfa\x3a\x05\xf3\x19\xac\xf5\x53\x03\x13\x04\x55\x3f\x94\x7d\x1e\x62\x2f\x8d\x73\x7a\x83\x9e\x19\xe7\xc0\xc9\x20\xa1\xd2\x80\xd9\xa6\x2d\x24\xd2\xd0\xec\x80\xb1\xcc\x71\xe7\x0c\x3d\x4c\xa1\x0d\x71\x9f\x52\x8c\xe5\x9e\x1f\x88\x57\xf3\xf6\xd2\x1d\xca\x0b\xab\xcd\x7e\x1c\x5e\x56\x34\x74\xe6\xdf\x30\xee\xe6\x6b\x9e\x7a\x5d\x98\xdb\x57\x04\x76\x08\x75\x80\x29\xe2\x5d\x9a\x2c\x2c\x8d\x02\xc3\x04\x01\x22\xab\xba\x1a\xf9\x26\x16\x4e\x49\x5b\xec\x16\x62\x71\x2b\xeb\x5e\x2d\x44\x55\xcb\xad\xc3\xf1\xeb\x1d\x7a\xf7\x9d\xc6\x4c\x91\x48\xf3\x40\x9a\x87\x69\x23\x67\xfa\x7c\x25\xa8\x6b\xd2\x0c\x91\x87\x93\x6c\x85\xe9\xa8\xcf\xcb\x7a\x45\xc8\x13\x6a\xd2\x60\xd6\x19\x6c\x60\x4b\xd2\x08\x14\xf8\x36\x2d\x76\x1a\x83\xf1\x8c\x0e\xae\x45\x5e\xe4\x4b\x9a\x4d\x30\x39\xa8\x56\xc2\x7c\x07\xd0\x4e\x15\x37\x39\x6f\x5d\x2c\x27\xa0\xa5\xbb\x71\x58\x59\xc8\x21\x3f\x94\x98\xad\x6e\x14\x0d\x07\x9d\xb2\x95\xb1\x0d\x5b\x1c\x55\xe6\x01\x48\xd1\x2c\xe2\x75\x83\xfd\x0c\x1c\x3e\x1a\xaf\x82\x3b\x07\x73\x9a\xde\x79\x9a\x76\xa4\x80\x49\x1a\xe3\x9d\xda\xaa\xfb\x95\x10\xef\x2a\xd6\x2e\x8e\x5f\xd2\x6e\x7b\xe2\xe7\x88\x4b\x69\xa0\x5d\x6b\x7c\x58\xf4\x64\x8b\x9d\x8e\xaa\x97\xa3\xf1\x43\xfb\x30\xa2\xd0\x0c\x65\x1a\x1d\x26\x94\x6f\x3d\xe4\xba\xe0\x7a\xa7\x7c\x74\x90\x08\x3e\x5c\x63\x32\xf3\xe1\xaa\x22\x1c\xe6\x30\x6a\x25\x2e\x6b\xac\xb1\x98\x08\x55\x08\x0d\x9a\xff\xf1\xc1\x63\x14\xa2\xc9\x92\x32\x12\x2e\x80\x05\xd0\x33\x9c\x76\x43\xa4\x80\x6d\xbc\x6d\x2c\x33\x6e\x77\x14\xe3\x09\x02\x01\x08\x02\xb7\xd8\x87\xdb\xf9\x9a\x93\x66\xa8\x0d\x8a\xf2\x96\x57\xbd\x15\x5f\x30\xc9\x8a\x24\x3f\xe0\xf6\x7a\x4f\xe3\x17\x47\x08\x9c\x57\x6a\x07\x97\xef\x15\x9f\x26\xb7\xf1\xe0\x7d\xb7\x53\xe4\x10\x8c\x5f\xad\x86\x0c\x97\xd6\xdd\xb8\x1d\x5e\x04\x7d\xd3\xde\xea\x90\x3f\xa0\x19\xe7\x58\x0a\x75\xca\x28\x9a\x37\x7b\x2b\xd9\x68\xf6\xb0\x3b\x00\x96\xda\xc2\x27\xc6\xee\x87\x1d\x18\x27\x83\x81\xf9\x8b\x5f\xce\xde\x7c\x3d\xbd\xf8\xf8\xfa\xdd\x9b\xaf\x6f\x2f\x3e\x9c\x1f\xc7\x2d\x5a\xc6\xe4\x78\x82\x91\x38\x71\x14\x52\x19\xd1\x80\x03\x2e\x58\x15\x4b\x8a\xba\x07\x0c\x73\x0a\x66\x0a\x06\xb8\x7b\xf9\x60\x42\x66\x6c\x4f\x6f\x15\x10\x98\x8d\x12\x67\x3a\xcf\xd2\x3f\xdd\xe2\x2c\xe9\xc1\x7b\xcd\x57\x59\x98\xda\x58\x87\xc4\x68\x28\x70\x6a\x23\xcb\x64\xc7\x00\x0f\x8e\xe4\x9b\xa0\x3b\x7b\xf1\x32\x48\x3c\xd3\xf6\xd5\xf1\x84\xcc\x1d\xe7\x41\x54\xbe\x0a\x4b\x7f\xf6\x2c\xed\xe4\x1c\x7c\x48\xc9\xf8\x9d\x67\xcf\xc6\xbc\x99\xee\xee\x53\x6e\xe2\x65\x84\x2e\x85\x33\xb5\xb4\xfa\xbb\x2a\x79\xe7\x19\x3f\x8f\x7c\xf1\x8a\x87\x6c\x32\x95\x3c\x56\x9b\x42\xfa\xa0\xe9\xa0\xe3\x12\xe1\x54\xc8\xb8\xa1\xed\x99\x54\x61\x7d\x2b\x4b\x35\x44\x66\x78\x2a\xc1\x88\x2f\xed\x9e\xb3\x9a\xe3\x73\xea\x01\x72\xd9\x46\xc5\x8d\x07\x07\xf9\xcd\x83\x02\x8b\x5f\x76\x74\x1d\x52\xd5\xc7\xa8\x16\x87\x5b\xcd\xcc\x4b\xd3\xa0\x08\xc1\x85\x2c\x00\x03\xd4\xf0\xe4\x87\xf8\x60\x61\x95\x62\x76\xd3\xe3\xeb\xa0\xe0\x8f\x89\x76\x1d\x8a\xa1\x76\x4f\x39\x72\x15\xe9\x07\xaf\xcd\x4f\x0c\xe0\x99\xbd\x2f\x39\xc2\xa2\x49\xae\x80\x22\xad\xdb\x19\xff\x2a\x94\x67\xfc\x51\xc3\x02\x84\xb6\x26\xae\x59\x8f\xb0\xc2\xd2\x8e\xe6\x87\x6b\x06\x1b\x84\x9d\x1d\x5e\xb9\xd2\x23\x9a\xf6\x0f\x74\xc3\x8d\xfe\x3f\xea\x55\xb4\xd8\xda\x3e\xde\xd1\x52\xfc\x46\x25\x37\x6a\xd6\x48\xd4\x6f\xd7\x5b\x75\x20\x74\x78\xc0\x1a\x0f\xe2\x4a\xc1\x49\xc5\x7d\x54\x87\x15\x79\x08\xca\xc8\xef\xc3\xbb\xd3\xab\x8b\xaf\xd7\x57\x9f\xcf\x4f\x2f\xde\x5f\x5c\xa1\x99\xdc\x6a\x6b\x5a\xee\x05\xb7\xd0\x8d\x5a\x0b\x69\x4b\x65\x18\x86\xfd\x31\x31\xe5\xa5\x9b\xf8\xc6\x4e\xca\xd9\xe6\xe5\xc6\xc1\x9a\x69\x89\x06\x48\x10\x8c\x0e\xa7\x04\x7a\x24\x75\xfe\x9c\x87\x62\xbf\x6f\xbd\xbc\xc7\x79\xdf\x5b\xe4\x74\xf8\x44\x81\xa4\x67\x3d\x53\x55\x8f\x1c\x64\x76\x51\x34\x06\x78\xee\x2b\x38\x4f\x17\x30\x34\x19\x4c\x70\x32\x3c\x0c\x80\xe8\x31\x1e\x55\x15\x98\xc8\xde\xa3\xeb\x96\xb0\x1d\x2c\xb8\x1a\x0f\xcf\x81\x12\xcd\xe4\x2e\x94\xe9\xf4\x1c\xe2\x64\x43\xb7\x4f\xe4\xa1\x1c\xc6\x1a\x49\x9e\x4d\x6f\x4c\xe1\xa5\x85\x7d\xfd\x94\xe6\x96\x26\x2c\xc8\x8b\x3d\xe1\xe0\x0d\xe6\xc9\x93\x47\x47\x47\x59\x76\x16\x31\x5d\xdd\x6f\xa9\x93\x84\x5a\x18\x4a\x1f\xf8\x78\x66\x4c\x3f\x44\x2d\xdb\x6d\x8f\x89\x8f\x06\x09\x41\x33\x8f\x78\x19\x43\x06\xc1\x37\x22\xa9\x7e\x9c\x2e\xc5\xd9\x52\xbc\x31\x4b\xf1\x17\x79\x2b\x79\xcf\xa7\x1f\xc8\x13\xdd\xa1\x5c\xbd\xef\x25\x12\xe5\xd2\x9a\x5b\x5d\x8e\xb3\x59\x12\x17\x55\x59\xfd\x77\x9b\xb7\x46\x37\x1c\xae\x50\x8f\x9e\x0e\x87\xef\xa4\xdd\x23\x02\xb6\xe6\x7f\x31\x4f\xd7\xb8\x35\x55\xe3\x07\xce\xf8\xfd\xbb\xb8\x92\xca\x27\x98\x59\xe2\xb0\x41\x2d\x86\x92\x21\x56\x8f\x38\xc7\xa6\xc6\x4e\x52\xca\xf4\xb8\x45\x95\x53\x64\x87\xd5\x12\x87\xfd\xf0\xd6\x82\x0a\xb0\x24\x56\x0a\xb3\x66\x11\x46\xa1\xc3\x16\xce\x5c\x12\xff\x8c\x03\x92\x4a\x34\x84\x82\x78\x45\x73\xd7\x7c\xb2\xf3\x54\x21\x1e\xe1\xc3\x55\x97\xb4\xe7\xc7\x25\x43\x83\x65\xd6\x60\xda\xe3\xc7\xb6\xf8\x66\x5e\x60\x87\x0a\xb1\x3d\x53\x32\xda\xc3\x67\x44\x3c\xb3\xca\xfe\x13\x00\x00\xff\xff\x76\x03\x88\xe2\x7b\x18\x00\x00") func runtimeHelpHelpMdBytes() ([]byte, error) { return bindataRead( @@ -252,7 +254,47 @@ func runtimeHelpHelpMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "runtime/help/help.md", size: 6295, mode: os.FileMode(420), modTime: time.Unix(1462111989, 0)} + info := bindataFileInfo{name: "runtime/help/help.md", size: 6267, mode: os.FileMode(420), modTime: time.Unix(1462467388, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _runtimePluginsGoGoLua = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x94\x91\x4f\x4f\xec\x20\x14\xc5\xf7\xfd\x14\x37\x24\x2f\xa1\xef\xf5\xf5\x03\x34\x99\x85\x2e\x9c\xe5\x18\x5d\x1a\x35\xd8\xde\xb6\x24\x0c\x10\xb8\x9d\xd1\x18\xbf\xbb\xd0\x32\xff\x9c\xfa\x8f\x4d\x53\xee\x39\x3f\xce\x01\xd9\xc2\x12\x69\x65\x49\x1a\xcd\x59\x67\xe4\xda\x1a\x47\x9e\xe5\xb0\x58\x80\x96\x0a\xa8\x47\x9d\x41\x58\x17\x4d\x73\x2e\x2b\xa0\x15\xca\x63\x9e\xa1\x6e\xb2\x8f\xac\x76\x4d\xdf\x71\xa2\xa4\x00\x72\x43\x42\x64\xed\xa0\xeb\x38\x84\xce\x3c\x1a\x7d\x2b\x36\xc8\xf3\xd1\x16\xe0\x1b\x89\xdb\xf2\x72\x68\xcb\x2b\xa9\x90\x5e\x2c\x46\x36\x5b\x1a\x76\x80\x27\xe5\x27\x95\x4e\x64\x71\x85\x43\xf6\xf3\x74\x4e\x5c\x18\x2a\xcd\x97\x99\x27\x84\xd9\xb1\x3b\xf6\xd8\xfd\xc4\xcc\xd5\x0d\xae\x2c\xea\x24\x89\xe3\xb3\xaa\xc7\x0c\x65\x6a\xa1\xa0\x17\xba\x51\xa1\x21\x48\x53\x5a\x13\xed\x53\x08\xf8\xbf\x05\x06\x65\x79\xb8\x8d\x6b\x41\xfd\xb1\xd3\xa1\x1f\x14\x05\xe7\x84\xa8\x1c\x8a\x86\xb3\xbf\x82\x4d\xa2\xb4\x5b\x2b\xe3\xe3\xdd\xce\x44\x39\xbd\x90\x2f\xe2\x24\xe1\x2f\x22\x05\xbe\xb7\x4a\x12\x3f\xcb\x56\x00\xab\x7e\x98\x70\x22\x78\x72\x05\x78\xb4\xb3\xe7\xbc\xbe\x9d\xec\x76\xf8\x1c\x36\x39\xe3\x77\x0f\x7f\xfc\xfd\xbf\x9c\xe5\x55\x6b\xdc\x5a\x04\xca\x0e\x10\xfe\x01\x45\xdd\x83\xd4\x10\xd0\x55\x17\xa6\x75\xcf\x47\x6f\x0e\x8d\xd9\xbf\x28\x89\x27\x85\xa5\xd4\x1e\x1d\xf1\xe9\xc0\x62\x74\x1e\x9e\x37\x7e\x1d\xd2\xe0\x74\x4a\x34\x76\x78\x0f\x00\x00\xff\xff\xb7\x44\x98\xaf\x6b\x03\x00\x00") + +func runtimePluginsGoGoLuaBytes() ([]byte, error) { + return bindataRead( + _runtimePluginsGoGoLua, + "runtime/plugins/go/go.lua", + ) +} + +func runtimePluginsGoGoLua() (*asset, error) { + bytes, err := runtimePluginsGoGoLuaBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "runtime/plugins/go/go.lua", size: 875, mode: os.FileMode(420), modTime: time.Unix(1462550078, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _runtimePluginsLinterLinterLua = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x8c\x55\x6d\x6b\xe4\x36\x10\xfe\xbe\xbf\x42\x88\xba\x58\x89\xed\xd0\x7c\x5c\x48\xe1\x7a\xa5\x81\x72\xed\x1d\xa4\xe5\x3e\xf4\xe5\xd0\xda\xe3\xb5\x1a\x59\x32\x92\x9c\xcd\x52\xfa\xdf\x3b\x92\x1c\xaf\x6d\x9c\xf8\x96\x10\xcd\xca\x33\xcf\x33\x2f\xcf\x8e\x45\x4d\xee\xc1\x7d\xec\x9c\xd0\x2a\xa5\x52\x28\x07\x86\x32\x72\x77\x47\x94\x90\xc4\x35\xa0\x76\x04\x3f\xef\xaa\x6a\xe1\x93\x11\x67\x7a\x60\x3b\x50\xd5\x6e\x57\xf7\xaa\xf4\x4f\x49\x7c\xf8\x45\xab\x07\xfe\x04\x29\x0b\xb1\xaf\x50\x8c\xd8\xfe\x23\x75\xc9\x25\xa9\x1d\xb9\x23\x4f\x02\x4e\xc5\x0f\x7d\x5d\xfc\x24\x24\xb8\x73\x07\x4b\x27\xbc\x9e\xba\x7d\xe2\xae\x59\xb8\x54\xf0\xa4\x7a\x29\xd1\x8b\xde\xa0\x7d\xe3\xbf\xd0\xd1\x07\xf3\xf9\xf8\xe0\x2b\xa4\x27\xa1\x2a\x7d\xb2\x74\x9e\x8b\xff\x4c\x10\x7e\xfd\xfd\xc3\x25\xd6\x57\x3b\xc1\xf1\x09\xa3\xcb\xbd\x5e\x81\x18\x5a\xe1\x8f\x94\x1e\xf5\xa1\x17\xb2\xc2\xae\xa1\x49\x82\x4d\x72\x4d\x28\x29\x8a\x17\x2e\x7c\x94\xd4\xfb\x44\xee\x49\xd2\x52\xf6\x16\x94\x3f\x23\x92\xb7\x22\xc8\xac\x1d\x23\x54\x52\x5d\x2f\xe0\x40\x5a\xb8\x24\xfe\xa1\xe7\x5b\x99\xcb\x9e\x97\x0d\x94\x8f\x9e\xf0\xc5\x26\x79\xae\x74\x5e\x6a\xa9\x4d\x64\xf7\x33\xf9\x6a\xd2\x4f\x67\xd7\x68\xb5\xc5\xdb\x9d\x6b\xc9\x1f\xc1\x7a\xde\x17\x7b\x85\xec\x2d\xa2\xf7\x9b\x53\x29\xcb\xd0\xc7\xb2\x24\x79\x6d\xcf\xca\xf1\xe7\x5c\x2b\x79\x26\xf9\x67\x8e\xc3\xcf\x3f\xc3\xb3\x33\xfc\x95\x12\x8b\xb7\xab\xfc\x71\x8b\xbc\x6a\x83\x1c\xf0\x20\xb1\x95\x77\xba\xae\x51\x14\x39\xc9\x4f\xf8\x27\xf0\x76\xc1\x9c\xa4\x89\x4c\xd8\x16\xf1\xcf\xfc\x69\x73\xa8\xff\xa0\x4f\x28\x3d\x18\x6b\x6d\x05\x63\xb4\xd9\xe4\x79\x28\x8d\xe8\xdc\x26\x9b\x6d\x06\xc5\x46\x6b\xc9\xe7\xbd\x81\x24\x32\x2b\xae\xb3\x05\xe3\xf0\x73\xf3\xcc\xe3\xa5\x97\xfa\xfe\xbd\x04\x6e\xde\x49\x79\xdf\x3b\x24\xfa\x05\xac\xe5\x47\xb0\xc3\xc6\xf1\x61\xab\x7b\x29\xe4\x13\xed\x8c\x94\x6d\x95\xc5\x3a\x6b\x6d\x5a\xee\x62\xec\x05\x7d\x01\x1d\xc3\xd8\x2e\x78\xc5\x3d\xd3\x70\x55\x85\x65\x24\x74\xd1\xe9\x0e\x70\xbd\xa5\xa1\x36\x84\xf6\x07\x65\xe1\x1b\x25\xb7\xdf\x7f\xfb\xdd\x50\x56\x8c\xf4\x15\x5b\x0c\x1c\xf2\xb2\x9d\x14\x2e\x8d\x70\x7b\x03\xbc\x4a\xe9\x15\xa7\x0c\xdb\xf3\xa7\x1a\xe2\x86\x87\xa5\xd4\xd6\x2f\xd6\x09\x96\x81\x23\x3c\x23\xd6\xa4\x94\xfd\xd1\xf6\x87\x94\x26\x49\xed\xbb\x9e\x16\xd7\x8c\xb2\xf1\x4e\x86\x3b\x94\xf0\xf4\xb2\xbd\x38\x06\x68\xc4\x21\x5f\xb2\x30\x18\xa1\x88\xe8\xb8\x30\xa1\x05\x60\x19\xa9\xf4\x38\x8b\x3c\x27\xbf\x19\xd1\x92\x53\x23\x1c\xd8\x8e\x97\x93\x6d\xed\x63\x43\x85\xb0\xc7\x94\xca\x26\xa5\x7f\x27\xf6\xca\x73\xe0\xf1\xcd\x64\xca\xa8\x29\xeb\x8c\x50\xc7\xa2\xc6\x85\x1c\x58\xb2\x58\x15\x5b\x51\xd6\xf8\x12\xc8\x48\xf4\x6c\xed\x11\x79\x06\x84\xc8\x34\x85\x98\x45\x23\xd5\xd0\xf2\x03\xb7\xa0\x78\x0b\xe9\x6c\x73\x86\x37\xdf\xd2\xc3\x93\xad\x64\x32\xaa\x65\x26\x94\x51\x5e\x4e\xab\xbe\x3d\x80\x09\xc9\xb0\x90\x66\x46\x6e\xe7\xe9\x4c\x5f\x27\xa3\xd6\x5f\x13\x6f\x14\x09\xd6\x99\x11\x0b\x1d\x9b\x29\xc0\xf6\xd2\xbf\x3b\xff\xfd\x6f\x45\x17\x28\xca\x3f\xb0\xf3\x7f\x85\x79\x47\x7d\xa4\x23\x80\x9f\x33\xe0\x56\xf7\x53\x46\xe8\xfd\x31\x36\x70\xe8\xfe\x64\xd2\x8e\x1f\x24\x14\x42\x59\x30\x2e\x8d\x84\x59\x88\xbc\xfc\xe4\xfc\x69\xc0\xf5\x46\x0d\x19\xad\xd7\x31\xef\xeb\x24\x61\x7f\x79\x99\x64\x50\xe6\xb0\x24\xd2\xe2\xea\x86\xe1\x3f\xe6\x55\x9a\xdc\x0e\xda\x19\xb8\x7c\x58\x60\xfa\x3f\x00\x00\xff\xff\xd5\x66\x68\xff\xcc\x08\x00\x00") + +func runtimePluginsLinterLinterLuaBytes() ([]byte, error) { + return bindataRead( + _runtimePluginsLinterLinterLua, + "runtime/plugins/linter/linter.lua", + ) +} + +func runtimePluginsLinterLinterLua() (*asset, error) { + bytes, err := runtimePluginsLinterLinterLuaBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "runtime/plugins/linter/linter.lua", size: 2252, mode: os.FileMode(420), modTime: time.Unix(1462467208, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1552,7 +1594,7 @@ func runtimeSyntaxRMicro() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "runtime/syntax/r.micro", size: 238, mode: os.FileMode(420), modTime: time.Unix(1462215620, 0)} + info := bindataFileInfo{name: "runtime/syntax/r.micro", size: 238, mode: os.FileMode(420), modTime: time.Unix(1462282751, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2014,6 +2056,8 @@ var _bindata = map[string]func() (*asset, error){ "runtime/colorschemes/solarized-tc.micro": runtimeColorschemesSolarizedTcMicro, "runtime/colorschemes/solarized.micro": runtimeColorschemesSolarizedMicro, "runtime/help/help.md": runtimeHelpHelpMd, + "runtime/plugins/go/go.lua": runtimePluginsGoGoLua, + "runtime/plugins/linter/linter.lua": runtimePluginsLinterLinterLua, "runtime/syntax/Dockerfile.micro": runtimeSyntaxDockerfileMicro, "runtime/syntax/LICENSE": runtimeSyntaxLicense, "runtime/syntax/README.md": runtimeSyntaxReadmeMd, @@ -2151,6 +2195,14 @@ var _bintree = &bintree{nil, map[string]*bintree{ "help": &bintree{nil, map[string]*bintree{ "help.md": &bintree{runtimeHelpHelpMd, map[string]*bintree{}}, }}, + "plugins": &bintree{nil, map[string]*bintree{ + "go": &bintree{nil, map[string]*bintree{ + "go.lua": &bintree{runtimePluginsGoGoLua, map[string]*bintree{}}, + }}, + "linter": &bintree{nil, map[string]*bintree{ + "linter.lua": &bintree{runtimePluginsLinterLinterLua, map[string]*bintree{}}, + }}, + }}, "syntax": &bintree{nil, map[string]*bintree{ "Dockerfile.micro": &bintree{runtimeSyntaxDockerfileMicro, map[string]*bintree{}}, "LICENSE": &bintree{runtimeSyntaxLicense, map[string]*bintree{}}, diff --git a/cmd/micro/search.go b/cmd/micro/search.go index 1ad9b069..4253a316 100644 --- a/cmd/micro/search.go +++ b/cmd/micro/search.go @@ -57,7 +57,7 @@ func HandleSearchEvent(event tcell.Event, v *View) { } if messenger.response == "" { - v.cursor.ResetSelection() + v.Cursor.ResetSelection() // We don't end the search though return } @@ -76,7 +76,7 @@ func Search(searchStr string, v *View, down bool) { } var str string var charPos int - text := v.buf.String() + text := v.Buf.String() if down { str = text[searchStart:] charPos = searchStart @@ -94,7 +94,7 @@ func Search(searchStr string, v *View, down bool) { matches = r.FindAllStringIndex(text, -1) charPos = 0 if matches == nil { - v.cursor.ResetSelection() + v.Cursor.ResetSelection() return } @@ -111,9 +111,9 @@ func Search(searchStr string, v *View, down bool) { match = matches[0] } - v.cursor.curSelection[0] = charPos + match[0] - v.cursor.curSelection[1] = charPos + match[1] - v.cursor.x, v.cursor.y = FromCharPos(charPos+match[1]-1, v.buf) + v.Cursor.curSelection[0] = charPos + match[0] + v.Cursor.curSelection[1] = charPos + match[1] + v.Cursor.x, v.Cursor.y = FromCharPos(charPos+match[1]-1, v.Buf) if v.Relocate() { v.matches = Match(v) } diff --git a/cmd/micro/settings.go b/cmd/micro/settings.go index 5eade696..b0818db8 100644 --- a/cmd/micro/settings.go +++ b/cmd/micro/settings.go @@ -55,6 +55,20 @@ func WriteSettings(filename string) error { return err } +// 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 + err := WriteSettings(configDir + "/settings.json") + if err != nil { + TermMessage("Error writing settings.json file: " + err.Error()) + } +} + +// GetOption returns the specified option. This is meant to be called by plugins to add options. +func GetOption(name string) interface{} { + return settings[name] +} + // DefaultSettings returns the default settings for micro func DefaultSettings() map[string]interface{} { return map[string]interface{}{ @@ -64,8 +78,6 @@ func DefaultSettings() map[string]interface{} { "syntax": true, "tabsToSpaces": false, "ruler": true, - "gofmt": false, - "goimports": false, } } @@ -102,7 +114,7 @@ func SetOption(view *View, args []string) { if option == "colorscheme" { LoadSyntaxFiles() - view.buf.UpdateRules() + view.Buf.UpdateRules() } err := WriteSettings(filename) diff --git a/cmd/micro/statusline.go b/cmd/micro/statusline.go index 34185ba6..b9cd0af5 100644 --- a/cmd/micro/statusline.go +++ b/cmd/micro/statusline.go @@ -17,14 +17,14 @@ func (sline *Statusline) Display() { // We'll draw the line at the lowest line in the view y := sline.view.height - file := sline.view.buf.name + 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.IsDirty() { + if sline.view.Buf.IsDirty() { file += " +" } @@ -32,13 +32,13 @@ func (sline *Statusline) Display() { // but users will be used to (1,1) (first line,first column) // We use GetVisualX() here because otherwise we get the column number in runes // so a '\t' is only 1, when it should be tabSize - columnNum := strconv.Itoa(sline.view.cursor.GetVisualX() + 1) - lineNum := strconv.Itoa(sline.view.cursor.y + 1) + columnNum := strconv.Itoa(sline.view.Cursor.GetVisualX() + 1) + lineNum := strconv.Itoa(sline.view.Cursor.y + 1) file += " (" + lineNum + "," + columnNum + ")" // Add the filetype - file += " " + sline.view.buf.filetype + file += " " + sline.view.Buf.Filetype rightText := "Ctrl-g for help " if helpOpen { diff --git a/cmd/micro/view.go b/cmd/micro/view.go index 175799f6..0fd7bd29 100644 --- a/cmd/micro/view.go +++ b/cmd/micro/view.go @@ -2,6 +2,8 @@ package main import ( "io/ioutil" + "reflect" + "runtime" "strconv" "strings" "time" @@ -13,10 +15,10 @@ import ( // It stores information about the cursor, and the viewport // that the user sees the buffer from. type View struct { - cursor Cursor + Cursor Cursor // The topmost line, used for vertical scrolling - topline int + Topline int // The leftmost column, used for horizontal scrolling leftCol int @@ -34,10 +36,11 @@ type View struct { // The eventhandler for undo/redo eh *EventHandler - messages []GutterMessage + // Holds the list of gutter messages + messages map[string][]GutterMessage // The buffer - buf *Buffer + Buf *Buffer // The statusline sline Statusline @@ -89,6 +92,8 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View { v.eh = NewEventHandler(v) + v.messages = make(map[string][]GutterMessage) + v.sline = Statusline{ view: v, } @@ -111,20 +116,20 @@ func (v *View) Resize(w, h int) { // ScrollUp scrolls the view up n lines (if possible) func (v *View) ScrollUp(n int) { // Try to scroll by n but if it would overflow, scroll by 1 - if v.topline-n >= 0 { - v.topline -= n - } else if v.topline > 0 { - v.topline-- + if v.Topline-n >= 0 { + v.Topline -= n + } else if v.Topline > 0 { + v.Topline-- } } // ScrollDown scrolls the view down n lines (if possible) func (v *View) ScrollDown(n int) { // Try to scroll by n but if it would overflow, scroll by 1 - if v.topline+n <= v.buf.numLines-v.height { - v.topline += n - } else if v.topline < v.buf.numLines-v.height { - v.topline++ + if v.Topline+n <= v.Buf.NumLines-v.height { + v.Topline += n + } else if v.Topline < v.Buf.NumLines-v.height { + v.Topline++ } } @@ -133,7 +138,7 @@ func (v *View) ScrollDown(n int) { // causing them to lose the unsaved changes // The message is what to print after saying "You have unsaved changes. " func (v *View) CanClose(msg string) bool { - if v.buf.IsDirty() { + if v.Buf.IsDirty() { quit, canceled := messenger.Prompt("You have unsaved changes. " + msg) if !canceled { if strings.ToLower(quit) == "yes" || strings.ToLower(quit) == "y" { @@ -152,16 +157,17 @@ func (v *View) CanClose(msg string) bool { // OpenBuffer opens a new buffer in this view. // This resets the topline, event handler and cursor. func (v *View) OpenBuffer(buf *Buffer) { - v.buf = buf - v.topline = 0 + v.Buf = buf + v.Topline = 0 v.leftCol = 0 // Put the cursor at the first spot - v.cursor = Cursor{ + v.Cursor = Cursor{ x: 0, y: 0, v: v, } - v.cursor.ResetSelection() + v.Cursor.ResetSelection() + v.messages = make(map[string][]GutterMessage) v.eh = NewEventHandler(v) v.matches = Match(v) @@ -172,20 +178,20 @@ func (v *View) OpenBuffer(buf *Buffer) { v.lastClickTime = time.Time{} } -// Close and Re-open the current file. -func (v *View) reOpen() { +// ReOpen reloads the current buffer +func (v *View) ReOpen() { if v.CanClose("Continue? (yes, no, save) ") { - file, err := ioutil.ReadFile(v.buf.path) - filename := v.buf.name + file, err := ioutil.ReadFile(v.Buf.Path) + filename := v.Buf.Name if err != nil { messenger.Error(err.Error()) return } buf := NewBuffer(string(file), filename) - v.buf = buf + v.Buf = buf v.matches = Match(v) - v.cursor.Relocate() + v.Cursor.Relocate() v.Relocate() } } @@ -194,17 +200,17 @@ func (v *View) reOpen() { // This is useful if the user has scrolled far away, and then starts typing func (v *View) Relocate() bool { ret := false - cy := v.cursor.y - if cy < v.topline { - v.topline = cy + cy := v.Cursor.y + if cy < v.Topline { + v.Topline = cy ret = true } - if cy > v.topline+v.height-1 { - v.topline = cy - v.height + 1 + if cy > v.Topline+v.height-1 { + v.Topline = cy - v.height + 1 ret = true } - cx := v.cursor.GetVisualX() + cx := v.Cursor.GetVisualX() if cx < v.leftCol { v.leftCol = cx ret = true @@ -219,12 +225,12 @@ func (v *View) Relocate() bool { // MoveToMouseClick moves the cursor to location x, y assuming x, y were given // by a mouse click func (v *View) MoveToMouseClick(x, y int) { - if y-v.topline > v.height-1 { + if y-v.Topline > v.height-1 { v.ScrollDown(1) - y = v.height + v.topline - 1 + y = v.height + v.Topline - 1 } - if y >= v.buf.numLines { - y = v.buf.numLines - 1 + if y >= v.Buf.NumLines { + y = v.Buf.NumLines - 1 } if y < 0 { y = 0 @@ -233,13 +239,13 @@ func (v *View) MoveToMouseClick(x, y int) { x = 0 } - x = v.cursor.GetCharPosInLine(y, x) - if x > Count(v.buf.lines[y]) { - x = Count(v.buf.lines[y]) + x = v.Cursor.GetCharPosInLine(y, x) + if x > Count(v.Buf.Lines[y]) { + x = Count(v.Buf.Lines[y]) } - v.cursor.x = x - v.cursor.y = y - v.cursor.lastVisualX = v.cursor.GetVisualX() + v.Cursor.x = x + v.Cursor.y = y + v.Cursor.lastVisualX = v.Cursor.GetVisualX() } // HandleEvent handles an event passed by the main loop @@ -255,32 +261,39 @@ func (v *View) HandleEvent(event tcell.Event) { case *tcell.EventKey: if e.Key() == tcell.KeyRune { // Insert a character - if v.cursor.HasSelection() { - v.cursor.DeleteSelection() - v.cursor.ResetSelection() + if v.Cursor.HasSelection() { + v.Cursor.DeleteSelection() + v.Cursor.ResetSelection() } - v.eh.Insert(v.cursor.Loc(), string(e.Rune())) - v.cursor.Right() + v.eh.Insert(v.Cursor.Loc(), string(e.Rune())) + v.Cursor.Right() } else { for key, action := range bindings { if e.Key() == key { relocate = action(v) + for _, pl := range loadedPlugins { + funcName := strings.Split(runtime.FuncForPC(reflect.ValueOf(action).Pointer()).Name(), ".") + err := Call(pl + "_on" + funcName[len(funcName)-1]) + if err != nil { + TermMessage(err) + } + } } } } case *tcell.EventPaste: - if v.cursor.HasSelection() { - v.cursor.DeleteSelection() - v.cursor.ResetSelection() + if v.Cursor.HasSelection() { + v.Cursor.DeleteSelection() + v.Cursor.ResetSelection() } clip := e.Text() - v.eh.Insert(v.cursor.Loc(), clip) - v.cursor.SetLoc(v.cursor.Loc() + Count(clip)) + v.eh.Insert(v.Cursor.Loc(), clip) + v.Cursor.SetLoc(v.Cursor.Loc() + Count(clip)) v.freshClip = false case *tcell.EventMouse: x, y := e.Position() x -= v.lineNumOffset - v.leftCol - y += v.topline + y += v.Topline button := e.Buttons() @@ -297,7 +310,7 @@ func (v *View) HandleEvent(event tcell.Event) { v.tripleClick = true v.doubleClick = false - v.cursor.SelectLine() + v.Cursor.SelectLine() } else { // Double click v.lastClickTime = time.Now() @@ -305,27 +318,27 @@ func (v *View) HandleEvent(event tcell.Event) { v.doubleClick = true v.tripleClick = false - v.cursor.SelectWord() + v.Cursor.SelectWord() } } else { v.doubleClick = false v.tripleClick = false v.lastClickTime = time.Now() - loc := v.cursor.Loc() - v.cursor.origSelection[0] = loc - v.cursor.curSelection[0] = loc - v.cursor.curSelection[1] = loc + loc := v.Cursor.Loc() + v.Cursor.origSelection[0] = loc + v.Cursor.curSelection[0] = loc + v.Cursor.curSelection[1] = loc } v.mouseReleased = false } else if !v.mouseReleased { v.MoveToMouseClick(x, y) if v.tripleClick { - v.cursor.AddLineToSelection() + v.Cursor.AddLineToSelection() } else if v.doubleClick { - v.cursor.AddWordToSelection() + v.Cursor.AddWordToSelection() } else { - v.cursor.curSelection[1] = v.cursor.Loc() + v.Cursor.curSelection[1] = v.Cursor.Loc() } } case tcell.ButtonNone: @@ -341,7 +354,7 @@ func (v *View) HandleEvent(event tcell.Event) { if !v.doubleClick && !v.tripleClick { v.MoveToMouseClick(x, y) - v.cursor.curSelection[1] = v.cursor.Loc() + v.Cursor.curSelection[1] = v.Cursor.Loc() } v.mouseReleased = true } @@ -370,28 +383,44 @@ func (v *View) HandleEvent(event tcell.Event) { } // GutterMessage creates a message in this view's gutter -func (v *View) GutterMessage(lineN int, msg string, kind int) { +func (v *View) GutterMessage(section string, lineN int, msg string, kind int) { + lineN-- gutterMsg := GutterMessage{ lineNum: lineN, msg: msg, kind: kind, } - for _, gmsg := range v.messages { - if gmsg.lineNum == lineN { - return + for _, v := range v.messages { + for _, gmsg := range v { + if gmsg.lineNum == lineN { + return + } } } - v.messages = append(v.messages, gutterMsg) + messages := v.messages[section] + v.messages[section] = append(messages, gutterMsg) +} + +// ClearGutterMessages clears all gutter messages from a given section +func (v *View) ClearGutterMessages(section string) { + v.messages[section] = []GutterMessage{} +} + +// ClearAllGutterMessages clears all the gutter messages +func (v *View) ClearAllGutterMessages() { + for k := range v.messages { + v.messages[k] = []GutterMessage{} + } } // 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 := ToCharPos(0, v.topline, v.buf) + charNum := ToCharPos(0, v.Topline, v.Buf) // Convert the length of buffer to a string, and get the length of the string // We are going to have to offset by that amount - maxLineLength := len(strconv.Itoa(v.buf.numLines)) + maxLineLength := len(strconv.Itoa(v.Buf.NumLines)) // + 1 for the little space after the line number if settings["ruler"] == true { v.lineNumOffset = maxLineLength + 1 @@ -400,7 +429,13 @@ func (v *View) DisplayView() { } var highlightStyle tcell.Style - if len(v.messages) > 0 { + var hasGutterMessages bool + for _, v := range v.messages { + if len(v) > 0 { + hasGutterMessages = true + } + } + if hasGutterMessages { v.lineNumOffset += 2 } @@ -408,38 +443,40 @@ func (v *View) DisplayView() { var x int // If the buffer is smaller than the view height // and we went too far, break - if lineN+v.topline >= v.buf.numLines { + if lineN+v.Topline >= v.Buf.NumLines { break } - line := v.buf.lines[lineN+v.topline] + line := v.Buf.Lines[lineN+v.Topline] - if len(v.messages) > 0 { + if hasGutterMessages { msgOnLine := false - for _, msg := range v.messages { - if msg.lineNum == lineN+v.topline { - msgOnLine = true - gutterStyle := tcell.StyleDefault - switch msg.kind { - case GutterInfo: - if style, ok := colorscheme["gutter-info"]; ok { - gutterStyle = style + for k := range v.messages { + for _, msg := range v.messages[k] { + if msg.lineNum == lineN+v.Topline { + msgOnLine = true + gutterStyle := tcell.StyleDefault + switch msg.kind { + case GutterInfo: + if style, ok := colorscheme["gutter-info"]; ok { + gutterStyle = style + } + case GutterWarning: + if style, ok := colorscheme["gutter-warning"]; ok { + gutterStyle = style + } + case GutterError: + if style, ok := colorscheme["gutter-error"]; ok { + gutterStyle = style + } } - case GutterWarning: - if style, ok := colorscheme["gutter-warning"]; ok { - gutterStyle = style + screen.SetContent(x, lineN, '>', nil, gutterStyle) + x++ + screen.SetContent(x, lineN, '>', nil, gutterStyle) + x++ + if v.Cursor.y == lineN+v.Topline { + messenger.Message(msg.msg) + messenger.gutterMessage = true } - case GutterError: - if style, ok := colorscheme["gutter-error"]; ok { - gutterStyle = style - } - } - screen.SetContent(x, lineN, '>', nil, gutterStyle) - x++ - screen.SetContent(x, lineN, '>', nil, gutterStyle) - x++ - if v.cursor.y == lineN { - messenger.Message(msg.msg) - messenger.gutterMessage = true } } } @@ -448,7 +485,7 @@ func (v *View) DisplayView() { x++ screen.SetContent(x, lineN, ' ', nil, tcell.StyleDefault) x++ - if v.cursor.y == lineN && messenger.gutterMessage { + if v.Cursor.y == lineN+v.Topline && messenger.gutterMessage { messenger.Reset() messenger.gutterMessage = false } @@ -463,7 +500,7 @@ func (v *View) DisplayView() { // Write the spaces before the line number if necessary var lineNum string if settings["ruler"] == true { - lineNum = strconv.Itoa(lineN + v.topline + 1) + lineNum = strconv.Itoa(lineN + v.Topline + 1) for i := 0; i < maxLineLength-len(lineNum); i++ { screen.SetContent(x, lineN, ' ', nil, lineNumStyle) x++ @@ -490,9 +527,9 @@ func (v *View) DisplayView() { highlightStyle = v.matches[lineN][colN] } - if v.cursor.HasSelection() && - (charNum >= v.cursor.curSelection[0] && charNum < v.cursor.curSelection[1] || - charNum < v.cursor.curSelection[0] && charNum >= v.cursor.curSelection[1]) { + if v.Cursor.HasSelection() && + (charNum >= v.Cursor.curSelection[0] && charNum < v.Cursor.curSelection[1] || + charNum < v.Cursor.curSelection[0] && charNum >= v.Cursor.curSelection[1]) { lineStyle = tcell.StyleDefault.Reverse(true) @@ -524,9 +561,9 @@ func (v *View) DisplayView() { // The newline may be selected, in which case we should draw the selection style // with a space to represent it - if v.cursor.HasSelection() && - (charNum >= v.cursor.curSelection[0] && charNum < v.cursor.curSelection[1] || - charNum < v.cursor.curSelection[0] && charNum >= v.cursor.curSelection[1]) { + if v.Cursor.HasSelection() && + (charNum >= v.Cursor.curSelection[0] && charNum < v.Cursor.curSelection[1] || + charNum < v.Cursor.curSelection[0] && charNum >= v.Cursor.curSelection[1]) { selectStyle := defStyle.Reverse(true) @@ -543,6 +580,6 @@ func (v *View) DisplayView() { // Display renders the view, the cursor, and statusline func (v *View) Display() { v.DisplayView() - v.cursor.Display() + v.Cursor.Display() v.sline.Display() } diff --git a/runtime/help/help.md b/runtime/help/help.md index 1832c8cc..c89c8e68 100644 --- a/runtime/help/help.md +++ b/runtime/help/help.md @@ -195,20 +195,24 @@ Here are the options that you can set: default value: `on` -* `gofmt`: Run `gofmt` whenever the file is saved (this only applies to `.go` - files) +--- + +Default plugin options: + +* `linter`: lint languages on save (supported languages are C, D, Go, Java, + Javascript, Lua). Provided by the `linter` plugin. + + default value: `on` + +* `goimports`: Run goimports on save. Provided by the `go` plugin. default value: `off` -* `goimports`: run `goimports` whenever the file is saved (this only applies - to `.go` files) +* `gofmt`: Run gofmt on save. Provided by the `go` plugin. - default value: `off` + default value: `on` Any option you set in the editor will be saved to the file ~/.config/micro/settings.json so, in effect, your configuration file will be created for you. If you'd like to take your configuration with you to another machine, simply copy the settings.json to the other machine. - -In the future, the `gofmt` and `goimports` will be refactored using a plugin -system. However, currently they make it easier to program micro in micro. diff --git a/runtime/plugins/go/go.lua b/runtime/plugins/go/go.lua new file mode 100644 index 00000000..a1cecddc --- /dev/null +++ b/runtime/plugins/go/go.lua @@ -0,0 +1,39 @@ +if GetOption("goimports") == nil then + AddOption("goimports", false) +end +if GetOption("gofmt") == nil then + AddOption("gofmt", true) +end + +function go_onSave() + if view.Buf.Filetype == "Go" then + if GetOption("goimports") then + go_goimports() + elseif GetOption("gofmt") then + go_gofmt() + end + + view:ReOpen() + end +end + +function go_gofmt() + local handle = io.popen("gofmt -w " .. view.Buf.Path) + local result = handle:read("*a") + handle:close() +end + +function go_goimports() + local handle = io.popen("goimports -w " .. view.Buf.Path) + local result = go_split(handle:read("*a"), ":") + handle:close() +end + +function go_split(str, sep) + local result = {} + local regex = ("([^%s]+)"):format(sep) + for each in str:gmatch(regex) do + table.insert(result, each) + end + return result +end diff --git a/runtime/plugins/linter/linter.lua b/runtime/plugins/linter/linter.lua new file mode 100644 index 00000000..f0c3e5f6 --- /dev/null +++ b/runtime/plugins/linter/linter.lua @@ -0,0 +1,66 @@ +if GetOption("linter") == nil then + AddOption("linter", true) +end + +function linter_onSave() + if GetOption("linter") then + local ft = view.Buf.Filetype + local file = view.Buf.Path + local devnull = "/dev/null" + if OS == "windows" then + devnull = "NUL" + end + if ft == "Go" then + linter_lint("gobuild", "go build -o " .. devnull, "%f:%l: %m") + linter_lint("golint", "golint " .. view.Buf.Path, "%f:%l:%d+: %m") + elseif ft == "Lua" then + linter_lint("luacheck", "luacheck --no-color " .. file, "%f:%l:%d+: %m") + elseif ft == "Python" then + linter_lint("pyflakes", "pyflakes " .. file, "%f:%l: %m") + elseif ft == "C" then + linter_lint("gcc", "gcc -fsyntax-only -Wall -Wextra " .. file, "%f:%l:%d+:.+: %m") + elseif ft == "D" then + linter_lint("dmd", "dmd -color=off -o- -w -wi -c " .. file, "%f%(%l%):.+: %m") + elseif ft == "Java" then + linter_lint("javac", "javac " .. file, "%f:%l: error: %m") + elseif ft == "JavaScript" then + linter_lint("jshint", "jshint " .. file, "%f: line %l,.+, %m") + end + else + view:ClearAllGutterMessages() + end +end + +function linter_lint(linter, cmd, errorformat) + view:ClearGutterMessages(linter) + + local handle = io.popen("(" .. cmd .. ")" .. " 2>&1") + local lines = linter_split(handle:read("*a"), "\n") + handle:close() + + local regex = errorformat:gsub("%%f", "(.+)"):gsub("%%l", "(%d+)"):gsub("%%m", "(.+)") + for _,line in ipairs(lines) do + -- Trim whitespace + line = line:match("^%s*(.+)%s*$") + if string.find(line, regex) then + local file, line, msg = string.match(line, regex) + if linter_basename(view.Buf.Path) == linter_basename(file) then + view:GutterMessage(linter, tonumber(line), msg, 2) + end + end + end +end + +function linter_split(str, sep) + local result = {} + local regex = ("([^%s]+)"):format(sep) + for each in str:gmatch(regex) do + table.insert(result, each) + end + return result +end + +function linter_basename(file) + local name = string.gsub(file, "(.*/)(.*)", "%2") + return name +end