mirror of
https://github.com/zyedidia/micro.git
synced 2026-02-05 06:30:28 +09:00
Add some more comments
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
|||||||
type Buffer struct {
|
type Buffer struct {
|
||||||
// The eventhandler for undo/redo
|
// The eventhandler for undo/redo
|
||||||
*EventHandler
|
*EventHandler
|
||||||
|
// This stores all the text in the buffer as an array of lines
|
||||||
*LineArray
|
*LineArray
|
||||||
|
|
||||||
Cursor Cursor
|
Cursor Cursor
|
||||||
@@ -27,6 +28,7 @@ type Buffer struct {
|
|||||||
// Name of the buffer on the status line
|
// Name of the buffer on the status line
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
|
// Whether or not the buffer has been modified since it was opened
|
||||||
IsModified bool
|
IsModified bool
|
||||||
|
|
||||||
// Stores the last modification time of the file the buffer is pointing to
|
// 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
|
// 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 {
|
type SerializedBuffer struct {
|
||||||
EventHandler *EventHandler
|
EventHandler *EventHandler
|
||||||
Cursor Cursor
|
Cursor Cursor
|
||||||
@@ -55,10 +58,12 @@ func NewBuffer(txt []byte, path string) *Buffer {
|
|||||||
b.Path = path
|
b.Path = path
|
||||||
b.Name = path
|
b.Name = path
|
||||||
|
|
||||||
|
// If the file doesn't have a path to disk then we give it no name
|
||||||
if path == "" {
|
if path == "" {
|
||||||
b.Name = "No name"
|
b.Name = "No name"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The last time this file was modified
|
||||||
b.ModTime, _ = GetModTime(b.Path)
|
b.ModTime, _ = GetModTime(b.Path)
|
||||||
|
|
||||||
b.EventHandler = NewEventHandler(b)
|
b.EventHandler = NewEventHandler(b)
|
||||||
@@ -80,6 +85,8 @@ func NewBuffer(txt []byte, path string) *Buffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if settings["savecursor"].(bool) || settings["saveundo"].(bool) {
|
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)
|
absPath, _ := filepath.Abs(b.Path)
|
||||||
file, err := os.Open(configDir + "/buffers/" + EscapePath(absPath))
|
file, err := os.Open(configDir + "/buffers/" + EscapePath(absPath))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -7,12 +7,24 @@ import (
|
|||||||
"strings"
|
"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 {
|
type JobFunction struct {
|
||||||
function func(string, ...string)
|
function func(string, ...string)
|
||||||
output string
|
output string
|
||||||
args []string
|
args []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A CallbackFile is the data structure that makes it possible to catch stderr and stdout write events
|
||||||
type CallbackFile struct {
|
type CallbackFile struct {
|
||||||
io.Writer
|
io.Writer
|
||||||
|
|
||||||
@@ -21,16 +33,21 @@ type CallbackFile struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *CallbackFile) Write(data []byte) (int, error) {
|
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}
|
jobFunc := JobFunction{f.callback, string(data), f.args}
|
||||||
jobs <- jobFunc
|
jobs <- jobFunc
|
||||||
return f.Writer.Write(data)
|
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 {
|
func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string) *exec.Cmd {
|
||||||
split := strings.Split(cmd, " ")
|
split := strings.Split(cmd, " ")
|
||||||
args := split[1:]
|
args := split[1:]
|
||||||
cmdName := split[0]
|
cmdName := split[0]
|
||||||
|
|
||||||
|
// Set up everything correctly if the functions have been provided
|
||||||
proc := exec.Command(cmdName, args...)
|
proc := exec.Command(cmdName, args...)
|
||||||
var outbuf bytes.Buffer
|
var outbuf bytes.Buffer
|
||||||
if onStdout != "" {
|
if onStdout != "" {
|
||||||
@@ -45,6 +62,7 @@ func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
// Run the process in the background and create the onExit callback
|
||||||
proc.Run()
|
proc.Run()
|
||||||
jobFunc := JobFunction{LuaFunctionJob(onExit), string(outbuf.Bytes()), userargs}
|
jobFunc := JobFunction{LuaFunctionJob(onExit), string(outbuf.Bytes()), userargs}
|
||||||
jobs <- jobFunc
|
jobs <- jobFunc
|
||||||
@@ -53,10 +71,12 @@ func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string)
|
|||||||
return proc
|
return proc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JobStop kills a job
|
||||||
func JobStop(cmd *exec.Cmd) {
|
func JobStop(cmd *exec.Cmd) {
|
||||||
cmd.Process.Kill()
|
cmd.Process.Kill()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JobSend sends the given data into the job's stdin stream
|
||||||
func JobSend(cmd *exec.Cmd, data string) {
|
func JobSend(cmd *exec.Cmd, data string) {
|
||||||
stdin, err := cmd.StdinPipe()
|
stdin, err := cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -26,12 +26,16 @@ func runeToByteIndex(n int, txt []byte) int {
|
|||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A LineArray simply stores and array of lines and makes it easy to insert
|
||||||
|
// and delete in it
|
||||||
type LineArray struct {
|
type LineArray struct {
|
||||||
lines [][]byte
|
lines [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewLineArray returns a new line array from an array of bytes
|
||||||
func NewLineArray(text []byte) *LineArray {
|
func NewLineArray(text []byte) *LineArray {
|
||||||
la := new(LineArray)
|
la := new(LineArray)
|
||||||
|
// Split the bytes into lines
|
||||||
split := bytes.Split(text, []byte("\n"))
|
split := bytes.Split(text, []byte("\n"))
|
||||||
la.lines = make([][]byte, len(split))
|
la.lines = make([][]byte, len(split))
|
||||||
for i := range split {
|
for i := range split {
|
||||||
@@ -42,16 +46,19 @@ func NewLineArray(text []byte) *LineArray {
|
|||||||
return la
|
return la
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the String representation of the LineArray
|
||||||
func (la *LineArray) String() string {
|
func (la *LineArray) String() string {
|
||||||
return string(bytes.Join(la.lines, []byte("\n")))
|
return string(bytes.Join(la.lines, []byte("\n")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewlineBelow adds a newline below the given line number
|
||||||
func (la *LineArray) NewlineBelow(y int) {
|
func (la *LineArray) NewlineBelow(y int) {
|
||||||
la.lines = append(la.lines, []byte(" "))
|
la.lines = append(la.lines, []byte(" "))
|
||||||
copy(la.lines[y+2:], la.lines[y+1:])
|
copy(la.lines[y+2:], la.lines[y+1:])
|
||||||
la.lines[y+1] = []byte("")
|
la.lines[y+1] = []byte("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inserts a byte array at a given location
|
||||||
func (la *LineArray) insert(pos Loc, value []byte) {
|
func (la *LineArray) insert(pos Loc, value []byte) {
|
||||||
x, y := runeToByteIndex(pos.X, la.lines[pos.Y]), pos.Y
|
x, y := runeToByteIndex(pos.X, la.lines[pos.Y]), pos.Y
|
||||||
// x, y := pos.x, 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) {
|
func (la *LineArray) insertByte(pos Loc, value byte) {
|
||||||
la.lines[pos.Y] = append(la.lines[pos.Y], 0)
|
la.lines[pos.Y] = append(la.lines[pos.Y], 0)
|
||||||
copy(la.lines[pos.Y][pos.X+1:], la.lines[pos.Y][pos.X:])
|
copy(la.lines[pos.Y][pos.X+1:], la.lines[pos.Y][pos.X:])
|
||||||
la.lines[pos.Y][pos.X] = value
|
la.lines[pos.Y][pos.X] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JoinLines joins the two lines a and b
|
||||||
func (la *LineArray) JoinLines(a, b int) {
|
func (la *LineArray) JoinLines(a, b int) {
|
||||||
la.insert(Loc{len(la.lines[a]), a}, la.lines[b])
|
la.insert(Loc{len(la.lines[a]), a}, la.lines[b])
|
||||||
la.DeleteLine(b)
|
la.DeleteLine(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split splits a line at a given position
|
||||||
func (la *LineArray) Split(pos Loc) {
|
func (la *LineArray) Split(pos Loc) {
|
||||||
la.NewlineBelow(pos.Y)
|
la.NewlineBelow(pos.Y)
|
||||||
la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y][pos.X:])
|
la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y][pos.X:])
|
||||||
la.DeleteToEnd(Loc{pos.X, pos.Y})
|
la.DeleteToEnd(Loc{pos.X, pos.Y})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// removes from start to end
|
||||||
func (la *LineArray) remove(start, end Loc) string {
|
func (la *LineArray) remove(start, end Loc) string {
|
||||||
sub := la.Substr(start, end)
|
sub := la.Substr(start, end)
|
||||||
startX := runeToByteIndex(start.X, la.lines[start.Y])
|
startX := runeToByteIndex(start.X, la.lines[start.Y])
|
||||||
@@ -101,22 +112,27 @@ func (la *LineArray) remove(start, end Loc) string {
|
|||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteToEnd deletes from the end of a line to the position
|
||||||
func (la *LineArray) DeleteToEnd(pos Loc) {
|
func (la *LineArray) DeleteToEnd(pos Loc) {
|
||||||
la.lines[pos.Y] = la.lines[pos.Y][:pos.X]
|
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) {
|
func (la *LineArray) DeleteFromStart(pos Loc) {
|
||||||
la.lines[pos.Y] = la.lines[pos.Y][pos.X+1:]
|
la.lines[pos.Y] = la.lines[pos.Y][pos.X+1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteLine deletes the line number
|
||||||
func (la *LineArray) DeleteLine(y int) {
|
func (la *LineArray) DeleteLine(y int) {
|
||||||
la.lines = la.lines[:y+copy(la.lines[y:], la.lines[y+1:])]
|
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) {
|
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:])]
|
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 {
|
func (la *LineArray) Substr(start, end Loc) string {
|
||||||
startX := runeToByteIndex(start.X, la.lines[start.Y])
|
startX := runeToByteIndex(start.X, la.lines[start.Y])
|
||||||
endX := runeToByteIndex(end.X, la.lines[end.Y])
|
endX := runeToByteIndex(end.X, la.lines[end.Y])
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ func (l Loc) LessEqual(b Loc) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This moves the location one character to the right
|
||||||
func (l Loc) right(buf *Buffer) Loc {
|
func (l Loc) right(buf *Buffer) Loc {
|
||||||
if l == buf.End() {
|
if l == buf.End() {
|
||||||
return Loc{l.X + 1, l.Y}
|
return Loc{l.X + 1, l.Y}
|
||||||
@@ -95,6 +96,8 @@ func (l Loc) right(buf *Buffer) Loc {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This moves the given location one character to the left
|
||||||
func (l Loc) left(buf *Buffer) Loc {
|
func (l Loc) left(buf *Buffer) Loc {
|
||||||
if l == buf.Start() {
|
if l == buf.Start() {
|
||||||
return Loc{l.X - 1, l.Y}
|
return Loc{l.X - 1, l.Y}
|
||||||
@@ -108,6 +111,8 @@ func (l Loc) left(buf *Buffer) Loc {
|
|||||||
return res
|
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 {
|
func (l Loc) Move(n int, buf *Buffer) Loc {
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
@@ -120,7 +125,3 @@ func (l Loc) Move(n int, buf *Buffer) Loc {
|
|||||||
}
|
}
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (l Loc) DistanceTo(b Loc, buf *Buffer) int {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -17,13 +17,6 @@ import (
|
|||||||
"github.com/zyedidia/tcell/encoding"
|
"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 (
|
var (
|
||||||
// The main screen
|
// The main screen
|
||||||
screen tcell.Screen
|
screen tcell.Screen
|
||||||
@@ -41,7 +34,7 @@ var (
|
|||||||
configDir string
|
configDir string
|
||||||
|
|
||||||
// Version is the version number or commit hash
|
// 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"
|
Version = "Unknown"
|
||||||
|
|
||||||
// L is the lua state
|
// L is the lua state
|
||||||
@@ -54,15 +47,18 @@ var (
|
|||||||
// It's just an index to the tab in the tabs array
|
// It's just an index to the tab in the tabs array
|
||||||
curTab int
|
curTab int
|
||||||
|
|
||||||
jobs chan JobFunction
|
// Channel of jobs running in the background
|
||||||
|
jobs chan JobFunction
|
||||||
|
// Event channel
|
||||||
events chan tcell.Event
|
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 {
|
func LoadInput() []*Buffer {
|
||||||
// There are a number of ways micro should start given its input
|
// 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
|
// 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
|
// 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
|
// 3. If there is no input file and the input is a terminal, an empty buffer
|
||||||
// should be opened
|
// 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 filename string
|
||||||
var input []byte
|
var input []byte
|
||||||
var err error
|
var err error
|
||||||
@@ -80,16 +74,19 @@ func LoadInput() []*Buffer {
|
|||||||
|
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
// Option 1
|
// Option 1
|
||||||
|
// We go through each file and load it
|
||||||
for i := 1; i < len(os.Args); i++ {
|
for i := 1; i < len(os.Args); i++ {
|
||||||
filename = os.Args[i]
|
filename = os.Args[i]
|
||||||
// Check that the file exists
|
// Check that the file exists
|
||||||
if _, e := os.Stat(filename); e == nil {
|
if _, e := os.Stat(filename); e == nil {
|
||||||
|
// If it exists we load it into a buffer
|
||||||
input, err = ioutil.ReadFile(filename)
|
input, err = ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
TermMessage(err)
|
TermMessage(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If the file didn't exist, input will be empty, and we'll open an empty buffer
|
||||||
buffers = append(buffers, NewBuffer(input, filename))
|
buffers = append(buffers, NewBuffer(input, filename))
|
||||||
}
|
}
|
||||||
} else if !isatty.IsTerminal(os.Stdin.Fd()) {
|
} else if !isatty.IsTerminal(os.Stdin.Fd()) {
|
||||||
@@ -182,15 +179,18 @@ func RedrawAll() {
|
|||||||
screen.Show()
|
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() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *flagVersion {
|
if *flagVersion {
|
||||||
|
// If -version was passed
|
||||||
fmt.Println("Micro version:", Version)
|
fmt.Println("Micro version:", Version)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start the Lua VM for running plugins
|
||||||
L = lua.NewState()
|
L = lua.NewState()
|
||||||
defer L.Close()
|
defer L.Close()
|
||||||
|
|
||||||
@@ -200,15 +200,19 @@ func main() {
|
|||||||
|
|
||||||
// Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
|
// Find the user's configuration directory (probably $XDG_CONFIG_HOME/micro)
|
||||||
InitConfigDir()
|
InitConfigDir()
|
||||||
|
|
||||||
// Load the user's settings
|
// Load the user's settings
|
||||||
InitSettings()
|
InitSettings()
|
||||||
InitCommands()
|
InitCommands()
|
||||||
InitBindings()
|
InitBindings()
|
||||||
|
|
||||||
// Load the syntax files, including the colorscheme
|
// Load the syntax files, including the colorscheme
|
||||||
LoadSyntaxFiles()
|
LoadSyntaxFiles()
|
||||||
|
|
||||||
// Load the help files
|
// Load the help files
|
||||||
LoadHelp()
|
LoadHelp()
|
||||||
|
|
||||||
|
// Start the screen
|
||||||
InitScreen()
|
InitScreen()
|
||||||
|
|
||||||
// This is just so if we have an error, we can exit cleanly and not completely
|
// 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 = new(Messenger)
|
||||||
messenger.history = make(map[string][]string)
|
messenger.history = make(map[string][]string)
|
||||||
|
|
||||||
|
// Now we load the input
|
||||||
buffers := LoadInput()
|
buffers := LoadInput()
|
||||||
for _, buf := range buffers {
|
for _, buf := range buffers {
|
||||||
|
// For each buffer we create a new tab and place the view in that tab
|
||||||
tab := NewTabFromView(NewView(buf))
|
tab := NewTabFromView(NewView(buf))
|
||||||
tab.SetNum(len(tabs))
|
tab.SetNum(len(tabs))
|
||||||
tabs = append(tabs, tab)
|
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("OS", luar.New(L, runtime.GOOS))
|
||||||
L.SetGlobal("tabs", luar.New(L, tabs))
|
L.SetGlobal("tabs", luar.New(L, tabs))
|
||||||
L.SetGlobal("curTab", luar.New(L, curTab))
|
L.SetGlobal("curTab", luar.New(L, curTab))
|
||||||
@@ -250,6 +260,7 @@ func main() {
|
|||||||
L.SetGlobal("CurView", luar.New(L, CurView))
|
L.SetGlobal("CurView", luar.New(L, CurView))
|
||||||
L.SetGlobal("IsWordChar", luar.New(L, IsWordChar))
|
L.SetGlobal("IsWordChar", luar.New(L, IsWordChar))
|
||||||
|
|
||||||
|
// Used for asynchronous jobs
|
||||||
L.SetGlobal("JobStart", luar.New(L, JobStart))
|
L.SetGlobal("JobStart", luar.New(L, JobStart))
|
||||||
L.SetGlobal("JobSend", luar.New(L, JobSend))
|
L.SetGlobal("JobSend", luar.New(L, JobSend))
|
||||||
L.SetGlobal("JobStop", luar.New(L, JobStop))
|
L.SetGlobal("JobStop", luar.New(L, JobStop))
|
||||||
@@ -259,6 +270,7 @@ func main() {
|
|||||||
jobs = make(chan JobFunction, 100)
|
jobs = make(chan JobFunction, 100)
|
||||||
events = make(chan tcell.Event)
|
events = make(chan tcell.Event)
|
||||||
|
|
||||||
|
// Here is the event loop which runs in a separate thread
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
events <- screen.PollEvent()
|
events <- screen.PollEvent()
|
||||||
@@ -270,8 +282,11 @@ func main() {
|
|||||||
RedrawAll()
|
RedrawAll()
|
||||||
|
|
||||||
var event tcell.Event
|
var event tcell.Event
|
||||||
|
|
||||||
|
// Check for new events
|
||||||
select {
|
select {
|
||||||
case f := <-jobs:
|
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...)
|
f.function(f.output, f.args...)
|
||||||
continue
|
continue
|
||||||
case event = <-events:
|
case event = <-events:
|
||||||
@@ -280,13 +295,20 @@ func main() {
|
|||||||
switch e := event.(type) {
|
switch e := event.(type) {
|
||||||
case *tcell.EventMouse:
|
case *tcell.EventMouse:
|
||||||
if e.Buttons() == tcell.Button1 {
|
if e.Buttons() == tcell.Button1 {
|
||||||
|
// If the user left clicked we check a couple things
|
||||||
_, h := screen.Size()
|
_, h := screen.Size()
|
||||||
x, y := e.Position()
|
x, y := e.Position()
|
||||||
if y == h-1 && messenger.message != "" {
|
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)
|
clipboard.WriteAll(messenger.message)
|
||||||
continue
|
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 {
|
for _, v := range tabs[curTab].views {
|
||||||
if x >= v.x && x < v.x+v.width && y >= v.y && y < v.y+v.height {
|
if x >= v.x && x < v.x+v.width && y >= v.y && y < v.y+v.height {
|
||||||
tabs[curTab].curView = v.Num
|
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) {
|
if TabbarHandleMouseEvent(event) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if searching {
|
if searching {
|
||||||
// Since searching is done in real time, we need to redraw every time
|
// 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())
|
HandleSearchEvent(event, CurView())
|
||||||
} else {
|
} else {
|
||||||
// Send it to the view
|
// Send it to the view
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type Tab struct {
|
|||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTabFromView creates a new tab and puts the given view in the tab
|
||||||
func NewTabFromView(v *View) *Tab {
|
func NewTabFromView(v *View) *Tab {
|
||||||
t := new(Tab)
|
t := new(Tab)
|
||||||
t.views = append(t.views, v)
|
t.views = append(t.views, v)
|
||||||
@@ -24,6 +25,7 @@ func NewTabFromView(v *View) *Tab {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetNum sets all this tab's views to have the correct tab number
|
||||||
func (t *Tab) SetNum(num int) {
|
func (t *Tab) SetNum(num int) {
|
||||||
for _, v := range t.views {
|
for _, v := range t.views {
|
||||||
v.TabNum = num
|
v.TabNum = num
|
||||||
@@ -36,6 +38,10 @@ func CurView() *View {
|
|||||||
return curTab.views[curTab.curView]
|
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) {
|
func TabbarString() (string, map[int]int) {
|
||||||
str := ""
|
str := ""
|
||||||
indicies := make(map[int]int)
|
indicies := make(map[int]int)
|
||||||
@@ -57,7 +63,11 @@ func TabbarString() (string, map[int]int) {
|
|||||||
return str, indicies
|
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 {
|
func TabbarHandleMouseEvent(event tcell.Event) bool {
|
||||||
|
// There is no tabbar displayed if there are less than 2 tabs
|
||||||
if len(tabs) <= 1 {
|
if len(tabs) <= 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -65,6 +75,7 @@ func TabbarHandleMouseEvent(event tcell.Event) bool {
|
|||||||
switch e := event.(type) {
|
switch e := event.(type) {
|
||||||
case *tcell.EventMouse:
|
case *tcell.EventMouse:
|
||||||
button := e.Buttons()
|
button := e.Buttons()
|
||||||
|
// Must be a left click
|
||||||
if button == tcell.Button1 {
|
if button == tcell.Button1 {
|
||||||
x, y := e.Position()
|
x, y := e.Position()
|
||||||
if y != 0 {
|
if y != 0 {
|
||||||
@@ -94,6 +105,7 @@ func TabbarHandleMouseEvent(event tcell.Event) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisplayTabs displays the tabbar at the top of the editor if there are multiple tabs
|
||||||
func DisplayTabs() {
|
func DisplayTabs() {
|
||||||
if len(tabs) <= 1 {
|
if len(tabs) <= 1 {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -140,12 +140,15 @@ func GetModTime(path string) (time.Time, bool) {
|
|||||||
return info.ModTime(), true
|
return info.ModTime(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringWidth returns the width of a string where tabs count as `tabsize` width
|
||||||
func StringWidth(str string) int {
|
func StringWidth(str string) int {
|
||||||
sw := runewidth.StringWidth(str)
|
sw := runewidth.StringWidth(str)
|
||||||
sw += NumOccurences(str, '\t') * (int(settings["tabsize"].(float64)) - 1)
|
sw += NumOccurences(str, '\t') * (int(settings["tabsize"].(float64)) - 1)
|
||||||
return sw
|
return sw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
|
||||||
|
// that have a width larger than 1 (this also counts tabs as `tabsize` width)
|
||||||
func WidthOfLargeRunes(str string) int {
|
func WidthOfLargeRunes(str string) int {
|
||||||
count := 0
|
count := 0
|
||||||
for _, ch := range str {
|
for _, ch := range str {
|
||||||
@@ -162,6 +165,8 @@ func WidthOfLargeRunes(str string) int {
|
|||||||
return count
|
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 {
|
func runePos(p int, str string) int {
|
||||||
return utf8.RuneCountInString(str[:p])
|
return utf8.RuneCountInString(str[:p])
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user