Add some more comments

This commit is contained in:
Zachary Yedidia 2016-07-10 13:26:05 -04:00
parent 6489f4b6e8
commit 8c0983a36c
7 changed files with 105 additions and 19 deletions

View file

@ -18,6 +18,7 @@ import (
type Buffer struct {
// The eventhandler for undo/redo
*EventHandler
// This stores all the text in the buffer as an array of lines
*LineArray
Cursor Cursor
@ -27,6 +28,7 @@ type Buffer struct {
// Name of the buffer on the status line
Name string
// Whether or not the buffer has been modified since it was opened
IsModified bool
// Stores the last modification time of the file the buffer is pointing to
@ -41,6 +43,7 @@ type Buffer struct {
}
// The SerializedBuffer holds the types that get serialized when a buffer is saved
// These are used for the savecursor and saveundo options
type SerializedBuffer struct {
EventHandler *EventHandler
Cursor Cursor
@ -55,10 +58,12 @@ func NewBuffer(txt []byte, path string) *Buffer {
b.Path = path
b.Name = path
// If the file doesn't have a path to disk then we give it no name
if path == "" {
b.Name = "No name"
}
// The last time this file was modified
b.ModTime, _ = GetModTime(b.Path)
b.EventHandler = NewEventHandler(b)
@ -80,6 +85,8 @@ func NewBuffer(txt []byte, path string) *Buffer {
}
if settings["savecursor"].(bool) || 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)
file, err := os.Open(configDir + "/buffers/" + EscapePath(absPath))
if err == nil {

View file

@ -7,12 +7,24 @@ import (
"strings"
)
// Jobs are the way plugins can run processes in the background
// A job is simply a process that gets executed asynchronously
// There are callbacks for when the job exits, when the job creates stdout
// and when the job creates stderr
// These jobs run in a separate goroutine but the lua callbacks need to be
// executed in the main thread (where the Lua VM is running) so they are
// put into the jobs channel which gets read by the main loop
// JobFunction is a representation of a job (this data structure is what is loaded
// into the jobs channel)
type JobFunction struct {
function func(string, ...string)
output string
args []string
}
// A CallbackFile is the data structure that makes it possible to catch stderr and stdout write events
type CallbackFile struct {
io.Writer
@ -21,16 +33,21 @@ type CallbackFile struct {
}
func (f *CallbackFile) Write(data []byte) (int, error) {
// This is either stderr or stdout
// In either case we create a new job function callback and put it in the jobs channel
jobFunc := JobFunction{f.callback, string(data), f.args}
jobs <- jobFunc
return f.Writer.Write(data)
}
// JobStart starts a process in the background with the given callbacks
// It returns an *exec.Cmd as the job id
func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string) *exec.Cmd {
split := strings.Split(cmd, " ")
args := split[1:]
cmdName := split[0]
// Set up everything correctly if the functions have been provided
proc := exec.Command(cmdName, args...)
var outbuf bytes.Buffer
if onStdout != "" {
@ -45,6 +62,7 @@ func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string)
}
go func() {
// Run the process in the background and create the onExit callback
proc.Run()
jobFunc := JobFunction{LuaFunctionJob(onExit), string(outbuf.Bytes()), userargs}
jobs <- jobFunc
@ -53,10 +71,12 @@ func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string)
return proc
}
// JobStop kills a job
func JobStop(cmd *exec.Cmd) {
cmd.Process.Kill()
}
// JobSend sends the given data into the job's stdin stream
func JobSend(cmd *exec.Cmd, data string) {
stdin, err := cmd.StdinPipe()
if err != nil {

View file

@ -26,12 +26,16 @@ func runeToByteIndex(n int, txt []byte) int {
return count
}
// A LineArray simply stores and array of lines and makes it easy to insert
// and delete in it
type LineArray struct {
lines [][]byte
}
// NewLineArray returns a new line array from an array of bytes
func NewLineArray(text []byte) *LineArray {
la := new(LineArray)
// Split the bytes into lines
split := bytes.Split(text, []byte("\n"))
la.lines = make([][]byte, len(split))
for i := range split {
@ -42,16 +46,19 @@ func NewLineArray(text []byte) *LineArray {
return la
}
// Returns the String representation of the LineArray
func (la *LineArray) String() string {
return string(bytes.Join(la.lines, []byte("\n")))
}
// NewlineBelow adds a newline below the given line number
func (la *LineArray) NewlineBelow(y int) {
la.lines = append(la.lines, []byte(" "))
copy(la.lines[y+2:], la.lines[y+1:])
la.lines[y+1] = []byte("")
}
// inserts a byte array at a given location
func (la *LineArray) insert(pos Loc, value []byte) {
x, y := runeToByteIndex(pos.X, la.lines[pos.Y]), pos.Y
// x, y := pos.x, pos.y
@ -67,23 +74,27 @@ func (la *LineArray) insert(pos Loc, value []byte) {
}
}
// inserts a byte at a given location
func (la *LineArray) insertByte(pos Loc, value byte) {
la.lines[pos.Y] = append(la.lines[pos.Y], 0)
copy(la.lines[pos.Y][pos.X+1:], la.lines[pos.Y][pos.X:])
la.lines[pos.Y][pos.X] = value
}
// JoinLines joins the two lines a and b
func (la *LineArray) JoinLines(a, b int) {
la.insert(Loc{len(la.lines[a]), a}, la.lines[b])
la.DeleteLine(b)
}
// Split splits a line at a given position
func (la *LineArray) Split(pos Loc) {
la.NewlineBelow(pos.Y)
la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y][pos.X:])
la.DeleteToEnd(Loc{pos.X, pos.Y})
}
// removes from start to end
func (la *LineArray) remove(start, end Loc) string {
sub := la.Substr(start, end)
startX := runeToByteIndex(start.X, la.lines[start.Y])
@ -101,22 +112,27 @@ func (la *LineArray) remove(start, end Loc) string {
return sub
}
// DeleteToEnd deletes from the end of a line to the position
func (la *LineArray) DeleteToEnd(pos Loc) {
la.lines[pos.Y] = la.lines[pos.Y][:pos.X]
}
// DeleteFromStart deletes from the start of a line to the position
func (la *LineArray) DeleteFromStart(pos Loc) {
la.lines[pos.Y] = la.lines[pos.Y][pos.X+1:]
}
// DeleteLine deletes the line number
func (la *LineArray) DeleteLine(y int) {
la.lines = la.lines[:y+copy(la.lines[y:], la.lines[y+1:])]
}
// DeleteByte deletes the byte at a position
func (la *LineArray) DeleteByte(pos Loc) {
la.lines[pos.Y] = la.lines[pos.Y][:pos.X+copy(la.lines[pos.Y][pos.X:], la.lines[pos.Y][pos.X+1:])]
}
// Substr returns the string representation between two locations
func (la *LineArray) Substr(start, end Loc) string {
startX := runeToByteIndex(start.X, la.lines[start.Y])
endX := runeToByteIndex(end.X, la.lines[end.Y])

View file

@ -83,6 +83,7 @@ func (l Loc) LessEqual(b Loc) bool {
return false
}
// This moves the location one character to the right
func (l Loc) right(buf *Buffer) Loc {
if l == buf.End() {
return Loc{l.X + 1, l.Y}
@ -95,6 +96,8 @@ func (l Loc) right(buf *Buffer) Loc {
}
return res
}
// This moves the given location one character to the left
func (l Loc) left(buf *Buffer) Loc {
if l == buf.Start() {
return Loc{l.X - 1, l.Y}
@ -108,6 +111,8 @@ func (l Loc) left(buf *Buffer) Loc {
return res
}
// Move moves the cursor n characters to the left or right
// It moves the cursor left if n is negative
func (l Loc) Move(n int, buf *Buffer) Loc {
if n > 0 {
for i := 0; i < n; i++ {
@ -120,7 +125,3 @@ func (l Loc) Move(n int, buf *Buffer) Loc {
}
return l
}
// func (l Loc) DistanceTo(b Loc, buf *Buffer) int {
//
// }

View file

@ -17,13 +17,6 @@ import (
"github.com/zyedidia/tcell/encoding"
)
const (
synLinesUp = 75 // How many lines up to look to do syntax highlighting
synLinesDown = 75 // How many lines down to look to do syntax highlighting
doubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click
undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
)
var (
// The main screen
screen tcell.Screen
@ -41,7 +34,7 @@ var (
configDir string
// Version is the version number or commit hash
// This should be set by the linker
// This should be set by the linker when compiling
Version = "Unknown"
// L is the lua state
@ -54,15 +47,18 @@ var (
// It's just an index to the tab in the tabs array
curTab int
jobs chan JobFunction
// Channel of jobs running in the background
jobs chan JobFunction
// Event channel
events chan tcell.Event
)
// LoadInput loads the file input for the editor
// LoadInput determines which files should be loaded into buffers
// based on the input stored in os.Args
func LoadInput() []*Buffer {
// There are a number of ways micro should start given its input
// 1. If it is given a file in os.Args, it should open that
// 1. If it is given a files in os.Args, it should open those
// 2. If there is no input file and the input is not a terminal, that means
// something is being piped in and the stdin should be opened in an
@ -71,8 +67,6 @@ func LoadInput() []*Buffer {
// 3. If there is no input file and the input is a terminal, an empty buffer
// should be opened
// These are empty by default so if we get to option 3, we can just returns the
// default values
var filename string
var input []byte
var err error
@ -80,16 +74,19 @@ func LoadInput() []*Buffer {
if len(os.Args) > 1 {
// Option 1
// We go through each file and load it
for i := 1; i < len(os.Args); i++ {
filename = os.Args[i]
// Check that the file exists
if _, e := os.Stat(filename); e == nil {
// If it exists we load it into a buffer
input, err = ioutil.ReadFile(filename)
if err != nil {
TermMessage(err)
continue
}
}
// If the file didn't exist, input will be empty, and we'll open an empty buffer
buffers = append(buffers, NewBuffer(input, filename))
}
} else if !isatty.IsTerminal(os.Stdin.Fd()) {
@ -182,15 +179,18 @@ func RedrawAll() {
screen.Show()
}
var flagVersion = flag.Bool("version", false, "Show version number")
// Passing -version as a flag will have micro print out the version number
var flagVersion = flag.Bool("version", false, "Show the version number")
func main() {
flag.Parse()
if *flagVersion {
// If -version was passed
fmt.Println("Micro version:", Version)
os.Exit(0)
}
// Start the Lua VM for running plugins
L = lua.NewState()
defer L.Close()
@ -200,15 +200,19 @@ func main() {
// Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
InitConfigDir()
// Load the user's settings
InitSettings()
InitCommands()
InitBindings()
// Load the syntax files, including the colorscheme
LoadSyntaxFiles()
// Load the help files
LoadHelp()
// Start the screen
InitScreen()
// This is just so if we have an error, we can exit cleanly and not completely
@ -224,11 +228,15 @@ func main() {
}
}()
// Create a new messenger
// This is used for sending the user messages in the bottom of the editor
messenger = new(Messenger)
messenger.history = make(map[string][]string)
// Now we load the input
buffers := LoadInput()
for _, buf := range buffers {
// For each buffer we create a new tab and place the view in that tab
tab := NewTabFromView(NewView(buf))
tab.SetNum(len(tabs))
tabs = append(tabs, tab)
@ -239,6 +247,8 @@ func main() {
}
}
// Load all the plugin stuff
// We give plugins access to a bunch of variables here which could be useful to them
L.SetGlobal("OS", luar.New(L, runtime.GOOS))
L.SetGlobal("tabs", luar.New(L, tabs))
L.SetGlobal("curTab", luar.New(L, curTab))
@ -250,6 +260,7 @@ func main() {
L.SetGlobal("CurView", luar.New(L, CurView))
L.SetGlobal("IsWordChar", luar.New(L, IsWordChar))
// Used for asynchronous jobs
L.SetGlobal("JobStart", luar.New(L, JobStart))
L.SetGlobal("JobSend", luar.New(L, JobSend))
L.SetGlobal("JobStop", luar.New(L, JobStop))
@ -259,6 +270,7 @@ func main() {
jobs = make(chan JobFunction, 100)
events = make(chan tcell.Event)
// Here is the event loop which runs in a separate thread
go func() {
for {
events <- screen.PollEvent()
@ -270,8 +282,11 @@ func main() {
RedrawAll()
var event tcell.Event
// Check for new events
select {
case f := <-jobs:
// If a new job has finished while running in the background we should execute the callback
f.function(f.output, f.args...)
continue
case event = <-events:
@ -280,13 +295,20 @@ func main() {
switch e := event.(type) {
case *tcell.EventMouse:
if e.Buttons() == tcell.Button1 {
// If the user left clicked we check a couple things
_, h := screen.Size()
x, y := e.Position()
if y == h-1 && messenger.message != "" {
// If the user clicked in the bottom bar, and there is a message down there
// we copy it to the clipboard.
// Often error messages are displayed down there so it can be useful to easily
// copy the message
clipboard.WriteAll(messenger.message)
continue
}
// We loop through each view in the current tab and make sure the current view
// it the one being clicked in
for _, v := range tabs[curTab].views {
if x >= v.x && x < v.x+v.width && y >= v.y && y < v.y+v.height {
tabs[curTab].curView = v.Num
@ -295,13 +317,16 @@ func main() {
}
}
// This function checks the mouse event for the possibility of changing the current tab
// If the tab was changed it returns true
if TabbarHandleMouseEvent(event) {
continue
}
if searching {
// Since searching is done in real time, we need to redraw every time
// there is a new event in the search bar
// there is a new event in the search bar so we need a special function
// to run instead of the standard HandleEvent.
HandleSearchEvent(event, CurView())
} else {
// Send it to the view

View file

@ -17,6 +17,7 @@ type Tab struct {
name string
}
// NewTabFromView creates a new tab and puts the given view in the tab
func NewTabFromView(v *View) *Tab {
t := new(Tab)
t.views = append(t.views, v)
@ -24,6 +25,7 @@ func NewTabFromView(v *View) *Tab {
return t
}
// SetNum sets all this tab's views to have the correct tab number
func (t *Tab) SetNum(num int) {
for _, v := range t.views {
v.TabNum = num
@ -36,6 +38,10 @@ func CurView() *View {
return curTab.views[curTab.curView]
}
// TabbarString returns the string that should be displayed in the tabbar
// It also returns a map containing which indicies correspond to which tab number
// This is useful when we know that the mouse click has occured at an x location
// but need to know which tab that corresponds to to accurately change the tab
func TabbarString() (string, map[int]int) {
str := ""
indicies := make(map[int]int)
@ -57,7 +63,11 @@ func TabbarString() (string, map[int]int) {
return str, indicies
}
// TabbarHandleMouseEvent checks the given mouse event if it is clicking on the tabbar
// If it is it changes the current tab accordingly
// This function returns true if the tab is changed
func TabbarHandleMouseEvent(event tcell.Event) bool {
// There is no tabbar displayed if there are less than 2 tabs
if len(tabs) <= 1 {
return false
}
@ -65,6 +75,7 @@ func TabbarHandleMouseEvent(event tcell.Event) bool {
switch e := event.(type) {
case *tcell.EventMouse:
button := e.Buttons()
// Must be a left click
if button == tcell.Button1 {
x, y := e.Position()
if y != 0 {
@ -94,6 +105,7 @@ func TabbarHandleMouseEvent(event tcell.Event) bool {
return false
}
// DisplayTabs displays the tabbar at the top of the editor if there are multiple tabs
func DisplayTabs() {
if len(tabs) <= 1 {
return

View file

@ -140,12 +140,15 @@ func GetModTime(path string) (time.Time, bool) {
return info.ModTime(), true
}
// StringWidth returns the width of a string where tabs count as `tabsize` width
func StringWidth(str string) int {
sw := runewidth.StringWidth(str)
sw += NumOccurences(str, '\t') * (int(settings["tabsize"].(float64)) - 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 {
count := 0
for _, ch := range str {
@ -162,6 +165,8 @@ func WidthOfLargeRunes(str string) int {
return count
}
// RunePos returns the rune index of a given byte index
// This could cause problems if the byte index is between code points
func runePos(p int, str string) int {
return utf8.RuneCountInString(str[:p])
}