mirror of
https://github.com/zyedidia/micro
synced 2024-09-30 13:14:00 +00:00
Add some more comments
This commit is contained in:
parent
6489f4b6e8
commit
8c0983a36c
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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 {
|
||||
//
|
||||
// }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue