]> git.lizzy.rs Git - micro.git/commitdiff
now is go gettable and updated make file
authoraerth <aerth@users.noreply.github.com>
Mon, 18 Apr 2016 10:59:41 +0000 (10:59 +0000)
committeraerth <aerth@users.noreply.github.com>
Mon, 18 Apr 2016 10:59:41 +0000 (10:59 +0000)
36 files changed:
.gitignore
Makefile
cmd/micro/buffer.go [new file with mode: 0644]
cmd/micro/colorscheme.go [new file with mode: 0644]
cmd/micro/command.go [new file with mode: 0644]
cmd/micro/cursor.go [new file with mode: 0644]
cmd/micro/eventhandler.go [new file with mode: 0644]
cmd/micro/help.go [new file with mode: 0644]
cmd/micro/highlighter.go [new file with mode: 0644]
cmd/micro/messenger.go [new file with mode: 0644]
cmd/micro/micro.go [new file with mode: 0644]
cmd/micro/search.go [new file with mode: 0644]
cmd/micro/settings.go [new file with mode: 0644]
cmd/micro/stack.go [new file with mode: 0644]
cmd/micro/stack_test.go [new file with mode: 0644]
cmd/micro/statusline.go [new file with mode: 0644]
cmd/micro/util.go [new file with mode: 0644]
cmd/micro/util_test.go [new file with mode: 0644]
cmd/micro/view.go [new file with mode: 0644]
src/buffer.go [deleted file]
src/colorscheme.go [deleted file]
src/command.go [deleted file]
src/cursor.go [deleted file]
src/eventhandler.go [deleted file]
src/help.go [deleted file]
src/highlighter.go [deleted file]
src/messenger.go [deleted file]
src/micro.go [deleted file]
src/search.go [deleted file]
src/settings.go [deleted file]
src/stack.go [deleted file]
src/stack_test.go [deleted file]
src/statusline.go [deleted file]
src/util.go [deleted file]
src/util_test.go [deleted file]
src/view.go [deleted file]

index 877caa7a528f6c936e0122e59d92ec6ca2c3e6a2..8dc155a4b2cf2d19e46fa8d09282d5f043a75f8e 100644 (file)
@@ -1,2 +1,3 @@
 micro
+!cmd/micro
 binaries/
index 13870c432b1499f381a68bd9568a6ecef058141f..d6077eb2a170b2eeac46c8f157a1c987f907e5ea 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 build: syn-files
-       go get -d ./src
-       go build -o micro ./src
+       go get -d ./cmd/micro
+       go build -o micro ./cmd/micro
 
 install: syn-files build
        mv micro $(GOBIN)
@@ -10,8 +10,8 @@ syn-files:
        cp -r runtime/* ~/.micro
 
 test:
-       go get -d ./src
-       go test ./src
+       go get -d ./cmd/micro
+       go test ./cmd/micro
 
 clean:
        rm -f micro
diff --git a/cmd/micro/buffer.go b/cmd/micro/buffer.go
new file mode 100644 (file)
index 0000000..66b02af
--- /dev/null
@@ -0,0 +1,117 @@
+package main
+
+import (
+       "github.com/vinzmay/go-rope"
+       "io/ioutil"
+       "strings"
+)
+
+// Buffer stores the text for files that are loaded into the text editor
+// It uses a rope to efficiently store the string and contains some
+// simple functions for saving and wrapper functions for modifying the rope
+type Buffer struct {
+       // Stores the text of the buffer
+       r *rope.Rope
+
+       // Path to the file on disk
+       path string
+       // Name of the buffer on the status line
+       name string
+
+       // This is the text stored every time the buffer is saved to check if the buffer is modified
+       savedText string
+
+       // 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
+       text  string
+       lines []string
+
+       // Syntax highlighting rules
+       rules []SyntaxRule
+       // The buffer's filetype
+       filetype string
+}
+
+// NewBuffer creates a new buffer from `txt` with path and name `path`
+func NewBuffer(txt, path string) *Buffer {
+       b := new(Buffer)
+       if txt == "" {
+               b.r = new(rope.Rope)
+       } else {
+               b.r = rope.New(txt)
+       }
+       b.path = path
+       b.name = path
+       b.savedText = txt
+
+       b.Update()
+       b.UpdateRules()
+
+       return b
+}
+
+// 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)
+}
+
+// Update fetches the string from the rope and updates the `text` and `lines` in the buffer
+func (b *Buffer) Update() {
+       if b.r.Len() == 0 {
+               b.text = ""
+       } else {
+               b.text = b.r.String()
+       }
+       b.lines = strings.Split(b.text, "\n")
+}
+
+// Save saves the buffer to its default path
+func (b *Buffer) Save() error {
+       return b.SaveAs(b.path)
+}
+
+// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
+func (b *Buffer) SaveAs(filename string) error {
+       b.UpdateRules()
+       err := ioutil.WriteFile(filename, []byte(b.text), 0644)
+       if err == nil {
+               b.savedText = b.text
+       }
+       return err
+}
+
+// IsDirty returns whether or not the buffer has been modified compared to the one on disk
+func (b *Buffer) IsDirty() bool {
+       return b.savedText != b.text
+}
+
+// Insert a string into the rope
+func (b *Buffer) Insert(idx int, value string) {
+       b.r = b.r.Insert(idx, value)
+       b.Update()
+}
+
+// Remove a slice of the rope from start to end (exclusive)
+// Returns the string that was removed
+func (b *Buffer) Remove(start, end int) string {
+       if start < 0 {
+               start = 0
+       }
+       if end > b.Len() {
+               end = b.Len()
+       }
+       removed := b.text[start:end]
+       // The rope implenentation I am using wants indicies starting at 1 instead of 0
+       start++
+       end++
+       b.r = b.r.Delete(start, end-start)
+       b.Update()
+       return removed
+}
+
+// Len gives the length of the buffer
+func (b *Buffer) Len() int {
+       return b.r.Len()
+}
diff --git a/cmd/micro/colorscheme.go b/cmd/micro/colorscheme.go
new file mode 100644 (file)
index 0000000..450da38
--- /dev/null
@@ -0,0 +1,198 @@
+package main
+
+import (
+       "fmt"
+       "github.com/gdamore/tcell"
+       "github.com/mitchellh/go-homedir"
+       "io/ioutil"
+       "regexp"
+       "strconv"
+       "strings"
+)
+
+// Colorscheme is a map from string to style -- it represents a colorscheme
+type Colorscheme map[string]tcell.Style
+
+// The current colorscheme
+var colorscheme Colorscheme
+
+// InitColorscheme picks and initializes the colorscheme when micro starts
+func InitColorscheme() {
+       LoadDefaultColorscheme()
+}
+
+// LoadDefaultColorscheme loads the default colorscheme from ~/.micro/colorschemes
+func LoadDefaultColorscheme() {
+       dir, err := homedir.Dir()
+       if err != nil {
+               TermMessage("Error finding your home directory\nCan't load runtime files")
+               return
+       }
+       LoadColorscheme(settings.Colorscheme, dir+"/.micro/colorschemes")
+}
+
+// LoadColorscheme loads the given colorscheme from a directory
+func LoadColorscheme(colorschemeName, dir string) {
+       files, _ := ioutil.ReadDir(dir)
+       for _, f := range files {
+               if f.Name() == colorschemeName+".micro" {
+                       text, err := ioutil.ReadFile(dir + "/" + f.Name())
+                       if err != nil {
+                               fmt.Println("Error loading colorscheme:", err)
+                               continue
+                       }
+                       colorscheme = ParseColorscheme(string(text))
+               }
+       }
+}
+
+// ParseColorscheme parses the text definition for a colorscheme and returns the corresponding object
+// Colorschemes are made up of color-link statements linking a color group to a list of colors
+// For example, color-link keyword (blue,red) makes all keywords have a blue foreground and
+// red background
+func ParseColorscheme(text string) Colorscheme {
+       parser := regexp.MustCompile(`color-link\s+(\S*)\s+"(.*)"`)
+
+       lines := strings.Split(text, "\n")
+
+       c := make(Colorscheme)
+
+       for _, line := range lines {
+               if strings.TrimSpace(line) == "" ||
+                       strings.TrimSpace(line)[0] == '#' {
+                       // Ignore this line
+                       continue
+               }
+
+               matches := parser.FindSubmatch([]byte(line))
+               if len(matches) == 3 {
+                       link := string(matches[1])
+                       colors := string(matches[2])
+
+                       c[link] = StringToStyle(colors)
+               } else {
+                       fmt.Println("Color-link statement is not valid:", line)
+               }
+       }
+
+       return c
+}
+
+// StringToStyle returns a style from a string
+// The strings must be in the format "extra foregroundcolor,backgroundcolor"
+// The 'extra' can be bold, reverse, or underline
+func StringToStyle(str string) tcell.Style {
+       var fg string
+       bg := "default"
+       split := strings.Split(str, ",")
+       if len(split) > 1 {
+               fg, bg = split[0], split[1]
+       } else {
+               fg = split[0]
+       }
+       fg = strings.TrimSpace(fg)
+       bg = strings.TrimSpace(bg)
+
+       style := tcell.StyleDefault.Foreground(StringToColor(fg)).Background(StringToColor(bg))
+       if strings.Contains(str, "bold") {
+               style = style.Bold(true)
+       }
+       if strings.Contains(str, "reverse") {
+               style = style.Reverse(true)
+       }
+       if strings.Contains(str, "underline") {
+               style = style.Underline(true)
+       }
+       return style
+}
+
+// StringToColor returns a tcell color from a string representation of a color
+// We accept either bright... or light... to mean the brighter version of a color
+func StringToColor(str string) tcell.Color {
+       switch str {
+       case "black":
+               return tcell.ColorBlack
+       case "red":
+               return tcell.ColorMaroon
+       case "green":
+               return tcell.ColorGreen
+       case "yellow":
+               return tcell.ColorOlive
+       case "blue":
+               return tcell.ColorNavy
+       case "magenta":
+               return tcell.ColorPurple
+       case "cyan":
+               return tcell.ColorTeal
+       case "white":
+               return tcell.ColorSilver
+       case "brightblack", "lightblack":
+               return tcell.ColorGray
+       case "brightred", "lightred":
+               return tcell.ColorRed
+       case "brightgreen", "lightgreen":
+               return tcell.ColorLime
+       case "brightyellow", "lightyellow":
+               return tcell.ColorYellow
+       case "brightblue", "lightblue":
+               return tcell.ColorBlue
+       case "brightmagenta", "lightmagenta":
+               return tcell.ColorFuchsia
+       case "brightcyan", "lightcyan":
+               return tcell.ColorAqua
+       case "brightwhite", "lightwhite":
+               return tcell.ColorWhite
+       case "default":
+               return tcell.ColorDefault
+       default:
+               // Check if this is a 256 color
+               if num, err := strconv.Atoi(str); err == nil {
+                       return GetColor256(num)
+               }
+               // Probably a truecolor hex value
+               return tcell.GetColor(str)
+       }
+}
+
+// GetColor256 returns the tcell color for a number between 0 and 255
+func GetColor256(color int) tcell.Color {
+       colors := []tcell.Color{tcell.ColorBlack, tcell.ColorMaroon, tcell.ColorGreen,
+               tcell.ColorOlive, tcell.ColorNavy, tcell.ColorPurple,
+               tcell.ColorTeal, tcell.ColorSilver, tcell.ColorGray,
+               tcell.ColorRed, tcell.ColorLime, tcell.ColorYellow,
+               tcell.ColorBlue, tcell.ColorFuchsia, tcell.ColorAqua,
+               tcell.ColorWhite, tcell.Color16, tcell.Color17, tcell.Color18, tcell.Color19, tcell.Color20,
+               tcell.Color21, tcell.Color22, tcell.Color23, tcell.Color24, tcell.Color25, tcell.Color26, tcell.Color27, tcell.Color28,
+               tcell.Color29, tcell.Color30, tcell.Color31, tcell.Color32, tcell.Color33, tcell.Color34, tcell.Color35, tcell.Color36,
+               tcell.Color37, tcell.Color38, tcell.Color39, tcell.Color40, tcell.Color41, tcell.Color42, tcell.Color43, tcell.Color44,
+               tcell.Color45, tcell.Color46, tcell.Color47, tcell.Color48, tcell.Color49, tcell.Color50, tcell.Color51, tcell.Color52,
+               tcell.Color53, tcell.Color54, tcell.Color55, tcell.Color56, tcell.Color57, tcell.Color58, tcell.Color59, tcell.Color60,
+               tcell.Color61, tcell.Color62, tcell.Color63, tcell.Color64, tcell.Color65, tcell.Color66, tcell.Color67, tcell.Color68,
+               tcell.Color69, tcell.Color70, tcell.Color71, tcell.Color72, tcell.Color73, tcell.Color74, tcell.Color75, tcell.Color76,
+               tcell.Color77, tcell.Color78, tcell.Color79, tcell.Color80, tcell.Color81, tcell.Color82, tcell.Color83, tcell.Color84,
+               tcell.Color85, tcell.Color86, tcell.Color87, tcell.Color88, tcell.Color89, tcell.Color90, tcell.Color91, tcell.Color92,
+               tcell.Color93, tcell.Color94, tcell.Color95, tcell.Color96, tcell.Color97, tcell.Color98, tcell.Color99, tcell.Color100,
+               tcell.Color101, tcell.Color102, tcell.Color103, tcell.Color104, tcell.Color105, tcell.Color106, tcell.Color107, tcell.Color108,
+               tcell.Color109, tcell.Color110, tcell.Color111, tcell.Color112, tcell.Color113, tcell.Color114, tcell.Color115, tcell.Color116,
+               tcell.Color117, tcell.Color118, tcell.Color119, tcell.Color120, tcell.Color121, tcell.Color122, tcell.Color123, tcell.Color124,
+               tcell.Color125, tcell.Color126, tcell.Color127, tcell.Color128, tcell.Color129, tcell.Color130, tcell.Color131, tcell.Color132,
+               tcell.Color133, tcell.Color134, tcell.Color135, tcell.Color136, tcell.Color137, tcell.Color138, tcell.Color139, tcell.Color140,
+               tcell.Color141, tcell.Color142, tcell.Color143, tcell.Color144, tcell.Color145, tcell.Color146, tcell.Color147, tcell.Color148,
+               tcell.Color149, tcell.Color150, tcell.Color151, tcell.Color152, tcell.Color153, tcell.Color154, tcell.Color155, tcell.Color156,
+               tcell.Color157, tcell.Color158, tcell.Color159, tcell.Color160, tcell.Color161, tcell.Color162, tcell.Color163, tcell.Color164,
+               tcell.Color165, tcell.Color166, tcell.Color167, tcell.Color168, tcell.Color169, tcell.Color170, tcell.Color171, tcell.Color172,
+               tcell.Color173, tcell.Color174, tcell.Color175, tcell.Color176, tcell.Color177, tcell.Color178, tcell.Color179, tcell.Color180,
+               tcell.Color181, tcell.Color182, tcell.Color183, tcell.Color184, tcell.Color185, tcell.Color186, tcell.Color187, tcell.Color188,
+               tcell.Color189, tcell.Color190, tcell.Color191, tcell.Color192, tcell.Color193, tcell.Color194, tcell.Color195, tcell.Color196,
+               tcell.Color197, tcell.Color198, tcell.Color199, tcell.Color200, tcell.Color201, tcell.Color202, tcell.Color203, tcell.Color204,
+               tcell.Color205, tcell.Color206, tcell.Color207, tcell.Color208, tcell.Color209, tcell.Color210, tcell.Color211, tcell.Color212,
+               tcell.Color213, tcell.Color214, tcell.Color215, tcell.Color216, tcell.Color217, tcell.Color218, tcell.Color219, tcell.Color220,
+               tcell.Color221, tcell.Color222, tcell.Color223, tcell.Color224, tcell.Color225, tcell.Color226, tcell.Color227, tcell.Color228,
+               tcell.Color229, tcell.Color230, tcell.Color231, tcell.Color232, tcell.Color233, tcell.Color234, tcell.Color235, tcell.Color236,
+               tcell.Color237, tcell.Color238, tcell.Color239, tcell.Color240, tcell.Color241, tcell.Color242, tcell.Color243, tcell.Color244,
+               tcell.Color245, tcell.Color246, tcell.Color247, tcell.Color248, tcell.Color249, tcell.Color250, tcell.Color251, tcell.Color252,
+               tcell.Color253, tcell.Color254, tcell.Color255,
+       }
+
+       return colors[color]
+}
diff --git a/cmd/micro/command.go b/cmd/micro/command.go
new file mode 100644 (file)
index 0000000..7fcf41d
--- /dev/null
@@ -0,0 +1,97 @@
+package main
+
+import (
+       "os"
+       "regexp"
+       "strings"
+)
+
+// HandleCommand handles input from the user
+func HandleCommand(input string, view *View) {
+       inputCmd := strings.Split(input, " ")[0]
+       args := strings.Split(input, " ")[1:]
+
+       commands := []string{"set", "quit", "save", "replace"}
+
+       i := 0
+       cmd := inputCmd
+
+       for _, c := range commands {
+               if strings.HasPrefix(c, inputCmd) {
+                       i++
+                       cmd = c
+               }
+       }
+       if i == 1 {
+               inputCmd = cmd
+       }
+
+       switch inputCmd {
+       case "set":
+               SetOption(view, args)
+       case "quit":
+               if view.CanClose("Quit anyway? ") {
+                       screen.Fini()
+                       os.Exit(0)
+               }
+       case "save":
+               view.Save()
+       case "replace":
+               r := regexp.MustCompile(`"[^"\\]*(?:\\.[^"\\]*)*"|[^\s]*`)
+               replaceCmd := r.FindAllString(strings.Join(args, " "), -1)
+               if len(replaceCmd) < 2 {
+                       messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
+                       return
+               }
+
+               var flags string
+               if len(replaceCmd) == 3 {
+                       // The user included some flags
+                       flags = replaceCmd[2]
+               }
+
+               search := string(replaceCmd[0])
+               replace := string(replaceCmd[1])
+
+               if strings.HasPrefix(search, `"`) && strings.HasSuffix(search, `"`) {
+                       search = search[1 : len(search)-1]
+               }
+               if strings.HasPrefix(replace, `"`) && strings.HasSuffix(replace, `"`) {
+                       replace = replace[1 : len(replace)-1]
+               }
+
+               search = strings.Replace(search, `\"`, `"`, -1)
+               replace = strings.Replace(replace, `\"`, `"`, -1)
+
+               // messenger.Error(search + " -> " + replace)
+
+               regex, err := regexp.Compile(search)
+               if err != nil {
+                       messenger.Error(err.Error())
+                       return
+               }
+
+               found := false
+               for {
+                       match := regex.FindStringIndex(view.buf.text)
+                       if match == nil {
+                               break
+                       }
+                       found = true
+                       if strings.Contains(flags, "c") {
+                               //      // The 'check' flag was used
+                               //      if messenger.YesNoPrompt("Perform replacement?") {
+                               //              view.eh.Replace(match[0], match[1], replace)
+                               //      } else {
+                               //              continue
+                               //      }
+                       }
+                       view.eh.Replace(match[0], match[1], replace)
+               }
+               if !found {
+                       messenger.Message("Nothing matched " + search)
+               }
+       default:
+               messenger.Error("Unknown command: " + inputCmd)
+       }
+}
diff --git a/cmd/micro/cursor.go b/cmd/micro/cursor.go
new file mode 100644 (file)
index 0000000..a922903
--- /dev/null
@@ -0,0 +1,308 @@
+package main
+
+import (
+       "strings"
+)
+
+// FromCharPos converts from a character position to an x, y position
+func FromCharPos(loc int, buf *Buffer) (int, int) {
+       return FromCharPosStart(0, 0, 0, loc, buf)
+}
+
+// FromCharPosStart converts from a character position to an x, y position, starting at the specified character location
+func FromCharPosStart(startLoc, startX, startY, loc int, buf *Buffer) (int, int) {
+       charNum := startLoc
+       x, y := startX, startY
+
+       lineLen := Count(buf.lines[y]) + 1
+       for charNum+lineLen <= loc {
+               charNum += lineLen
+               y++
+               lineLen = Count(buf.lines[y]) + 1
+       }
+       x = loc - charNum
+
+       return x, y
+}
+
+// ToCharPos converts from an x, y position to a character position
+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 += x
+       return loc
+}
+
+// The Cursor struct stores the location of the cursor in the view
+// The complicated part about the cursor is storing its location.
+// The cursor must be displayed at an x, y location, but since the buffer
+// uses a rope to store text, to insert text we must have an index. It
+// is also simpler to use character indicies for other tasks such as
+// selection.
+type Cursor struct {
+       v *View
+
+       // The cursor display location
+       x int
+       y int
+
+       // Last cursor x position
+       lastVisualX int
+
+       // The current selection as a range of character numbers (inclusive)
+       curSelection [2]int
+       // The original selection as a range of character numbers
+       // This is used for line and word selection where it is necessary
+       // to know what the original selection was
+       origSelection [2]int
+}
+
+// SetLoc sets the location of the cursor in terms of character number
+// 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)
+}
+
+// Loc gets the cursor location in terms of character number instead
+// 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)
+}
+
+// ResetSelection resets the user's selection
+func (c *Cursor) ResetSelection() {
+       c.curSelection[0] = 0
+       c.curSelection[1] = 0
+}
+
+// HasSelection returns whether or not the user has selected anything
+func (c *Cursor) HasSelection() bool {
+       return c.curSelection[0] != c.curSelection[1]
+}
+
+// DeleteSelection deletes the currently selected text
+func (c *Cursor) DeleteSelection() {
+       if c.curSelection[0] > c.curSelection[1] {
+               c.v.eh.Remove(c.curSelection[1], c.curSelection[0])
+               c.SetLoc(c.curSelection[1])
+       } else {
+               c.v.eh.Remove(c.curSelection[0], c.curSelection[1])
+               c.SetLoc(c.curSelection[0])
+       }
+}
+
+// GetSelection returns the cursor's selection
+func (c *Cursor) GetSelection() string {
+       if c.curSelection[0] > c.curSelection[1] {
+               return string([]rune(c.v.buf.text)[c.curSelection[1]:c.curSelection[0]])
+       }
+       return string([]rune(c.v.buf.text)[c.curSelection[0]:c.curSelection[1]])
+}
+
+// SelectLine selects the current line
+func (c *Cursor) SelectLine() {
+       c.Start()
+       c.curSelection[0] = c.Loc()
+       c.End()
+       c.curSelection[1] = c.Loc()
+
+       c.origSelection = c.curSelection
+}
+
+// AddLineToSelection adds the current line to the selection
+func (c *Cursor) AddLineToSelection() {
+       loc := c.Loc()
+
+       if loc < c.origSelection[0] {
+               c.Start()
+               c.curSelection[0] = c.Loc()
+               c.curSelection[1] = c.origSelection[1]
+       }
+       if loc > c.origSelection[1] {
+               c.End()
+               c.curSelection[1] = c.Loc()
+               c.curSelection[0] = c.origSelection[0]
+       }
+
+       if loc < c.origSelection[1] && loc > c.origSelection[0] {
+               c.curSelection = c.origSelection
+       }
+}
+
+// SelectWord selects the word the cursor is currently on
+func (c *Cursor) SelectWord() {
+       if len(c.v.buf.lines[c.y]) == 0 {
+               return
+       }
+
+       if !IsWordChar(string(c.RuneUnder(c.x))) {
+               loc := c.Loc()
+               c.curSelection[0] = loc
+               c.curSelection[1] = loc + 1
+               c.origSelection = c.curSelection
+               return
+       }
+
+       forward, backward := c.x, c.x
+
+       for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
+               backward--
+       }
+
+       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))) {
+               forward++
+       }
+
+       c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + 1
+       c.origSelection[1] = c.curSelection[1]
+}
+
+// AddWordToSelection adds the word the cursor is currently on to the selection
+func (c *Cursor) AddWordToSelection() {
+       loc := c.Loc()
+
+       if loc > c.origSelection[0] && loc < c.origSelection[1] {
+               c.curSelection = c.origSelection
+               return
+       }
+
+       if loc < c.origSelection[0] {
+               backward := c.x
+
+               for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
+                       backward--
+               }
+
+               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))) {
+                       forward++
+               }
+
+               c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + 1
+               c.curSelection[0] = c.origSelection[0]
+       }
+}
+
+// RuneUnder returns the rune under the given x position
+func (c *Cursor) RuneUnder(x int) rune {
+       line := []rune(c.v.buf.lines[c.y])
+       if x >= len(line) {
+               x = len(line) - 1
+       } else if x < 0 {
+               x = 0
+       }
+       return line[x]
+}
+
+// Up moves the cursor up one line (if possible)
+func (c *Cursor) Up() {
+       if c.y > 0 {
+               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)
+               }
+       }
+}
+
+// Down moves the cursor down one line (if possible)
+func (c *Cursor) Down() {
+       if c.y < len(c.v.buf.lines)-1 {
+               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)
+               }
+       }
+}
+
+// Left moves the cursor left one cell (if possible) or to the last line if it is at the beginning
+func (c *Cursor) Left() {
+       if c.Loc() == 0 {
+               return
+       }
+       if c.x > 0 {
+               c.x--
+       } else {
+               c.Up()
+               c.End()
+       }
+       c.lastVisualX = c.GetVisualX()
+}
+
+// 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() {
+               return
+       }
+       if c.x < Count(c.v.buf.lines[c.y]) {
+               c.x++
+       } else {
+               c.Down()
+               c.Start()
+       }
+       c.lastVisualX = c.GetVisualX()
+}
+
+// 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.lastVisualX = c.GetVisualX()
+}
+
+// Start moves the cursor to the start of the line it is on
+func (c *Cursor) Start() {
+       c.x = 0
+       c.lastVisualX = c.GetVisualX()
+}
+
+// GetCharPosInLine gets the char position of a visual x y coordinate (this is necessary because tabs are 1 char but 4 visual spaces)
+func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
+       // Get the tab size
+       tabSize := settings.TabSize
+       // 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)
+       if visualPos > Count(visualLine) {
+               visualPos = Count(visualLine)
+       }
+       numTabs := NumOccurences(visualLine[:visualPos], '\t')
+       if visualPos >= (tabSize-1)*numTabs {
+               return visualPos - (tabSize-1)*numTabs
+       }
+       return visualPos / tabSize
+}
+
+// GetVisualX returns the x value of the cursor in visual spaces
+func (c *Cursor) GetVisualX() int {
+       runes := []rune(c.v.buf.lines[c.y])
+       tabSize := settings.TabSize
+       return c.x + NumOccurences(string(runes[:c.x]), '\t')*(tabSize-1)
+}
+
+// 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() {
+               screen.HideCursor()
+       } else {
+               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
new file mode 100644 (file)
index 0000000..c54319e
--- /dev/null
@@ -0,0 +1,196 @@
+package main
+
+import (
+       "time"
+)
+
+const (
+       // Opposite and undoing events must have opposite values
+
+       // TextEventInsert repreasents an insertion event
+       TextEventInsert = 1
+       // TextEventRemove represents a deletion event
+       TextEventRemove = -1
+)
+
+// TextEvent holds data for a manipulation on some text that can be undone
+type TextEvent struct {
+       c Cursor
+
+       eventType int
+       text      string
+       start     int
+       end       int
+       buf       *Buffer
+       time      time.Time
+}
+
+// ExecuteTextEvent runs a text event
+func ExecuteTextEvent(t *TextEvent) {
+       if t.eventType == TextEventInsert {
+               t.buf.Insert(t.start, t.text)
+       } else if t.eventType == TextEventRemove {
+               t.text = t.buf.Remove(t.start, t.end)
+       }
+}
+
+// UndoTextEvent undoes a text event
+func UndoTextEvent(t *TextEvent) {
+       t.eventType = -t.eventType
+       ExecuteTextEvent(t)
+}
+
+// EventHandler executes text manipulations and allows undoing and redoing
+type EventHandler struct {
+       v    *View
+       undo *Stack
+       redo *Stack
+}
+
+// NewEventHandler returns a new EventHandler
+func NewEventHandler(v *View) *EventHandler {
+       eh := new(EventHandler)
+       eh.undo = new(Stack)
+       eh.redo = new(Stack)
+       eh.v = v
+       return eh
+}
+
+// Insert creates an insert text event and executes it
+func (eh *EventHandler) Insert(start int, text string) {
+       e := &TextEvent{
+               c:         eh.v.cursor,
+               eventType: TextEventInsert,
+               text:      text,
+               start:     start,
+               end:       start + Count(text),
+               buf:       eh.v.buf,
+               time:      time.Now(),
+       }
+       eh.Execute(e)
+}
+
+// Remove creates a remove text event and executes it
+func (eh *EventHandler) Remove(start, end int) {
+       e := &TextEvent{
+               c:         eh.v.cursor,
+               eventType: TextEventRemove,
+               start:     start,
+               end:       end,
+               buf:       eh.v.buf,
+               time:      time.Now(),
+       }
+       eh.Execute(e)
+}
+
+// Replace deletes from start to end and replaces it with the given string
+func (eh *EventHandler) Replace(start, end int, replace string) {
+       eh.Remove(start, end)
+       eh.Insert(start, replace)
+}
+
+// Execute a textevent and add it to the undo stack
+func (eh *EventHandler) Execute(t *TextEvent) {
+       if eh.redo.Len() > 0 {
+               eh.redo = new(Stack)
+       }
+       eh.undo.Push(t)
+       ExecuteTextEvent(t)
+}
+
+// Undo the first event in the undo stack
+func (eh *EventHandler) Undo() {
+       t := eh.undo.Peek()
+       if t == nil {
+               return
+       }
+
+       te := t.(*TextEvent)
+       startTime := t.(*TextEvent).time.UnixNano() / int64(time.Millisecond)
+
+       eh.UndoOneEvent()
+
+       for {
+               t = eh.undo.Peek()
+               if t == nil {
+                       return
+               }
+
+               te = t.(*TextEvent)
+
+               if startTime-(te.time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
+                       return
+               }
+
+               eh.UndoOneEvent()
+       }
+}
+
+// UndoOneEvent undoes one event
+func (eh *EventHandler) UndoOneEvent() {
+       // This event should be undone
+       // Pop it off the stack
+       t := eh.undo.Pop()
+       if t == nil {
+               return
+       }
+
+       te := t.(*TextEvent)
+       // Undo it
+       // Modifies the text event
+       UndoTextEvent(te)
+
+       // Set the cursor in the right place
+       teCursor := te.c
+       te.c = eh.v.cursor
+       eh.v.cursor = teCursor
+
+       // Push it to the redo stack
+       eh.redo.Push(te)
+}
+
+// Redo the first event in the redo stack
+func (eh *EventHandler) Redo() {
+       t := eh.redo.Peek()
+       if t == nil {
+               return
+       }
+
+       te := t.(*TextEvent)
+       startTime := t.(*TextEvent).time.UnixNano() / int64(time.Millisecond)
+
+       eh.RedoOneEvent()
+
+       for {
+               t = eh.redo.Peek()
+               if t == nil {
+                       return
+               }
+
+               te = t.(*TextEvent)
+
+               if (te.time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
+                       return
+               }
+
+               eh.RedoOneEvent()
+       }
+}
+
+// RedoOneEvent redoes one event
+func (eh *EventHandler) RedoOneEvent() {
+       t := eh.redo.Pop()
+       if t == nil {
+               return
+       }
+
+       te := t.(*TextEvent)
+       // Modifies the text event
+       UndoTextEvent(te)
+
+       teCursor := te.c
+       te.c = eh.v.cursor
+       eh.v.cursor = teCursor
+
+       eh.undo.Push(te)
+}
diff --git a/cmd/micro/help.go b/cmd/micro/help.go
new file mode 100644 (file)
index 0000000..5d2d3ed
--- /dev/null
@@ -0,0 +1,104 @@
+package main
+
+import (
+       "github.com/gdamore/tcell"
+       "strings"
+)
+
+const helpTxt = `Press Ctrl-q to quit help
+
+Micro keybindings:
+
+Ctrl-q:   Quit
+Ctrl-s:   Save
+Ctrl-o:   Open file
+
+Ctrl-z:   Undo
+Ctrl-y:   Redo
+
+Ctrl-f:   Find
+Ctrl-n:   Find next
+Ctrl-p:   Find previous
+
+Ctrl-a:   Select all
+
+Ctrl-c:   Copy
+Ctrl-x:   Cut
+Ctrl-v:   Paste
+
+Ctrl-h:   Open help
+
+Ctrl-u:   Half page up
+Ctrl-d:   Half page down
+PageUp:   Page up
+PageDown: Page down
+
+Ctrl-e:   Execute a command
+
+Possible commands:
+
+'quit': Quits micro
+'save': saves the current buffer
+
+'replace "search" "value"': This will replace 'search' with 'value'.
+Note that 'search' must be a valid regex.  If one of the arguments
+does not have any spaces in it, you may omit the quotes.
+
+'set option value': sets the option to value. Please see the next section for a list of options you can set
+
+Micro options:
+
+colorscheme: loads the colorscheme stored in ~/.micro/colorschemes/'option'.micro
+       default value: 'default'
+
+tabsize: sets the tab size to 'option'
+       default value: '4'
+
+syntax: turns syntax on or off
+       default value: 'on'
+`
+
+// DisplayHelp displays the help txt
+// It blocks the main loop
+func DisplayHelp() {
+       topline := 0
+       _, height := screen.Size()
+       screen.HideCursor()
+       totalLines := strings.Split(helpTxt, "\n")
+       for {
+               screen.Clear()
+
+               lineEnd := topline + height
+               if lineEnd > len(totalLines) {
+                       lineEnd = len(totalLines)
+               }
+               lines := totalLines[topline:lineEnd]
+               for y, line := range lines {
+                       for x, ch := range line {
+                               st := defStyle
+                               screen.SetContent(x, y, ch, nil, st)
+                       }
+               }
+
+               screen.Show()
+
+               event := screen.PollEvent()
+               switch e := event.(type) {
+               case *tcell.EventResize:
+                       _, height = e.Size()
+               case *tcell.EventKey:
+                       switch e.Key() {
+                       case tcell.KeyUp:
+                               if topline > 0 {
+                                       topline--
+                               }
+                       case tcell.KeyDown:
+                               if topline < len(totalLines)-height {
+                                       topline++
+                               }
+                       case tcell.KeyCtrlQ, tcell.KeyCtrlW, tcell.KeyEscape, tcell.KeyCtrlC:
+                               return
+                       }
+               }
+       }
+}
diff --git a/cmd/micro/highlighter.go b/cmd/micro/highlighter.go
new file mode 100644 (file)
index 0000000..00f8b5a
--- /dev/null
@@ -0,0 +1,337 @@
+package main
+
+import (
+       "github.com/gdamore/tcell"
+       "github.com/mitchellh/go-homedir"
+       "io/ioutil"
+       "path/filepath"
+       "regexp"
+       "strings"
+)
+
+// FileTypeRules represents a complete set of syntax rules for a filetype
+type FileTypeRules struct {
+       filetype string
+       rules    []SyntaxRule
+}
+
+// SyntaxRule represents a regex to highlight in a certain style
+type SyntaxRule struct {
+       // What to highlight
+       regex *regexp.Regexp
+       // Any flags
+       flags string
+       // Whether this regex is a start=... end=... regex
+       startend bool
+       // How to highlight it
+       style tcell.Style
+}
+
+var syntaxFiles map[[2]*regexp.Regexp]FileTypeRules
+
+// LoadSyntaxFiles loads the syntax files from the default directory ~/.micro
+func LoadSyntaxFiles() {
+       home, err := homedir.Dir()
+       if err != nil {
+               TermMessage("Error finding your home directory\nCan't load syntax files")
+               return
+       }
+       LoadSyntaxFilesFromDir(home + "/.micro/syntax")
+}
+
+// JoinRule takes a syntax rule (which can be multiple regular expressions)
+// and joins it into one regular expression by ORing everything together
+func JoinRule(rule string) string {
+       split := strings.Split(rule, `" "`)
+       joined := strings.Join(split, ")|(")
+       joined = "(" + joined + ")"
+       return joined
+}
+
+// LoadSyntaxFile loads the specified syntax file
+// A syntax file is a list of syntax rules, explaining how to color certain
+// regular expressions
+// Example: color comment "//.*"
+// This would color all strings that match the regex "//.*" in the comment color defined
+// by the colorscheme
+func LoadSyntaxFile(filename string) {
+       text, err := ioutil.ReadFile(filename)
+
+       if err != nil {
+               TermMessage("Error loading syntax file " + filename + ": " + err.Error())
+               return
+       }
+       lines := strings.Split(string(text), "\n")
+
+       // Regex for parsing syntax statements
+       syntaxParser := regexp.MustCompile(`syntax "(.*?)"\s+"(.*)"+`)
+       // Regex for parsing header statements
+       headerParser := regexp.MustCompile(`header "(.*)"`)
+
+       // Regex for parsing standard syntax rules
+       ruleParser := regexp.MustCompile(`color (.*?)\s+(?:\((.*?)\)\s+)?"(.*)"`)
+       // Regex for parsing syntax rules with start="..." end="..."
+       ruleStartEndParser := regexp.MustCompile(`color (.*?)\s+(?:\((.*?)\)\s+)?start="(.*)"\s+end="(.*)"`)
+
+       var syntaxRegex *regexp.Regexp
+       var headerRegex *regexp.Regexp
+       var filetype string
+       var rules []SyntaxRule
+       for lineNum, line := range lines {
+               if strings.TrimSpace(line) == "" ||
+                       strings.TrimSpace(line)[0] == '#' {
+                       // Ignore this line
+                       continue
+               }
+
+               if strings.HasPrefix(line, "syntax") {
+                       // Syntax statement
+                       syntaxMatches := syntaxParser.FindSubmatch([]byte(line))
+                       if len(syntaxMatches) == 3 {
+                               if syntaxRegex != nil {
+                                       // Add the current rules to the syntaxFiles variable
+                                       regexes := [2]*regexp.Regexp{syntaxRegex, headerRegex}
+                                       syntaxFiles[regexes] = FileTypeRules{filetype, rules}
+                               }
+                               rules = rules[:0]
+
+                               filetype = string(syntaxMatches[1])
+                               extensions := JoinRule(string(syntaxMatches[2]))
+
+                               syntaxRegex, err = regexp.Compile(extensions)
+                               if err != nil {
+                                       TermError(filename, lineNum, err.Error())
+                                       continue
+                               }
+                       } else {
+                               TermError(filename, lineNum, "Syntax statement is not valid: "+line)
+                               continue
+                       }
+               } else if strings.HasPrefix(line, "header") {
+                       // Header statement
+                       headerMatches := headerParser.FindSubmatch([]byte(line))
+                       if len(headerMatches) == 2 {
+                               header := JoinRule(string(headerMatches[1]))
+
+                               headerRegex, err = regexp.Compile(header)
+                               if err != nil {
+                                       TermError(filename, lineNum, "Regex error: "+err.Error())
+                                       continue
+                               }
+                       } else {
+                               TermError(filename, lineNum, "Header statement is not valid: "+line)
+                               continue
+                       }
+               } else {
+                       // Syntax rule, but it could be standard or start-end
+                       if ruleParser.MatchString(line) {
+                               // Standard syntax rule
+                               // Parse the line
+                               submatch := ruleParser.FindSubmatch([]byte(line))
+                               var color string
+                               var regexStr string
+                               var flags string
+                               if len(submatch) == 4 {
+                                       // If len is 4 then the user specified some additional flags to use
+                                       color = string(submatch[1])
+                                       flags = string(submatch[2])
+                                       regexStr = "(?" + flags + ")" + JoinRule(string(submatch[3]))
+                               } else if len(submatch) == 3 {
+                                       // If len is 3, no additional flags were given
+                                       color = string(submatch[1])
+                                       regexStr = JoinRule(string(submatch[2]))
+                               } else {
+                                       // If len is not 3 or 4 there is a problem
+                                       TermError(filename, lineNum, "Invalid statement: "+line)
+                                       continue
+                               }
+                               // Compile the regex
+                               regex, err := regexp.Compile(regexStr)
+                               if err != nil {
+                                       TermError(filename, lineNum, err.Error())
+                                       continue
+                               }
+
+                               // Get the style
+                               // The user could give us a "color" that is really a part of the colorscheme
+                               // in which case we should look that up in the colorscheme
+                               // They can also just give us a straight up color
+                               st := defStyle
+                               if _, ok := colorscheme[color]; ok {
+                                       st = colorscheme[color]
+                               } else {
+                                       st = StringToStyle(color)
+                               }
+                               // Add the regex, flags, and style
+                               // False because this is not start-end
+                               rules = append(rules, SyntaxRule{regex, flags, false, st})
+                       } else if ruleStartEndParser.MatchString(line) {
+                               // Start-end syntax rule
+                               submatch := ruleStartEndParser.FindSubmatch([]byte(line))
+                               var color string
+                               var start string
+                               var end string
+                               // Use m and s flags by default
+                               flags := "ms"
+                               if len(submatch) == 5 {
+                                       // If len is 5 the user provided some additional flags
+                                       color = string(submatch[1])
+                                       flags += string(submatch[2])
+                                       start = string(submatch[3])
+                                       end = string(submatch[4])
+                               } else if len(submatch) == 4 {
+                                       // If len is 4 the user did not provide additional flags
+                                       color = string(submatch[1])
+                                       start = string(submatch[2])
+                                       end = string(submatch[3])
+                               } else {
+                                       // If len is not 4 or 5 there is a problem
+                                       TermError(filename, lineNum, "Invalid statement: "+line)
+                                       continue
+                               }
+
+                               // Compile the regex
+                               regex, err := regexp.Compile("(?" + flags + ")" + "(" + start + ").*?(" + end + ")")
+                               if err != nil {
+                                       TermError(filename, lineNum, err.Error())
+                                       continue
+                               }
+
+                               // Get the style
+                               // The user could give us a "color" that is really a part of the colorscheme
+                               // in which case we should look that up in the colorscheme
+                               // They can also just give us a straight up color
+                               st := defStyle
+                               if _, ok := colorscheme[color]; ok {
+                                       st = colorscheme[color]
+                               } else {
+                                       st = StringToStyle(color)
+                               }
+                               // Add the regex, flags, and style
+                               // True because this is start-end
+                               rules = append(rules, SyntaxRule{regex, flags, true, st})
+                       }
+               }
+       }
+       if syntaxRegex != nil {
+               // Add the current rules to the syntaxFiles variable
+               regexes := [2]*regexp.Regexp{syntaxRegex, headerRegex}
+               syntaxFiles[regexes] = FileTypeRules{filetype, rules}
+       }
+}
+
+// LoadSyntaxFilesFromDir loads the syntax files from a specified directory
+// To load the syntax files, we must fill the `syntaxFiles` map
+// This involves finding the regex for syntax and if it exists, the regex
+// for the header. Then we must get the text for the file and the filetype.
+func LoadSyntaxFilesFromDir(dir string) {
+       InitColorscheme()
+
+       syntaxFiles = make(map[[2]*regexp.Regexp]FileTypeRules)
+       files, _ := ioutil.ReadDir(dir)
+       for _, f := range files {
+               if filepath.Ext(f.Name()) == ".micro" {
+                       LoadSyntaxFile(dir + "/" + f.Name())
+               }
+       }
+}
+
+// GetRules finds the syntax rules that should be used for the buffer
+// 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) {
+                       return syntaxFiles[r].rules, syntaxFiles[r].filetype
+               } else if r[1] != nil && r[1].MatchString(buf.lines[0]) {
+                       return syntaxFiles[r].rules, syntaxFiles[r].filetype
+               }
+       }
+       return nil, "Unknown"
+}
+
+// SyntaxMatches is an alias to a map from character numbers to styles,
+// so map[3] represents the style of the third character
+type SyntaxMatches [][]tcell.Style
+
+// Match takes a buffer and returns the syntax matches a map specifying how it should be syntax highlighted
+// We need to check the start-end regexes for the entire buffer every time Match is called, but for the
+// non start-end rules, we only have to update the updateLines provided by the view
+func Match(v *View) SyntaxMatches {
+       buf := v.buf
+       rules := v.buf.rules
+
+       viewStart := v.topline
+       viewEnd := v.topline + v.height
+       if viewEnd > len(buf.lines) {
+               viewEnd = len(buf.lines)
+       }
+
+       // updateStart := v.updateLines[0]
+       // updateEnd := v.updateLines[1]
+       //
+       // if updateEnd > len(buf.lines) {
+       //      updateEnd = len(buf.lines)
+       // }
+       // if updateStart < 0 {
+       //      updateStart = 0
+       // }
+       lines := buf.lines[viewStart:viewEnd]
+       // updateLines := buf.lines[updateStart:updateEnd]
+       matches := make(SyntaxMatches, len(lines))
+
+       for i, line := range lines {
+               matches[i] = make([]tcell.Style, len(line)+1)
+       }
+
+       // We don't actually check the entire buffer, just from synLinesUp to synLinesDown
+       totalStart := v.topline - synLinesUp
+       totalEnd := v.topline + v.height + synLinesDown
+       if totalStart < 0 {
+               totalStart = 0
+       }
+       if totalEnd > len(buf.lines) {
+               totalEnd = len(buf.lines)
+       }
+
+       str := strings.Join(buf.lines[totalStart:totalEnd], "\n")
+       startNum := ToCharPos(0, totalStart, v.buf)
+
+       toplineNum := ToCharPos(0, v.topline, v.buf)
+
+       for _, rule := range rules {
+               if rule.startend {
+                       if indicies := rule.regex.FindAllStringIndex(str, -1); indicies != nil {
+                               for _, value := range indicies {
+                                       value[0] += startNum
+                                       value[1] += startNum
+                                       for i := value[0]; i < value[1]; i++ {
+                                               if i < toplineNum {
+                                                       continue
+                                               }
+                                               colNum, lineNum := FromCharPosStart(toplineNum, 0, v.topline, i, buf)
+                                               if lineNum == -1 || colNum == -1 {
+                                                       continue
+                                               }
+                                               lineNum -= viewStart
+                                               if lineNum >= 0 && lineNum < v.height {
+                                                       matches[lineNum][colNum] = rule.style
+                                               }
+                                       }
+                               }
+                       }
+               } else {
+                       for lineN, line := range lines {
+                               if indicies := rule.regex.FindAllStringIndex(line, -1); indicies != nil {
+                                       for _, value := range indicies {
+                                               for i := value[0]; i < value[1]; i++ {
+                                                       // matches[lineN+updateStart][i] = rule.style
+                                                       matches[lineN][i] = rule.style
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       return matches
+}
diff --git a/cmd/micro/messenger.go b/cmd/micro/messenger.go
new file mode 100644 (file)
index 0000000..bb30ba2
--- /dev/null
@@ -0,0 +1,192 @@
+package main
+
+import (
+       "bufio"
+       "fmt"
+       "github.com/gdamore/tcell"
+       "os"
+       "strconv"
+)
+
+// TermMessage sends a message to the user in the terminal. This usually occurs before
+// micro has been fully initialized -- ie if there is an error in the syntax highlighting
+// regular expressions
+// The function must be called when the screen is not initialized
+// This will write the message, and wait for the user
+// to press and key to continue
+func TermMessage(msg string) {
+       fmt.Println(msg)
+       fmt.Print("\nPress enter to continue")
+
+       reader := bufio.NewReader(os.Stdin)
+       reader.ReadString('\n')
+}
+
+// TermError sends an error to the user in the terminal. Like TermMessage except formatted
+// as an error
+func TermError(filename string, lineNum int, err string) {
+       TermMessage(filename + ", " + strconv.Itoa(lineNum) + ": " + err)
+}
+
+// Messenger is an object that makes it easy to send messages to the user
+// and get input from the user
+type Messenger struct {
+       // Are we currently prompting the user?
+       hasPrompt bool
+       // Is there a message to print
+       hasMessage bool
+
+       // Message to print
+       message string
+       // The user's response to a prompt
+       response string
+       // style to use when drawing the message
+       style tcell.Style
+
+       // We have to keep track of the cursor for prompting
+       cursorx int
+}
+
+// Message sends a message to the user
+func (m *Messenger) Message(msg string) {
+       m.message = msg
+       m.style = defStyle
+
+       if _, ok := colorscheme["message"]; ok {
+               m.style = colorscheme["message"]
+       }
+       m.hasMessage = true
+}
+
+// Error sends an error message to the user
+func (m *Messenger) Error(msg string) {
+       m.message = msg
+       m.style = defStyle.
+               Foreground(tcell.ColorBlack).
+               Background(tcell.ColorMaroon)
+
+       if _, ok := colorscheme["error-message"]; ok {
+               m.style = colorscheme["error-message"]
+       }
+       m.hasMessage = true
+}
+
+// YesNoPrompt asks the user a yes or no question (waits for y or n) and returns the result
+func (m *Messenger) YesNoPrompt(prompt string) bool {
+       m.Message(prompt)
+
+       for {
+               m.Clear()
+               m.Display()
+               screen.Show()
+               event := screen.PollEvent()
+
+               switch e := event.(type) {
+               case *tcell.EventKey:
+                       if e.Key() == tcell.KeyRune {
+                               if e.Rune() == 'y' {
+                                       return true
+                               } else if e.Rune() == 'n' {
+                                       return false
+                               }
+                       }
+               }
+       }
+}
+
+// Prompt sends the user a message and waits for a response to be typed in
+// This function blocks the main loop while waiting for input
+func (m *Messenger) Prompt(prompt string) (string, bool) {
+       m.hasPrompt = true
+       m.Message(prompt)
+
+       response, canceled := "", true
+
+       for m.hasPrompt {
+               m.Clear()
+               m.Display()
+
+               event := screen.PollEvent()
+
+               switch e := event.(type) {
+               case *tcell.EventKey:
+                       switch e.Key() {
+                       case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
+                               // Cancel
+                               m.hasPrompt = false
+                       case tcell.KeyEnter:
+                               // User is done entering their response
+                               m.hasPrompt = false
+                               response, canceled = m.response, false
+                       }
+               }
+
+               m.HandleEvent(event)
+
+               if m.cursorx < 0 {
+                       // Cancel
+                       m.hasPrompt = false
+               }
+       }
+
+       m.Reset()
+       return response, canceled
+}
+
+// HandleEvent handles an event for the prompter
+func (m *Messenger) HandleEvent(event tcell.Event) {
+       switch e := event.(type) {
+       case *tcell.EventKey:
+               switch e.Key() {
+               case tcell.KeyLeft:
+                       if m.cursorx > 0 {
+                               m.cursorx--
+                       }
+               case tcell.KeyRight:
+                       if m.cursorx < Count(m.response) {
+                               m.cursorx++
+                       }
+               case tcell.KeyBackspace2:
+                       if m.cursorx > 0 {
+                               m.response = string([]rune(m.response)[:m.cursorx-1]) + string(m.response[m.cursorx:])
+                       }
+                       m.cursorx--
+               case tcell.KeySpace:
+                       m.response += " "
+                       m.cursorx++
+               case tcell.KeyRune:
+                       m.response = Insert(m.response, m.cursorx, string(e.Rune()))
+                       m.cursorx++
+               }
+       }
+}
+
+// Reset resets the messenger's cursor, message and response
+func (m *Messenger) Reset() {
+       m.cursorx = 0
+       m.message = ""
+       m.response = ""
+}
+
+// Clear clears the line at the bottom of the editor
+func (m *Messenger) Clear() {
+       w, h := screen.Size()
+       for x := 0; x < w; x++ {
+               screen.SetContent(x, h-1, ' ', nil, defStyle)
+       }
+}
+
+// Display displays messages or prompts
+func (m *Messenger) Display() {
+       _, h := screen.Size()
+       if m.hasMessage {
+               runes := []rune(m.message + m.response)
+               for x := 0; x < len(runes); x++ {
+                       screen.SetContent(x, h-1, runes[x], nil, m.style)
+               }
+       }
+       if m.hasPrompt {
+               screen.ShowCursor(Count(m.message)+m.cursorx, h-1)
+               screen.Show()
+       }
+}
diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go
new file mode 100644 (file)
index 0000000..8733aa4
--- /dev/null
@@ -0,0 +1,173 @@
+package main
+
+import (
+       "fmt"
+       "github.com/gdamore/tcell"
+       "github.com/go-errors/errors"
+       "github.com/mattn/go-isatty"
+       "io/ioutil"
+       "os"
+)
+
+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
+
+       // Object to send messages and prompts to the user
+       messenger *Messenger
+
+       // The default style
+       defStyle tcell.Style
+)
+
+// LoadInput loads the file input for the editor
+func LoadInput() (string, []byte, error) {
+       // 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
+
+       // 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
+       // empty 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
+
+       if len(os.Args) > 1 {
+               // Option 1
+               filename = os.Args[1]
+               // Check that the file exists
+               if _, e := os.Stat(filename); e == nil {
+                       input, err = ioutil.ReadFile(filename)
+               }
+       } else if !isatty.IsTerminal(os.Stdin.Fd()) {
+               // Option 2
+               // The input is not a terminal, so something is being piped in
+               // and we should read from stdin
+               input, err = ioutil.ReadAll(os.Stdin)
+       }
+
+       // Option 3, or just return whatever we got
+       return filename, input, err
+}
+
+func main() {
+       filename, input, err := LoadInput()
+       if err != nil {
+               fmt.Println(err)
+               os.Exit(1)
+       }
+
+       InitSettings()
+
+       // Load the syntax files, including the colorscheme
+       LoadSyntaxFiles()
+
+       // Should we enable true color?
+       truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
+
+       // In order to enable true color, we have to set the TERM to `xterm-truecolor` when
+       // initializing tcell, but after that, we can set the TERM back to whatever it was
+       oldTerm := os.Getenv("TERM")
+       if truecolor {
+               os.Setenv("TERM", "xterm-truecolor")
+       }
+
+       // Initilize tcell
+       screen, err = tcell.NewScreen()
+       if err != nil {
+               fmt.Println(err)
+               os.Exit(1)
+       }
+       if err = screen.Init(); err != nil {
+               fmt.Println(err)
+               os.Exit(1)
+       }
+
+       // Now we can put the TERM back to what it was before
+       if truecolor {
+               os.Setenv("TERM", oldTerm)
+       }
+
+       // This is just so if we have an error, we can exit cleanly and not completely
+       // mess up the terminal being worked in
+       defer func() {
+               if err := recover(); err != nil {
+                       screen.Fini()
+                       fmt.Println("Micro encountered an error:", err)
+                       // Print the stack trace too
+                       fmt.Print(errors.Wrap(err, 2).ErrorStack())
+                       os.Exit(1)
+               }
+       }()
+
+       // Default style
+       defStyle = tcell.StyleDefault.
+               Foreground(tcell.ColorDefault).
+               Background(tcell.ColorDefault)
+
+       // There may be another default style defined in the colorscheme
+       if style, ok := colorscheme["default"]; ok {
+               defStyle = style
+       }
+
+       screen.SetStyle(defStyle)
+       screen.EnableMouse()
+
+       messenger = new(Messenger)
+       view := NewView(NewBuffer(string(input), filename))
+
+       for {
+               // Display everything
+               screen.Clear()
+
+               view.Display()
+               messenger.Display()
+
+               screen.Show()
+
+               // Wait for the user's action
+               event := screen.PollEvent()
+
+               if searching {
+                       HandleSearchEvent(event, view)
+               } else {
+                       // Check if we should quit
+                       switch e := event.(type) {
+                       case *tcell.EventKey:
+                               switch e.Key() {
+                               case tcell.KeyCtrlQ:
+                                       // Make sure not to quit if there are unsaved changes
+                                       if view.CanClose("Quit anyway? ") {
+                                               screen.Fini()
+                                               os.Exit(0)
+                                       }
+                               case tcell.KeyCtrlE:
+                                       input, canceled := messenger.Prompt("> ")
+                                       if !canceled {
+                                               HandleCommand(input, view)
+                                       }
+                               case tcell.KeyCtrlH:
+                                       DisplayHelp()
+                                       // Make sure to resize the view if the user resized the terminal while looking at the help text
+                                       view.Resize(screen.Size())
+                               }
+                       }
+
+                       // Send it to the view
+                       view.HandleEvent(event)
+               }
+       }
+}
diff --git a/cmd/micro/search.go b/cmd/micro/search.go
new file mode 100644 (file)
index 0000000..f5efce6
--- /dev/null
@@ -0,0 +1,116 @@
+package main
+
+import (
+       "github.com/gdamore/tcell"
+       "regexp"
+)
+
+var (
+       // What was the last search
+       lastSearch string
+
+       // Where should we start the search down from (or up from)
+       searchStart int
+
+       // Is there currently a search in progress
+       searching bool
+)
+
+// BeginSearch starts a search
+func BeginSearch() {
+       searching = true
+       messenger.hasPrompt = true
+       messenger.Message("Find: ")
+}
+
+// EndSearch stops the current search
+func EndSearch() {
+       searching = false
+       messenger.hasPrompt = false
+       messenger.Clear()
+       messenger.Reset()
+}
+
+// HandleSearchEvent takes an event and a view and will do a real time match from the messenger's output
+// to the current buffer. It searches down the buffer.
+func HandleSearchEvent(event tcell.Event, v *View) {
+       switch e := event.(type) {
+       case *tcell.EventKey:
+               switch e.Key() {
+               case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape, tcell.KeyEnter:
+                       // Done
+                       EndSearch()
+                       return
+               }
+       }
+
+       messenger.HandleEvent(event)
+
+       if messenger.cursorx < 0 {
+               // Done
+               EndSearch()
+               return
+       }
+
+       if messenger.response == "" {
+               v.cursor.ResetSelection()
+               // We don't end the search though
+               return
+       }
+
+       Search(messenger.response, v, true)
+
+       return
+}
+
+// Search searches in the view for the given regex. The down bool
+// specifies whether it should search down from the searchStart position
+// or up from there
+func Search(searchStr string, v *View, down bool) {
+       if searchStr == "" {
+               return
+       }
+       var str string
+       var charPos int
+       if down {
+               str = v.buf.text[searchStart:]
+               charPos = searchStart
+       } else {
+               str = v.buf.text[:searchStart]
+       }
+       r, err := regexp.Compile(searchStr)
+       if err != nil {
+               return
+       }
+       matches := r.FindAllStringIndex(str, -1)
+       var match []int
+       if matches == nil {
+               // Search the entire buffer now
+               matches = r.FindAllStringIndex(v.buf.text, -1)
+               charPos = 0
+               if matches == nil {
+                       v.cursor.ResetSelection()
+                       return
+               }
+
+               if !down {
+                       match = matches[len(matches)-1]
+               } else {
+                       match = matches[0]
+               }
+       }
+
+       if !down {
+               match = matches[len(matches)-1]
+       } else {
+               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)
+       if v.Relocate() {
+               v.matches = Match(v)
+       }
+       lastSearch = searchStr
+}
diff --git a/cmd/micro/settings.go b/cmd/micro/settings.go
new file mode 100644 (file)
index 0000000..0e026f5
--- /dev/null
@@ -0,0 +1,123 @@
+package main
+
+import (
+       "encoding/json"
+       "github.com/mitchellh/go-homedir"
+       "io/ioutil"
+       "os"
+       "strconv"
+       "strings"
+)
+
+// The options that the user can set
+var settings Settings
+
+// All the possible settings
+var possibleSettings = []string{"colorscheme", "tabsize", "autoindent", "syntax"}
+
+// The Settings struct contains the settings for micro
+type Settings struct {
+       Colorscheme string `json:"colorscheme"`
+       TabSize     int    `json:"tabsize"`
+       AutoIndent  bool   `json:"autoindent"`
+       Syntax      bool   `json:"syntax"`
+}
+
+// InitSettings initializes the options map and sets all options to their default values
+func InitSettings() {
+       home, err := homedir.Dir()
+       if err != nil {
+               TermMessage("Error finding your home directory\nCan't load settings file")
+               return
+       }
+
+       filename := home + "/.micro/settings.json"
+       if _, e := os.Stat(filename); e == nil {
+               input, err := ioutil.ReadFile(filename)
+               if err != nil {
+                       TermMessage("Error reading settings.json file: " + err.Error())
+                       return
+               }
+
+               json.Unmarshal(input, &settings)
+       } else {
+               settings = DefaultSettings()
+               err := WriteSettings(filename)
+               if err != nil {
+                       TermMessage("Error writing settings.json file: " + err.Error())
+               }
+       }
+}
+
+// WriteSettings writes the settings to the specified filename as JSON
+func WriteSettings(filename string) error {
+       var err error
+       home, err := homedir.Dir()
+       if err != nil {
+               return err
+       }
+       if _, e := os.Stat(home + "/.micro"); e == nil {
+               txt, _ := json.MarshalIndent(settings, "", "    ")
+               err = ioutil.WriteFile(filename, txt, 0644)
+       }
+       return err
+}
+
+// DefaultSettings returns the default settings for micro
+func DefaultSettings() Settings {
+       return Settings{
+               Colorscheme: "default",
+               TabSize:     4,
+               AutoIndent:  true,
+               Syntax:      true,
+       }
+}
+
+// SetOption prompts the user to set an option and checks that the response is valid
+func SetOption(view *View, args []string) {
+       home, err := homedir.Dir()
+       if err != nil {
+               messenger.Error("Error finding your home directory\nCan't load settings file")
+       }
+
+       filename := home + "/.micro/settings.json"
+       if len(args) == 2 {
+               option := strings.TrimSpace(args[0])
+               value := strings.TrimSpace(args[1])
+
+               if Contains(possibleSettings, option) {
+                       if option == "tabsize" {
+                               tsize, err := strconv.Atoi(value)
+                               if err != nil {
+                                       messenger.Error("Invalid value for " + option)
+                                       return
+                               }
+                               settings.TabSize = tsize
+                       } else if option == "colorscheme" {
+                               settings.Colorscheme = value
+                               LoadSyntaxFiles()
+                               view.buf.UpdateRules()
+                       } else if option == "syntax" {
+                               if value == "on" {
+                                       settings.Syntax = true
+                               } else if value == "off" {
+                                       settings.Syntax = false
+                               } else {
+                                       messenger.Error("Invalid value for " + option)
+                                       return
+                               }
+                               LoadSyntaxFiles()
+                               view.buf.UpdateRules()
+                       }
+                       err := WriteSettings(filename)
+                       if err != nil {
+                               messenger.Error("Error writing to settings.json: " + err.Error())
+                               return
+                       }
+               } else {
+                       messenger.Error("Option " + option + " does not exist")
+               }
+       } else {
+               messenger.Error("Invalid option, please use option value")
+       }
+}
diff --git a/cmd/micro/stack.go b/cmd/micro/stack.go
new file mode 100644 (file)
index 0000000..c7c8fd7
--- /dev/null
@@ -0,0 +1,43 @@
+package main
+
+// Stack is a simple implementation of a LIFO stack
+type Stack struct {
+       top  *Element
+       size int
+}
+
+// An Element which is stored in the Stack
+type Element struct {
+       value interface{} // All types satisfy the empty interface, so we can store anything here.
+       next  *Element
+}
+
+// Len returns the stack's length
+func (s *Stack) Len() int {
+       return s.size
+}
+
+// Push a new element onto the stack
+func (s *Stack) Push(value interface{}) {
+       s.top = &Element{value, s.top}
+       s.size++
+}
+
+// Pop removes the top element from the stack and returns its value
+// If the stack is empty, return nil
+func (s *Stack) Pop() (value interface{}) {
+       if s.size > 0 {
+               value, s.top = s.top.value, s.top.next
+               s.size--
+               return
+       }
+       return nil
+}
+
+// Peek returns the top element of the stack without removing it
+func (s *Stack) Peek() interface{} {
+       if s.size > 0 {
+               return s.top.value
+       }
+       return nil
+}
diff --git a/cmd/micro/stack_test.go b/cmd/micro/stack_test.go
new file mode 100644 (file)
index 0000000..a352694
--- /dev/null
@@ -0,0 +1,39 @@
+package main
+
+import "testing"
+
+func TestStack(t *testing.T) {
+       stack := new(Stack)
+
+       if stack.Len() != 0 {
+               t.Errorf("Len failed")
+       }
+       stack.Push(5)
+       stack.Push("test")
+       stack.Push(10)
+       if stack.Len() != 3 {
+               t.Errorf("Len failed")
+       }
+
+       var popped interface{}
+       popped = stack.Pop()
+       if popped != 10 {
+               t.Errorf("Pop failed")
+       }
+
+       popped = stack.Pop()
+       if popped != "test" {
+               t.Errorf("Pop failed")
+       }
+
+       stack.Push("test")
+       popped = stack.Pop()
+       if popped != "test" {
+               t.Errorf("Pop failed")
+       }
+       stack.Pop()
+       popped = stack.Pop()
+       if popped != nil {
+               t.Errorf("Pop failed")
+       }
+}
diff --git a/cmd/micro/statusline.go b/cmd/micro/statusline.go
new file mode 100644 (file)
index 0000000..f02d499
--- /dev/null
@@ -0,0 +1,61 @@
+package main
+
+import (
+       "strconv"
+)
+
+// Statusline represents the information line at the bottom
+// of each view
+// It gives information such as filename, whether the file has been
+// modified, filetype, cursor location
+type Statusline struct {
+       view *View
+}
+
+// Display draws the statusline to the screen
+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
+       // 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() {
+               file += " +"
+       }
+
+       // Add one to cursor.x and cursor.y because (0,0) is the top left,
+       // 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)
+
+       file += " (" + lineNum + "," + columnNum + ")"
+
+       // Add the filetype
+       file += " " + sline.view.buf.filetype
+
+       centerText := "Press Ctrl-h for help"
+
+       statusLineStyle := defStyle.Reverse(true)
+       if style, ok := colorscheme["statusline"]; ok {
+               statusLineStyle = style
+       }
+
+       // Maybe there is a unicode filename?
+       fileRunes := []rune(file)
+       for x := 0; x < sline.view.width; x++ {
+               if x < len(fileRunes) {
+                       screen.SetContent(x, y, fileRunes[x], nil, statusLineStyle)
+               } else if x >= sline.view.width/2-len(centerText)/2 && x < len(centerText)+sline.view.width/2-len(centerText)/2 {
+                       screen.SetContent(x, y, []rune(centerText)[x-sline.view.width/2+len(centerText)/2], nil, statusLineStyle)
+               } else {
+                       screen.SetContent(x, y, ' ', nil, statusLineStyle)
+               }
+       }
+}
diff --git a/cmd/micro/util.go b/cmd/micro/util.go
new file mode 100644 (file)
index 0000000..069aff6
--- /dev/null
@@ -0,0 +1,77 @@
+package main
+
+import (
+       "unicode/utf8"
+)
+
+// Util.go is a collection of utility functions that are used throughout
+// the program
+
+// Count returns the length of a string in runes
+// This is exactly equivalent to utf8.RuneCountInString(), just less characters
+func Count(s string) int {
+       return utf8.RuneCountInString(s)
+}
+
+// NumOccurences counts the number of occurences of a byte in a string
+func NumOccurences(s string, c byte) int {
+       var n int
+       for i := 0; i < len(s); i++ {
+               if s[i] == c {
+                       n++
+               }
+       }
+       return n
+}
+
+// Spaces returns a string with n spaces
+func Spaces(n int) string {
+       var str string
+       for i := 0; i < n; i++ {
+               str += " "
+       }
+       return str
+}
+
+// Min takes the min of two ints
+func Min(a, b int) int {
+       if a > b {
+               return b
+       }
+       return a
+}
+
+// Max takes the max of two ints
+func Max(a, b int) int {
+       if a > b {
+               return a
+       }
+       return b
+}
+
+// IsWordChar returns whether or not the string is a 'word character'
+// If it is a unicode character, then it does not match
+// Word characters are defined as [A-Za-z0-9_]
+func IsWordChar(str string) bool {
+       if len(str) > 1 {
+               // Unicode
+               return false
+       }
+       c := str[0]
+       return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
+}
+
+// Contains returns whether or not a string array contains a given string
+func Contains(list []string, a string) bool {
+       for _, b := range list {
+               if b == a {
+                       return true
+               }
+       }
+       return false
+}
+
+// Insert makes a simple insert into a string at the given position
+func Insert(str string, pos int, value string) string {
+       return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
+}
diff --git a/cmd/micro/util_test.go b/cmd/micro/util_test.go
new file mode 100644 (file)
index 0000000..29fc009
--- /dev/null
@@ -0,0 +1,65 @@
+package main
+
+import "testing"
+
+func TestNumOccurences(t *testing.T) {
+       var tests = []struct {
+               inputStr  string
+               inputChar byte
+               want      int
+       }{
+               {"aaaa", 'a', 4},
+               {"\trfd\ta", '\t', 2},
+               {"∆ƒ\tø Â®Â \t\t", '\t', 3},
+       }
+       for _, test := range tests {
+               if got := NumOccurences(test.inputStr, test.inputChar); got != test.want {
+                       t.Errorf("NumOccurences(%s, %c) = %d", test.inputStr, test.inputChar, got)
+               }
+       }
+}
+
+func TestSpaces(t *testing.T) {
+       var tests = []struct {
+               input int
+               want  string
+       }{
+               {4, "    "},
+               {0, ""},
+       }
+       for _, test := range tests {
+               if got := Spaces(test.input); got != test.want {
+                       t.Errorf("Spaces(%d) = \"%s\"", test.input, got)
+               }
+       }
+}
+
+func TestIsWordChar(t *testing.T) {
+       if IsWordChar("t") == false {
+               t.Errorf("IsWordChar(t) = false")
+       }
+       if IsWordChar("T") == false {
+               t.Errorf("IsWordChar(T) = false")
+       }
+       if IsWordChar("5") == false {
+               t.Errorf("IsWordChar(5) = false")
+       }
+       if IsWordChar("_") == false {
+               t.Errorf("IsWordChar(_) = false")
+       }
+       if IsWordChar("~") == true {
+               t.Errorf("IsWordChar(~) = true")
+       }
+       if IsWordChar(" ") == true {
+               t.Errorf("IsWordChar( ) = true")
+       }
+       if IsWordChar("ß") == true {
+               t.Errorf("IsWordChar(ß) = true")
+       }
+       if IsWordChar(")") == true {
+               t.Errorf("IsWordChar()) = true")
+       }
+       if IsWordChar("\n") == true {
+               t.Errorf("IsWordChar(\n)) = true")
+       }
+}
diff --git a/cmd/micro/view.go b/cmd/micro/view.go
new file mode 100644 (file)
index 0000000..347d50e
--- /dev/null
@@ -0,0 +1,737 @@
+package main
+
+import (
+       "github.com/atotto/clipboard"
+       "github.com/gdamore/tcell"
+       "io/ioutil"
+       "strconv"
+       "strings"
+       "time"
+)
+
+// The View struct stores information about a view into a buffer.
+// It has a stores information about the cursor, and the viewport
+// that the user sees the buffer from.
+type View struct {
+       cursor Cursor
+
+       // The topmost line, used for vertical scrolling
+       topline int
+       // The leftmost column, used for horizontal scrolling
+       leftCol int
+
+       // Percentage of the terminal window that this view takes up (from 0 to 100)
+       widthPercent  int
+       heightPercent int
+
+       // Actual with and height
+       width  int
+       height int
+
+       // How much to offset because of line numbers
+       lineNumOffset int
+
+       // The eventhandler for undo/redo
+       eh *EventHandler
+
+       // The buffer
+       buf *Buffer
+       // The statusline
+       sline Statusline
+
+       // Since tcell doesn't differentiate between a mouse release event
+       // and a mouse move event with no keys pressed, we need to keep
+       // track of whether or not the mouse was pressed (or not released) last event to determine
+       // mouse release events
+       mouseReleased bool
+
+       // This stores when the last click was
+       // This is useful for detecting double and triple clicks
+       lastClickTime time.Time
+
+       // Was the last mouse event actually a double click?
+       // Useful for detecting triple clicks -- if a double click is detected
+       // but the last mouse event was actually a double click, it's a triple click
+       doubleClick bool
+       // Same here, just to keep track for mouse move events
+       tripleClick bool
+
+       // Syntax highlighting matches
+       matches SyntaxMatches
+       // The matches from the last frame
+       lastMatches SyntaxMatches
+
+       // This is the range of lines that should have their syntax highlighting updated
+       updateLines [2]int
+}
+
+// NewView returns a new fullscreen view
+func NewView(buf *Buffer) *View {
+       return NewViewWidthHeight(buf, 100, 100)
+}
+
+// NewViewWidthHeight returns a new view with the specified width and height percentages
+// Note that w and h are percentages not actual values
+func NewViewWidthHeight(buf *Buffer, w, h int) *View {
+       v := new(View)
+
+       v.buf = buf
+
+       v.widthPercent = w
+       v.heightPercent = h
+       v.Resize(screen.Size())
+
+       v.topline = 0
+       // Put the cursor at the first spot
+       v.cursor = Cursor{
+               x: 0,
+               y: 0,
+               v: v,
+       }
+       v.cursor.ResetSelection()
+
+       v.eh = NewEventHandler(v)
+
+       v.sline = Statusline{
+               view: v,
+       }
+
+       // Update the syntax highlighting for the entire buffer at the start
+       v.UpdateLines(v.topline, v.topline+v.height)
+       v.matches = Match(v)
+
+       // Set mouseReleased to true because we assume the mouse is not being pressed when
+       // the editor is opened
+       v.mouseReleased = true
+       v.lastClickTime = time.Time{}
+
+       return v
+}
+
+// UpdateLines sets the values for v.updateLines
+func (v *View) UpdateLines(start, end int) {
+       v.updateLines[0] = start
+       v.updateLines[1] = end + 1
+}
+
+// Resize recalculates the actual width and height of the view from the width and height
+// percentages
+// This is usually called when the window is resized, or when a split has been added and
+// the percentages have changed
+func (v *View) Resize(w, h int) {
+       // Always include 1 line for the command line at the bottom
+       h--
+       v.width = int(float32(w) * float32(v.widthPercent) / 100)
+       // We subtract 1 for the statusline
+       v.height = int(float32(h)*float32(v.heightPercent)/100) - 1
+}
+
+// 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--
+       }
+}
+
+// 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 <= len(v.buf.lines)-v.height {
+               v.topline += n
+       } else if v.topline < len(v.buf.lines)-v.height {
+               v.topline++
+       }
+}
+
+// PageUp scrolls the view up a page
+func (v *View) PageUp() {
+       if v.topline > v.height {
+               v.ScrollUp(v.height)
+       } else {
+               v.topline = 0
+       }
+}
+
+// PageDown scrolls the view down a page
+func (v *View) PageDown() {
+       if len(v.buf.lines)-(v.topline+v.height) > v.height {
+               v.ScrollDown(v.height)
+       } else {
+               if len(v.buf.lines) >= v.height {
+                       v.topline = len(v.buf.lines) - v.height
+               }
+       }
+}
+
+// HalfPageUp scrolls the view up half a page
+func (v *View) HalfPageUp() {
+       if v.topline > v.height/2 {
+               v.ScrollUp(v.height / 2)
+       } else {
+               v.topline = 0
+       }
+}
+
+// HalfPageDown scrolls the view down half a page
+func (v *View) HalfPageDown() {
+       if len(v.buf.lines)-(v.topline+v.height) > v.height/2 {
+               v.ScrollDown(v.height / 2)
+       } else {
+               if len(v.buf.lines) >= v.height {
+                       v.topline = len(v.buf.lines) - v.height
+               }
+       }
+}
+
+// CanClose returns whether or not the view can be closed
+// If there are unsaved changes, the user will be asked if the view can be closed
+// 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() {
+               quit, canceled := messenger.Prompt("You have unsaved changes. " + msg)
+               if !canceled {
+                       if strings.ToLower(quit) == "yes" || strings.ToLower(quit) == "y" {
+                               return true
+                       }
+               }
+       } else {
+               return true
+       }
+       return false
+}
+
+// Save the buffer to disk
+func (v *View) Save() {
+       // If this is an empty buffer, ask for a filename
+       if v.buf.path == "" {
+               filename, canceled := messenger.Prompt("Filename: ")
+               if !canceled {
+                       v.buf.path = filename
+                       v.buf.name = filename
+               } else {
+                       return
+               }
+       }
+       err := v.buf.Save()
+       if err != nil {
+               messenger.Error(err.Error())
+       } else {
+               messenger.Message("Saved " + v.buf.path)
+       }
+}
+
+// Copy the selection to the system clipboard
+func (v *View) Copy() {
+       if v.cursor.HasSelection() {
+               if !clipboard.Unsupported {
+                       clipboard.WriteAll(v.cursor.GetSelection())
+               } else {
+                       messenger.Error("Clipboard is not supported on your system")
+               }
+       }
+}
+
+// Cut the selection to the system clipboard
+func (v *View) Cut() {
+       if v.cursor.HasSelection() {
+               if !clipboard.Unsupported {
+                       clipboard.WriteAll(v.cursor.GetSelection())
+                       v.cursor.DeleteSelection()
+                       v.cursor.ResetSelection()
+               } else {
+                       messenger.Error("Clipboard is not supported on your system")
+               }
+       }
+}
+
+// Paste whatever is in the system clipboard into the buffer
+// Delete and paste if the user has a selection
+func (v *View) Paste() {
+       if !clipboard.Unsupported {
+               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))
+       } else {
+               messenger.Error("Clipboard is not supported on your system")
+       }
+}
+
+// SelectAll selects the entire buffer
+func (v *View) SelectAll() {
+       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
+}
+
+// OpenFile opens a new file in the current view
+// It makes sure that the current buffer can be closed first (unsaved changes)
+func (v *View) OpenFile() {
+       if v.CanClose("Continue? ") {
+               filename, canceled := messenger.Prompt("File to open: ")
+               if canceled {
+                       return
+               }
+               file, err := ioutil.ReadFile(filename)
+
+               if err != nil {
+                       messenger.Error(err.Error())
+                       return
+               }
+               v.buf = NewBuffer(string(file), filename)
+       }
+}
+
+// Relocate moves the view window so that the cursor is in view
+// 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
+               ret = true
+       }
+       if cy > v.topline+v.height-1 {
+               v.topline = cy - v.height + 1
+               ret = true
+       }
+
+       cx := v.cursor.GetVisualX()
+       if cx < v.leftCol {
+               v.leftCol = cx
+               ret = true
+       }
+       if cx+v.lineNumOffset+1 > v.leftCol+v.width {
+               v.leftCol = cx - v.width + v.lineNumOffset + 1
+               ret = true
+       }
+       return ret
+}
+
+// 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 {
+               v.ScrollDown(1)
+               y = v.height + v.topline - 1
+       }
+       if y >= len(v.buf.lines) {
+               y = len(v.buf.lines) - 1
+       }
+       if x < 0 {
+               x = 0
+       }
+
+       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()
+}
+
+// HandleEvent handles an event passed by the main loop
+func (v *View) HandleEvent(event tcell.Event) {
+       // This bool determines whether the view is relocated at the end of the function
+       // By default it's true because most events should cause a relocate
+       relocate := true
+
+       // By default we don't update and syntax highlighting
+       v.UpdateLines(-2, 0)
+       switch e := event.(type) {
+       case *tcell.EventResize:
+               // Window resized
+               v.Resize(e.Size())
+       case *tcell.EventKey:
+               switch e.Key() {
+               case tcell.KeyUp:
+                       // Cursor up
+                       v.cursor.ResetSelection()
+                       v.cursor.Up()
+               case tcell.KeyDown:
+                       // Cursor down
+                       v.cursor.ResetSelection()
+                       v.cursor.Down()
+               case tcell.KeyLeft:
+                       // Cursor left
+                       v.cursor.ResetSelection()
+                       v.cursor.Left()
+               case tcell.KeyRight:
+                       // Cursor right
+                       v.cursor.ResetSelection()
+                       v.cursor.Right()
+               case tcell.KeyEnter:
+                       // Insert a newline
+                       if v.cursor.HasSelection() {
+                               v.cursor.DeleteSelection()
+                               v.cursor.ResetSelection()
+                       }
+                       v.eh.Insert(v.cursor.Loc(), "\n")
+                       v.cursor.Right()
+                       // Rehighlight the entire buffer
+                       v.UpdateLines(v.topline, v.topline+v.height)
+                       v.cursor.lastVisualX = v.cursor.GetVisualX()
+                       // v.UpdateLines(v.cursor.y-1, v.cursor.y)
+               case tcell.KeySpace:
+                       // Insert a space
+                       if v.cursor.HasSelection() {
+                               v.cursor.DeleteSelection()
+                               v.cursor.ResetSelection()
+                       }
+                       v.eh.Insert(v.cursor.Loc(), " ")
+                       v.cursor.Right()
+                       v.UpdateLines(v.cursor.y, v.cursor.y)
+               case tcell.KeyBackspace2:
+                       // Delete a character
+                       if v.cursor.HasSelection() {
+                               v.cursor.DeleteSelection()
+                               v.cursor.ResetSelection()
+                               // Rehighlight the entire buffer
+                               v.UpdateLines(v.topline, v.topline+v.height)
+                       } 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
+                               // So instead we move left, save the position, move back, delete
+                               // and restore the position
+                               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
+                               // Rehighlight the entire buffer
+                               v.UpdateLines(v.topline, v.topline+v.height)
+                               // v.UpdateLines(v.cursor.y, v.cursor.y+1)
+                       }
+                       v.cursor.lastVisualX = v.cursor.GetVisualX()
+               case tcell.KeyTab:
+                       // Insert a tab
+                       if v.cursor.HasSelection() {
+                               v.cursor.DeleteSelection()
+                               v.cursor.ResetSelection()
+                       }
+                       v.eh.Insert(v.cursor.Loc(), "\t")
+                       v.cursor.Right()
+                       v.UpdateLines(v.cursor.y, v.cursor.y)
+               case tcell.KeyCtrlS:
+                       v.Save()
+               case tcell.KeyCtrlF:
+                       if v.cursor.HasSelection() {
+                               searchStart = v.cursor.curSelection[1]
+                       } else {
+                               searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
+                       }
+                       BeginSearch()
+               case tcell.KeyCtrlN:
+                       if v.cursor.HasSelection() {
+                               searchStart = v.cursor.curSelection[1]
+                       } else {
+                               searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
+                       }
+                       messenger.Message("Find: " + lastSearch)
+                       Search(lastSearch, v, true)
+               case tcell.KeyCtrlP:
+                       if v.cursor.HasSelection() {
+                               searchStart = v.cursor.curSelection[0]
+                       } else {
+                               searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
+                       }
+                       messenger.Message("Find: " + lastSearch)
+                       Search(lastSearch, v, false)
+               case tcell.KeyCtrlZ:
+                       v.eh.Undo()
+                       // Rehighlight the entire buffer
+                       v.UpdateLines(v.topline, v.topline+v.height)
+               case tcell.KeyCtrlY:
+                       v.eh.Redo()
+                       // Rehighlight the entire buffer
+                       v.UpdateLines(v.topline, v.topline+v.height)
+               case tcell.KeyCtrlC:
+                       v.Copy()
+                       // Rehighlight the entire buffer
+                       v.UpdateLines(v.topline, v.topline+v.height)
+               case tcell.KeyCtrlX:
+                       v.Cut()
+                       // Rehighlight the entire buffer
+                       v.UpdateLines(v.topline, v.topline+v.height)
+               case tcell.KeyCtrlV:
+                       v.Paste()
+                       // Rehighlight the entire buffer
+                       v.UpdateLines(v.topline, v.topline+v.height)
+               case tcell.KeyCtrlA:
+                       v.SelectAll()
+               case tcell.KeyCtrlO:
+                       v.OpenFile()
+                       // Rehighlight the entire buffer
+                       v.UpdateLines(v.topline, v.topline+v.height)
+               case tcell.KeyPgUp:
+                       v.PageUp()
+                       relocate = false
+               case tcell.KeyPgDn:
+                       v.PageDown()
+                       relocate = false
+               case tcell.KeyCtrlU:
+                       v.HalfPageUp()
+                       relocate = false
+               case tcell.KeyCtrlD:
+                       v.HalfPageDown()
+                       relocate = false
+               case tcell.KeyRune:
+                       // Insert a character
+                       if v.cursor.HasSelection() {
+                               v.cursor.DeleteSelection()
+                               v.cursor.ResetSelection()
+                               // Rehighlight the entire buffer
+                               v.UpdateLines(v.topline, v.topline+v.height)
+                       } else {
+                               v.UpdateLines(v.cursor.y, v.cursor.y)
+                       }
+                       v.eh.Insert(v.cursor.Loc(), string(e.Rune()))
+                       v.cursor.Right()
+               }
+       case *tcell.EventMouse:
+               x, y := e.Position()
+               x -= v.lineNumOffset - v.leftCol
+               y += v.topline
+               // Position always seems to be off by one
+               x--
+               y--
+
+               button := e.Buttons()
+
+               switch button {
+               case tcell.Button1:
+                       // Left click
+                       origX, origY := v.cursor.x, v.cursor.y
+                       v.MoveToMouseClick(x, y)
+
+                       if v.mouseReleased {
+                               if (time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold) &&
+                                       (origX == v.cursor.x && origY == v.cursor.y) {
+                                       if v.doubleClick {
+                                               // Triple click
+                                               v.lastClickTime = time.Now()
+
+                                               v.tripleClick = true
+                                               v.doubleClick = false
+
+                                               v.cursor.SelectLine()
+                                       } else {
+                                               // Double click
+                                               v.lastClickTime = time.Now()
+
+                                               v.doubleClick = true
+                                               v.tripleClick = false
+
+                                               v.cursor.SelectWord()
+                                       }
+                               } else {
+                                       v.doubleClick = false
+                                       v.tripleClick = false
+                                       v.lastClickTime = time.Now()
+
+                                       loc := v.cursor.Loc()
+                                       v.cursor.curSelection[0] = loc
+                                       v.cursor.curSelection[1] = loc
+                               }
+                       } else {
+                               if v.tripleClick {
+                                       v.cursor.AddLineToSelection()
+                               } else if v.doubleClick {
+                                       v.cursor.AddWordToSelection()
+                               } else {
+                                       v.cursor.curSelection[1] = v.cursor.Loc()
+                               }
+                       }
+                       v.mouseReleased = false
+               case tcell.ButtonNone:
+                       // Mouse event with no click
+                       if !v.mouseReleased {
+                               // Mouse was just released
+
+                               // Relocating here isn't really necessary because the cursor will
+                               // be in the right place from the last mouse event
+                               // However, if we are running in a terminal that doesn't support mouse motion
+                               // events, this still allows the user to make selections, except only after they
+                               // release the mouse
+
+                               if !v.doubleClick && !v.tripleClick {
+                                       v.MoveToMouseClick(x, y)
+                                       v.cursor.curSelection[1] = v.cursor.Loc()
+                               }
+                               v.mouseReleased = true
+                       }
+                       // We don't want to relocate because otherwise the view will be relocated
+                       // every time the user moves the cursor
+                       relocate = false
+               case tcell.WheelUp:
+                       // Scroll up two lines
+                       v.ScrollUp(2)
+                       // We don't want to relocate if the user is scrolling
+                       relocate = false
+                       // Rehighlight the entire buffer
+                       v.UpdateLines(v.topline, v.topline+v.height)
+               case tcell.WheelDown:
+                       // Scroll down two lines
+                       v.ScrollDown(2)
+                       // We don't want to relocate if the user is scrolling
+                       relocate = false
+                       // Rehighlight the entire buffer
+                       v.UpdateLines(v.topline, v.topline+v.height)
+               }
+       }
+
+       if relocate {
+               v.Relocate()
+       }
+       if settings.Syntax {
+               v.matches = Match(v)
+       }
+}
+
+// DisplayView renders the view to the screen
+func (v *View) DisplayView() {
+       // matches := make(SyntaxMatches, len(v.buf.lines))
+       //
+       // viewStart := v.topline
+       // viewEnd := v.topline + v.height
+       // if viewEnd > len(v.buf.lines) {
+       //      viewEnd = len(v.buf.lines)
+       // }
+       //
+       // lines := v.buf.lines[viewStart:viewEnd]
+       // for i, line := range lines {
+       //      matches[i] = make([]tcell.Style, len(line))
+       // }
+
+       // The character number of the character in the top left of the screen
+
+       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(len(v.buf.lines)))
+       // + 1 for the little space after the line number
+       v.lineNumOffset = maxLineLength + 1
+
+       var highlightStyle tcell.Style
+
+       for lineN := 0; lineN < v.height; lineN++ {
+               var x int
+               // If the buffer is smaller than the view height
+               // and we went too far, break
+               if lineN+v.topline >= len(v.buf.lines) {
+                       break
+               }
+               line := v.buf.lines[lineN+v.topline]
+
+               // Write the line number
+               lineNumStyle := defStyle
+               if style, ok := colorscheme["line-number"]; ok {
+                       lineNumStyle = style
+               }
+               // Write the spaces before the line number if necessary
+               lineNum := strconv.Itoa(lineN + v.topline + 1)
+               for i := 0; i < maxLineLength-len(lineNum); i++ {
+                       screen.SetContent(x, lineN, ' ', nil, lineNumStyle)
+                       x++
+               }
+               // Write the actual line number
+               for _, ch := range lineNum {
+                       screen.SetContent(x, lineN, ch, nil, lineNumStyle)
+                       x++
+               }
+               // Write the extra space
+               screen.SetContent(x, lineN, ' ', nil, lineNumStyle)
+               x++
+
+               // Write the line
+               tabchars := 0
+               runes := []rune(line)
+               for colN := v.leftCol; colN < v.leftCol+v.width; colN++ {
+                       if colN >= len(runes) {
+                               break
+                       }
+                       ch := runes[colN]
+                       var lineStyle tcell.Style
+                       // Does the current character need to be syntax highlighted?
+
+                       // if lineN >= v.updateLines[0] && lineN < v.updateLines[1] {
+                       if settings.Syntax {
+                               highlightStyle = v.matches[lineN][colN]
+                       }
+                       // } else if lineN < len(v.lastMatches) && colN < len(v.lastMatches[lineN]) {
+                       // highlightStyle = v.lastMatches[lineN][colN]
+                       // } else {
+                       // highlightStyle = defStyle
+                       // }
+
+                       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 = defStyle.Reverse(true)
+
+                               if style, ok := colorscheme["selection"]; ok {
+                                       lineStyle = style
+                               }
+                       } else {
+                               lineStyle = highlightStyle
+                       }
+                       // matches[lineN][colN] = highlightStyle
+
+                       if ch == '\t' {
+                               screen.SetContent(x+tabchars, lineN, ' ', nil, lineStyle)
+                               tabSize := settings.TabSize
+                               for i := 0; i < tabSize-1; i++ {
+                                       tabchars++
+                                       if x-v.leftCol+tabchars >= v.lineNumOffset {
+                                               screen.SetContent(x-v.leftCol+tabchars, lineN, ' ', nil, lineStyle)
+                                       }
+                               }
+                       } else {
+                               if x-v.leftCol+tabchars >= v.lineNumOffset {
+                                       screen.SetContent(x-v.leftCol+tabchars, lineN, ch, nil, lineStyle)
+                               }
+                       }
+                       charNum++
+                       x++
+               }
+               // Here we are at a newline
+
+               // 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]) {
+
+                       selectStyle := defStyle.Reverse(true)
+
+                       if style, ok := colorscheme["selection"]; ok {
+                               selectStyle = style
+                       }
+                       screen.SetContent(x-v.leftCol+tabchars, lineN, ' ', nil, selectStyle)
+               }
+
+               charNum++
+       }
+       // v.lastMatches = matches
+}
+
+// Display renders the view, the cursor, and statusline
+func (v *View) Display() {
+       v.DisplayView()
+       v.cursor.Display()
+       v.sline.Display()
+}
diff --git a/src/buffer.go b/src/buffer.go
deleted file mode 100644 (file)
index 66b02af..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-package main
-
-import (
-       "github.com/vinzmay/go-rope"
-       "io/ioutil"
-       "strings"
-)
-
-// Buffer stores the text for files that are loaded into the text editor
-// It uses a rope to efficiently store the string and contains some
-// simple functions for saving and wrapper functions for modifying the rope
-type Buffer struct {
-       // Stores the text of the buffer
-       r *rope.Rope
-
-       // Path to the file on disk
-       path string
-       // Name of the buffer on the status line
-       name string
-
-       // This is the text stored every time the buffer is saved to check if the buffer is modified
-       savedText string
-
-       // 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
-       text  string
-       lines []string
-
-       // Syntax highlighting rules
-       rules []SyntaxRule
-       // The buffer's filetype
-       filetype string
-}
-
-// NewBuffer creates a new buffer from `txt` with path and name `path`
-func NewBuffer(txt, path string) *Buffer {
-       b := new(Buffer)
-       if txt == "" {
-               b.r = new(rope.Rope)
-       } else {
-               b.r = rope.New(txt)
-       }
-       b.path = path
-       b.name = path
-       b.savedText = txt
-
-       b.Update()
-       b.UpdateRules()
-
-       return b
-}
-
-// 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)
-}
-
-// Update fetches the string from the rope and updates the `text` and `lines` in the buffer
-func (b *Buffer) Update() {
-       if b.r.Len() == 0 {
-               b.text = ""
-       } else {
-               b.text = b.r.String()
-       }
-       b.lines = strings.Split(b.text, "\n")
-}
-
-// Save saves the buffer to its default path
-func (b *Buffer) Save() error {
-       return b.SaveAs(b.path)
-}
-
-// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
-func (b *Buffer) SaveAs(filename string) error {
-       b.UpdateRules()
-       err := ioutil.WriteFile(filename, []byte(b.text), 0644)
-       if err == nil {
-               b.savedText = b.text
-       }
-       return err
-}
-
-// IsDirty returns whether or not the buffer has been modified compared to the one on disk
-func (b *Buffer) IsDirty() bool {
-       return b.savedText != b.text
-}
-
-// Insert a string into the rope
-func (b *Buffer) Insert(idx int, value string) {
-       b.r = b.r.Insert(idx, value)
-       b.Update()
-}
-
-// Remove a slice of the rope from start to end (exclusive)
-// Returns the string that was removed
-func (b *Buffer) Remove(start, end int) string {
-       if start < 0 {
-               start = 0
-       }
-       if end > b.Len() {
-               end = b.Len()
-       }
-       removed := b.text[start:end]
-       // The rope implenentation I am using wants indicies starting at 1 instead of 0
-       start++
-       end++
-       b.r = b.r.Delete(start, end-start)
-       b.Update()
-       return removed
-}
-
-// Len gives the length of the buffer
-func (b *Buffer) Len() int {
-       return b.r.Len()
-}
diff --git a/src/colorscheme.go b/src/colorscheme.go
deleted file mode 100644 (file)
index 450da38..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-package main
-
-import (
-       "fmt"
-       "github.com/gdamore/tcell"
-       "github.com/mitchellh/go-homedir"
-       "io/ioutil"
-       "regexp"
-       "strconv"
-       "strings"
-)
-
-// Colorscheme is a map from string to style -- it represents a colorscheme
-type Colorscheme map[string]tcell.Style
-
-// The current colorscheme
-var colorscheme Colorscheme
-
-// InitColorscheme picks and initializes the colorscheme when micro starts
-func InitColorscheme() {
-       LoadDefaultColorscheme()
-}
-
-// LoadDefaultColorscheme loads the default colorscheme from ~/.micro/colorschemes
-func LoadDefaultColorscheme() {
-       dir, err := homedir.Dir()
-       if err != nil {
-               TermMessage("Error finding your home directory\nCan't load runtime files")
-               return
-       }
-       LoadColorscheme(settings.Colorscheme, dir+"/.micro/colorschemes")
-}
-
-// LoadColorscheme loads the given colorscheme from a directory
-func LoadColorscheme(colorschemeName, dir string) {
-       files, _ := ioutil.ReadDir(dir)
-       for _, f := range files {
-               if f.Name() == colorschemeName+".micro" {
-                       text, err := ioutil.ReadFile(dir + "/" + f.Name())
-                       if err != nil {
-                               fmt.Println("Error loading colorscheme:", err)
-                               continue
-                       }
-                       colorscheme = ParseColorscheme(string(text))
-               }
-       }
-}
-
-// ParseColorscheme parses the text definition for a colorscheme and returns the corresponding object
-// Colorschemes are made up of color-link statements linking a color group to a list of colors
-// For example, color-link keyword (blue,red) makes all keywords have a blue foreground and
-// red background
-func ParseColorscheme(text string) Colorscheme {
-       parser := regexp.MustCompile(`color-link\s+(\S*)\s+"(.*)"`)
-
-       lines := strings.Split(text, "\n")
-
-       c := make(Colorscheme)
-
-       for _, line := range lines {
-               if strings.TrimSpace(line) == "" ||
-                       strings.TrimSpace(line)[0] == '#' {
-                       // Ignore this line
-                       continue
-               }
-
-               matches := parser.FindSubmatch([]byte(line))
-               if len(matches) == 3 {
-                       link := string(matches[1])
-                       colors := string(matches[2])
-
-                       c[link] = StringToStyle(colors)
-               } else {
-                       fmt.Println("Color-link statement is not valid:", line)
-               }
-       }
-
-       return c
-}
-
-// StringToStyle returns a style from a string
-// The strings must be in the format "extra foregroundcolor,backgroundcolor"
-// The 'extra' can be bold, reverse, or underline
-func StringToStyle(str string) tcell.Style {
-       var fg string
-       bg := "default"
-       split := strings.Split(str, ",")
-       if len(split) > 1 {
-               fg, bg = split[0], split[1]
-       } else {
-               fg = split[0]
-       }
-       fg = strings.TrimSpace(fg)
-       bg = strings.TrimSpace(bg)
-
-       style := tcell.StyleDefault.Foreground(StringToColor(fg)).Background(StringToColor(bg))
-       if strings.Contains(str, "bold") {
-               style = style.Bold(true)
-       }
-       if strings.Contains(str, "reverse") {
-               style = style.Reverse(true)
-       }
-       if strings.Contains(str, "underline") {
-               style = style.Underline(true)
-       }
-       return style
-}
-
-// StringToColor returns a tcell color from a string representation of a color
-// We accept either bright... or light... to mean the brighter version of a color
-func StringToColor(str string) tcell.Color {
-       switch str {
-       case "black":
-               return tcell.ColorBlack
-       case "red":
-               return tcell.ColorMaroon
-       case "green":
-               return tcell.ColorGreen
-       case "yellow":
-               return tcell.ColorOlive
-       case "blue":
-               return tcell.ColorNavy
-       case "magenta":
-               return tcell.ColorPurple
-       case "cyan":
-               return tcell.ColorTeal
-       case "white":
-               return tcell.ColorSilver
-       case "brightblack", "lightblack":
-               return tcell.ColorGray
-       case "brightred", "lightred":
-               return tcell.ColorRed
-       case "brightgreen", "lightgreen":
-               return tcell.ColorLime
-       case "brightyellow", "lightyellow":
-               return tcell.ColorYellow
-       case "brightblue", "lightblue":
-               return tcell.ColorBlue
-       case "brightmagenta", "lightmagenta":
-               return tcell.ColorFuchsia
-       case "brightcyan", "lightcyan":
-               return tcell.ColorAqua
-       case "brightwhite", "lightwhite":
-               return tcell.ColorWhite
-       case "default":
-               return tcell.ColorDefault
-       default:
-               // Check if this is a 256 color
-               if num, err := strconv.Atoi(str); err == nil {
-                       return GetColor256(num)
-               }
-               // Probably a truecolor hex value
-               return tcell.GetColor(str)
-       }
-}
-
-// GetColor256 returns the tcell color for a number between 0 and 255
-func GetColor256(color int) tcell.Color {
-       colors := []tcell.Color{tcell.ColorBlack, tcell.ColorMaroon, tcell.ColorGreen,
-               tcell.ColorOlive, tcell.ColorNavy, tcell.ColorPurple,
-               tcell.ColorTeal, tcell.ColorSilver, tcell.ColorGray,
-               tcell.ColorRed, tcell.ColorLime, tcell.ColorYellow,
-               tcell.ColorBlue, tcell.ColorFuchsia, tcell.ColorAqua,
-               tcell.ColorWhite, tcell.Color16, tcell.Color17, tcell.Color18, tcell.Color19, tcell.Color20,
-               tcell.Color21, tcell.Color22, tcell.Color23, tcell.Color24, tcell.Color25, tcell.Color26, tcell.Color27, tcell.Color28,
-               tcell.Color29, tcell.Color30, tcell.Color31, tcell.Color32, tcell.Color33, tcell.Color34, tcell.Color35, tcell.Color36,
-               tcell.Color37, tcell.Color38, tcell.Color39, tcell.Color40, tcell.Color41, tcell.Color42, tcell.Color43, tcell.Color44,
-               tcell.Color45, tcell.Color46, tcell.Color47, tcell.Color48, tcell.Color49, tcell.Color50, tcell.Color51, tcell.Color52,
-               tcell.Color53, tcell.Color54, tcell.Color55, tcell.Color56, tcell.Color57, tcell.Color58, tcell.Color59, tcell.Color60,
-               tcell.Color61, tcell.Color62, tcell.Color63, tcell.Color64, tcell.Color65, tcell.Color66, tcell.Color67, tcell.Color68,
-               tcell.Color69, tcell.Color70, tcell.Color71, tcell.Color72, tcell.Color73, tcell.Color74, tcell.Color75, tcell.Color76,
-               tcell.Color77, tcell.Color78, tcell.Color79, tcell.Color80, tcell.Color81, tcell.Color82, tcell.Color83, tcell.Color84,
-               tcell.Color85, tcell.Color86, tcell.Color87, tcell.Color88, tcell.Color89, tcell.Color90, tcell.Color91, tcell.Color92,
-               tcell.Color93, tcell.Color94, tcell.Color95, tcell.Color96, tcell.Color97, tcell.Color98, tcell.Color99, tcell.Color100,
-               tcell.Color101, tcell.Color102, tcell.Color103, tcell.Color104, tcell.Color105, tcell.Color106, tcell.Color107, tcell.Color108,
-               tcell.Color109, tcell.Color110, tcell.Color111, tcell.Color112, tcell.Color113, tcell.Color114, tcell.Color115, tcell.Color116,
-               tcell.Color117, tcell.Color118, tcell.Color119, tcell.Color120, tcell.Color121, tcell.Color122, tcell.Color123, tcell.Color124,
-               tcell.Color125, tcell.Color126, tcell.Color127, tcell.Color128, tcell.Color129, tcell.Color130, tcell.Color131, tcell.Color132,
-               tcell.Color133, tcell.Color134, tcell.Color135, tcell.Color136, tcell.Color137, tcell.Color138, tcell.Color139, tcell.Color140,
-               tcell.Color141, tcell.Color142, tcell.Color143, tcell.Color144, tcell.Color145, tcell.Color146, tcell.Color147, tcell.Color148,
-               tcell.Color149, tcell.Color150, tcell.Color151, tcell.Color152, tcell.Color153, tcell.Color154, tcell.Color155, tcell.Color156,
-               tcell.Color157, tcell.Color158, tcell.Color159, tcell.Color160, tcell.Color161, tcell.Color162, tcell.Color163, tcell.Color164,
-               tcell.Color165, tcell.Color166, tcell.Color167, tcell.Color168, tcell.Color169, tcell.Color170, tcell.Color171, tcell.Color172,
-               tcell.Color173, tcell.Color174, tcell.Color175, tcell.Color176, tcell.Color177, tcell.Color178, tcell.Color179, tcell.Color180,
-               tcell.Color181, tcell.Color182, tcell.Color183, tcell.Color184, tcell.Color185, tcell.Color186, tcell.Color187, tcell.Color188,
-               tcell.Color189, tcell.Color190, tcell.Color191, tcell.Color192, tcell.Color193, tcell.Color194, tcell.Color195, tcell.Color196,
-               tcell.Color197, tcell.Color198, tcell.Color199, tcell.Color200, tcell.Color201, tcell.Color202, tcell.Color203, tcell.Color204,
-               tcell.Color205, tcell.Color206, tcell.Color207, tcell.Color208, tcell.Color209, tcell.Color210, tcell.Color211, tcell.Color212,
-               tcell.Color213, tcell.Color214, tcell.Color215, tcell.Color216, tcell.Color217, tcell.Color218, tcell.Color219, tcell.Color220,
-               tcell.Color221, tcell.Color222, tcell.Color223, tcell.Color224, tcell.Color225, tcell.Color226, tcell.Color227, tcell.Color228,
-               tcell.Color229, tcell.Color230, tcell.Color231, tcell.Color232, tcell.Color233, tcell.Color234, tcell.Color235, tcell.Color236,
-               tcell.Color237, tcell.Color238, tcell.Color239, tcell.Color240, tcell.Color241, tcell.Color242, tcell.Color243, tcell.Color244,
-               tcell.Color245, tcell.Color246, tcell.Color247, tcell.Color248, tcell.Color249, tcell.Color250, tcell.Color251, tcell.Color252,
-               tcell.Color253, tcell.Color254, tcell.Color255,
-       }
-
-       return colors[color]
-}
diff --git a/src/command.go b/src/command.go
deleted file mode 100644 (file)
index 7fcf41d..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-package main
-
-import (
-       "os"
-       "regexp"
-       "strings"
-)
-
-// HandleCommand handles input from the user
-func HandleCommand(input string, view *View) {
-       inputCmd := strings.Split(input, " ")[0]
-       args := strings.Split(input, " ")[1:]
-
-       commands := []string{"set", "quit", "save", "replace"}
-
-       i := 0
-       cmd := inputCmd
-
-       for _, c := range commands {
-               if strings.HasPrefix(c, inputCmd) {
-                       i++
-                       cmd = c
-               }
-       }
-       if i == 1 {
-               inputCmd = cmd
-       }
-
-       switch inputCmd {
-       case "set":
-               SetOption(view, args)
-       case "quit":
-               if view.CanClose("Quit anyway? ") {
-                       screen.Fini()
-                       os.Exit(0)
-               }
-       case "save":
-               view.Save()
-       case "replace":
-               r := regexp.MustCompile(`"[^"\\]*(?:\\.[^"\\]*)*"|[^\s]*`)
-               replaceCmd := r.FindAllString(strings.Join(args, " "), -1)
-               if len(replaceCmd) < 2 {
-                       messenger.Error("Invalid replace statement: " + strings.Join(args, " "))
-                       return
-               }
-
-               var flags string
-               if len(replaceCmd) == 3 {
-                       // The user included some flags
-                       flags = replaceCmd[2]
-               }
-
-               search := string(replaceCmd[0])
-               replace := string(replaceCmd[1])
-
-               if strings.HasPrefix(search, `"`) && strings.HasSuffix(search, `"`) {
-                       search = search[1 : len(search)-1]
-               }
-               if strings.HasPrefix(replace, `"`) && strings.HasSuffix(replace, `"`) {
-                       replace = replace[1 : len(replace)-1]
-               }
-
-               search = strings.Replace(search, `\"`, `"`, -1)
-               replace = strings.Replace(replace, `\"`, `"`, -1)
-
-               // messenger.Error(search + " -> " + replace)
-
-               regex, err := regexp.Compile(search)
-               if err != nil {
-                       messenger.Error(err.Error())
-                       return
-               }
-
-               found := false
-               for {
-                       match := regex.FindStringIndex(view.buf.text)
-                       if match == nil {
-                               break
-                       }
-                       found = true
-                       if strings.Contains(flags, "c") {
-                               //      // The 'check' flag was used
-                               //      if messenger.YesNoPrompt("Perform replacement?") {
-                               //              view.eh.Replace(match[0], match[1], replace)
-                               //      } else {
-                               //              continue
-                               //      }
-                       }
-                       view.eh.Replace(match[0], match[1], replace)
-               }
-               if !found {
-                       messenger.Message("Nothing matched " + search)
-               }
-       default:
-               messenger.Error("Unknown command: " + inputCmd)
-       }
-}
diff --git a/src/cursor.go b/src/cursor.go
deleted file mode 100644 (file)
index a922903..0000000
+++ /dev/null
@@ -1,308 +0,0 @@
-package main
-
-import (
-       "strings"
-)
-
-// FromCharPos converts from a character position to an x, y position
-func FromCharPos(loc int, buf *Buffer) (int, int) {
-       return FromCharPosStart(0, 0, 0, loc, buf)
-}
-
-// FromCharPosStart converts from a character position to an x, y position, starting at the specified character location
-func FromCharPosStart(startLoc, startX, startY, loc int, buf *Buffer) (int, int) {
-       charNum := startLoc
-       x, y := startX, startY
-
-       lineLen := Count(buf.lines[y]) + 1
-       for charNum+lineLen <= loc {
-               charNum += lineLen
-               y++
-               lineLen = Count(buf.lines[y]) + 1
-       }
-       x = loc - charNum
-
-       return x, y
-}
-
-// ToCharPos converts from an x, y position to a character position
-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 += x
-       return loc
-}
-
-// The Cursor struct stores the location of the cursor in the view
-// The complicated part about the cursor is storing its location.
-// The cursor must be displayed at an x, y location, but since the buffer
-// uses a rope to store text, to insert text we must have an index. It
-// is also simpler to use character indicies for other tasks such as
-// selection.
-type Cursor struct {
-       v *View
-
-       // The cursor display location
-       x int
-       y int
-
-       // Last cursor x position
-       lastVisualX int
-
-       // The current selection as a range of character numbers (inclusive)
-       curSelection [2]int
-       // The original selection as a range of character numbers
-       // This is used for line and word selection where it is necessary
-       // to know what the original selection was
-       origSelection [2]int
-}
-
-// SetLoc sets the location of the cursor in terms of character number
-// 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)
-}
-
-// Loc gets the cursor location in terms of character number instead
-// 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)
-}
-
-// ResetSelection resets the user's selection
-func (c *Cursor) ResetSelection() {
-       c.curSelection[0] = 0
-       c.curSelection[1] = 0
-}
-
-// HasSelection returns whether or not the user has selected anything
-func (c *Cursor) HasSelection() bool {
-       return c.curSelection[0] != c.curSelection[1]
-}
-
-// DeleteSelection deletes the currently selected text
-func (c *Cursor) DeleteSelection() {
-       if c.curSelection[0] > c.curSelection[1] {
-               c.v.eh.Remove(c.curSelection[1], c.curSelection[0])
-               c.SetLoc(c.curSelection[1])
-       } else {
-               c.v.eh.Remove(c.curSelection[0], c.curSelection[1])
-               c.SetLoc(c.curSelection[0])
-       }
-}
-
-// GetSelection returns the cursor's selection
-func (c *Cursor) GetSelection() string {
-       if c.curSelection[0] > c.curSelection[1] {
-               return string([]rune(c.v.buf.text)[c.curSelection[1]:c.curSelection[0]])
-       }
-       return string([]rune(c.v.buf.text)[c.curSelection[0]:c.curSelection[1]])
-}
-
-// SelectLine selects the current line
-func (c *Cursor) SelectLine() {
-       c.Start()
-       c.curSelection[0] = c.Loc()
-       c.End()
-       c.curSelection[1] = c.Loc()
-
-       c.origSelection = c.curSelection
-}
-
-// AddLineToSelection adds the current line to the selection
-func (c *Cursor) AddLineToSelection() {
-       loc := c.Loc()
-
-       if loc < c.origSelection[0] {
-               c.Start()
-               c.curSelection[0] = c.Loc()
-               c.curSelection[1] = c.origSelection[1]
-       }
-       if loc > c.origSelection[1] {
-               c.End()
-               c.curSelection[1] = c.Loc()
-               c.curSelection[0] = c.origSelection[0]
-       }
-
-       if loc < c.origSelection[1] && loc > c.origSelection[0] {
-               c.curSelection = c.origSelection
-       }
-}
-
-// SelectWord selects the word the cursor is currently on
-func (c *Cursor) SelectWord() {
-       if len(c.v.buf.lines[c.y]) == 0 {
-               return
-       }
-
-       if !IsWordChar(string(c.RuneUnder(c.x))) {
-               loc := c.Loc()
-               c.curSelection[0] = loc
-               c.curSelection[1] = loc + 1
-               c.origSelection = c.curSelection
-               return
-       }
-
-       forward, backward := c.x, c.x
-
-       for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
-               backward--
-       }
-
-       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))) {
-               forward++
-       }
-
-       c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + 1
-       c.origSelection[1] = c.curSelection[1]
-}
-
-// AddWordToSelection adds the word the cursor is currently on to the selection
-func (c *Cursor) AddWordToSelection() {
-       loc := c.Loc()
-
-       if loc > c.origSelection[0] && loc < c.origSelection[1] {
-               c.curSelection = c.origSelection
-               return
-       }
-
-       if loc < c.origSelection[0] {
-               backward := c.x
-
-               for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
-                       backward--
-               }
-
-               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))) {
-                       forward++
-               }
-
-               c.curSelection[1] = ToCharPos(forward, c.y, c.v.buf) + 1
-               c.curSelection[0] = c.origSelection[0]
-       }
-}
-
-// RuneUnder returns the rune under the given x position
-func (c *Cursor) RuneUnder(x int) rune {
-       line := []rune(c.v.buf.lines[c.y])
-       if x >= len(line) {
-               x = len(line) - 1
-       } else if x < 0 {
-               x = 0
-       }
-       return line[x]
-}
-
-// Up moves the cursor up one line (if possible)
-func (c *Cursor) Up() {
-       if c.y > 0 {
-               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)
-               }
-       }
-}
-
-// Down moves the cursor down one line (if possible)
-func (c *Cursor) Down() {
-       if c.y < len(c.v.buf.lines)-1 {
-               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)
-               }
-       }
-}
-
-// Left moves the cursor left one cell (if possible) or to the last line if it is at the beginning
-func (c *Cursor) Left() {
-       if c.Loc() == 0 {
-               return
-       }
-       if c.x > 0 {
-               c.x--
-       } else {
-               c.Up()
-               c.End()
-       }
-       c.lastVisualX = c.GetVisualX()
-}
-
-// 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() {
-               return
-       }
-       if c.x < Count(c.v.buf.lines[c.y]) {
-               c.x++
-       } else {
-               c.Down()
-               c.Start()
-       }
-       c.lastVisualX = c.GetVisualX()
-}
-
-// 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.lastVisualX = c.GetVisualX()
-}
-
-// Start moves the cursor to the start of the line it is on
-func (c *Cursor) Start() {
-       c.x = 0
-       c.lastVisualX = c.GetVisualX()
-}
-
-// GetCharPosInLine gets the char position of a visual x y coordinate (this is necessary because tabs are 1 char but 4 visual spaces)
-func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
-       // Get the tab size
-       tabSize := settings.TabSize
-       // 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)
-       if visualPos > Count(visualLine) {
-               visualPos = Count(visualLine)
-       }
-       numTabs := NumOccurences(visualLine[:visualPos], '\t')
-       if visualPos >= (tabSize-1)*numTabs {
-               return visualPos - (tabSize-1)*numTabs
-       }
-       return visualPos / tabSize
-}
-
-// GetVisualX returns the x value of the cursor in visual spaces
-func (c *Cursor) GetVisualX() int {
-       runes := []rune(c.v.buf.lines[c.y])
-       tabSize := settings.TabSize
-       return c.x + NumOccurences(string(runes[:c.x]), '\t')*(tabSize-1)
-}
-
-// 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() {
-               screen.HideCursor()
-       } else {
-               screen.ShowCursor(c.GetVisualX()+c.v.lineNumOffset-c.v.leftCol, c.y-c.v.topline)
-       }
-}
diff --git a/src/eventhandler.go b/src/eventhandler.go
deleted file mode 100644 (file)
index c54319e..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-package main
-
-import (
-       "time"
-)
-
-const (
-       // Opposite and undoing events must have opposite values
-
-       // TextEventInsert repreasents an insertion event
-       TextEventInsert = 1
-       // TextEventRemove represents a deletion event
-       TextEventRemove = -1
-)
-
-// TextEvent holds data for a manipulation on some text that can be undone
-type TextEvent struct {
-       c Cursor
-
-       eventType int
-       text      string
-       start     int
-       end       int
-       buf       *Buffer
-       time      time.Time
-}
-
-// ExecuteTextEvent runs a text event
-func ExecuteTextEvent(t *TextEvent) {
-       if t.eventType == TextEventInsert {
-               t.buf.Insert(t.start, t.text)
-       } else if t.eventType == TextEventRemove {
-               t.text = t.buf.Remove(t.start, t.end)
-       }
-}
-
-// UndoTextEvent undoes a text event
-func UndoTextEvent(t *TextEvent) {
-       t.eventType = -t.eventType
-       ExecuteTextEvent(t)
-}
-
-// EventHandler executes text manipulations and allows undoing and redoing
-type EventHandler struct {
-       v    *View
-       undo *Stack
-       redo *Stack
-}
-
-// NewEventHandler returns a new EventHandler
-func NewEventHandler(v *View) *EventHandler {
-       eh := new(EventHandler)
-       eh.undo = new(Stack)
-       eh.redo = new(Stack)
-       eh.v = v
-       return eh
-}
-
-// Insert creates an insert text event and executes it
-func (eh *EventHandler) Insert(start int, text string) {
-       e := &TextEvent{
-               c:         eh.v.cursor,
-               eventType: TextEventInsert,
-               text:      text,
-               start:     start,
-               end:       start + Count(text),
-               buf:       eh.v.buf,
-               time:      time.Now(),
-       }
-       eh.Execute(e)
-}
-
-// Remove creates a remove text event and executes it
-func (eh *EventHandler) Remove(start, end int) {
-       e := &TextEvent{
-               c:         eh.v.cursor,
-               eventType: TextEventRemove,
-               start:     start,
-               end:       end,
-               buf:       eh.v.buf,
-               time:      time.Now(),
-       }
-       eh.Execute(e)
-}
-
-// Replace deletes from start to end and replaces it with the given string
-func (eh *EventHandler) Replace(start, end int, replace string) {
-       eh.Remove(start, end)
-       eh.Insert(start, replace)
-}
-
-// Execute a textevent and add it to the undo stack
-func (eh *EventHandler) Execute(t *TextEvent) {
-       if eh.redo.Len() > 0 {
-               eh.redo = new(Stack)
-       }
-       eh.undo.Push(t)
-       ExecuteTextEvent(t)
-}
-
-// Undo the first event in the undo stack
-func (eh *EventHandler) Undo() {
-       t := eh.undo.Peek()
-       if t == nil {
-               return
-       }
-
-       te := t.(*TextEvent)
-       startTime := t.(*TextEvent).time.UnixNano() / int64(time.Millisecond)
-
-       eh.UndoOneEvent()
-
-       for {
-               t = eh.undo.Peek()
-               if t == nil {
-                       return
-               }
-
-               te = t.(*TextEvent)
-
-               if startTime-(te.time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
-                       return
-               }
-
-               eh.UndoOneEvent()
-       }
-}
-
-// UndoOneEvent undoes one event
-func (eh *EventHandler) UndoOneEvent() {
-       // This event should be undone
-       // Pop it off the stack
-       t := eh.undo.Pop()
-       if t == nil {
-               return
-       }
-
-       te := t.(*TextEvent)
-       // Undo it
-       // Modifies the text event
-       UndoTextEvent(te)
-
-       // Set the cursor in the right place
-       teCursor := te.c
-       te.c = eh.v.cursor
-       eh.v.cursor = teCursor
-
-       // Push it to the redo stack
-       eh.redo.Push(te)
-}
-
-// Redo the first event in the redo stack
-func (eh *EventHandler) Redo() {
-       t := eh.redo.Peek()
-       if t == nil {
-               return
-       }
-
-       te := t.(*TextEvent)
-       startTime := t.(*TextEvent).time.UnixNano() / int64(time.Millisecond)
-
-       eh.RedoOneEvent()
-
-       for {
-               t = eh.redo.Peek()
-               if t == nil {
-                       return
-               }
-
-               te = t.(*TextEvent)
-
-               if (te.time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
-                       return
-               }
-
-               eh.RedoOneEvent()
-       }
-}
-
-// RedoOneEvent redoes one event
-func (eh *EventHandler) RedoOneEvent() {
-       t := eh.redo.Pop()
-       if t == nil {
-               return
-       }
-
-       te := t.(*TextEvent)
-       // Modifies the text event
-       UndoTextEvent(te)
-
-       teCursor := te.c
-       te.c = eh.v.cursor
-       eh.v.cursor = teCursor
-
-       eh.undo.Push(te)
-}
diff --git a/src/help.go b/src/help.go
deleted file mode 100644 (file)
index 5d2d3ed..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-package main
-
-import (
-       "github.com/gdamore/tcell"
-       "strings"
-)
-
-const helpTxt = `Press Ctrl-q to quit help
-
-Micro keybindings:
-
-Ctrl-q:   Quit
-Ctrl-s:   Save
-Ctrl-o:   Open file
-
-Ctrl-z:   Undo
-Ctrl-y:   Redo
-
-Ctrl-f:   Find
-Ctrl-n:   Find next
-Ctrl-p:   Find previous
-
-Ctrl-a:   Select all
-
-Ctrl-c:   Copy
-Ctrl-x:   Cut
-Ctrl-v:   Paste
-
-Ctrl-h:   Open help
-
-Ctrl-u:   Half page up
-Ctrl-d:   Half page down
-PageUp:   Page up
-PageDown: Page down
-
-Ctrl-e:   Execute a command
-
-Possible commands:
-
-'quit': Quits micro
-'save': saves the current buffer
-
-'replace "search" "value"': This will replace 'search' with 'value'.
-Note that 'search' must be a valid regex.  If one of the arguments
-does not have any spaces in it, you may omit the quotes.
-
-'set option value': sets the option to value. Please see the next section for a list of options you can set
-
-Micro options:
-
-colorscheme: loads the colorscheme stored in ~/.micro/colorschemes/'option'.micro
-       default value: 'default'
-
-tabsize: sets the tab size to 'option'
-       default value: '4'
-
-syntax: turns syntax on or off
-       default value: 'on'
-`
-
-// DisplayHelp displays the help txt
-// It blocks the main loop
-func DisplayHelp() {
-       topline := 0
-       _, height := screen.Size()
-       screen.HideCursor()
-       totalLines := strings.Split(helpTxt, "\n")
-       for {
-               screen.Clear()
-
-               lineEnd := topline + height
-               if lineEnd > len(totalLines) {
-                       lineEnd = len(totalLines)
-               }
-               lines := totalLines[topline:lineEnd]
-               for y, line := range lines {
-                       for x, ch := range line {
-                               st := defStyle
-                               screen.SetContent(x, y, ch, nil, st)
-                       }
-               }
-
-               screen.Show()
-
-               event := screen.PollEvent()
-               switch e := event.(type) {
-               case *tcell.EventResize:
-                       _, height = e.Size()
-               case *tcell.EventKey:
-                       switch e.Key() {
-                       case tcell.KeyUp:
-                               if topline > 0 {
-                                       topline--
-                               }
-                       case tcell.KeyDown:
-                               if topline < len(totalLines)-height {
-                                       topline++
-                               }
-                       case tcell.KeyCtrlQ, tcell.KeyCtrlW, tcell.KeyEscape, tcell.KeyCtrlC:
-                               return
-                       }
-               }
-       }
-}
diff --git a/src/highlighter.go b/src/highlighter.go
deleted file mode 100644 (file)
index 00f8b5a..0000000
+++ /dev/null
@@ -1,337 +0,0 @@
-package main
-
-import (
-       "github.com/gdamore/tcell"
-       "github.com/mitchellh/go-homedir"
-       "io/ioutil"
-       "path/filepath"
-       "regexp"
-       "strings"
-)
-
-// FileTypeRules represents a complete set of syntax rules for a filetype
-type FileTypeRules struct {
-       filetype string
-       rules    []SyntaxRule
-}
-
-// SyntaxRule represents a regex to highlight in a certain style
-type SyntaxRule struct {
-       // What to highlight
-       regex *regexp.Regexp
-       // Any flags
-       flags string
-       // Whether this regex is a start=... end=... regex
-       startend bool
-       // How to highlight it
-       style tcell.Style
-}
-
-var syntaxFiles map[[2]*regexp.Regexp]FileTypeRules
-
-// LoadSyntaxFiles loads the syntax files from the default directory ~/.micro
-func LoadSyntaxFiles() {
-       home, err := homedir.Dir()
-       if err != nil {
-               TermMessage("Error finding your home directory\nCan't load syntax files")
-               return
-       }
-       LoadSyntaxFilesFromDir(home + "/.micro/syntax")
-}
-
-// JoinRule takes a syntax rule (which can be multiple regular expressions)
-// and joins it into one regular expression by ORing everything together
-func JoinRule(rule string) string {
-       split := strings.Split(rule, `" "`)
-       joined := strings.Join(split, ")|(")
-       joined = "(" + joined + ")"
-       return joined
-}
-
-// LoadSyntaxFile loads the specified syntax file
-// A syntax file is a list of syntax rules, explaining how to color certain
-// regular expressions
-// Example: color comment "//.*"
-// This would color all strings that match the regex "//.*" in the comment color defined
-// by the colorscheme
-func LoadSyntaxFile(filename string) {
-       text, err := ioutil.ReadFile(filename)
-
-       if err != nil {
-               TermMessage("Error loading syntax file " + filename + ": " + err.Error())
-               return
-       }
-       lines := strings.Split(string(text), "\n")
-
-       // Regex for parsing syntax statements
-       syntaxParser := regexp.MustCompile(`syntax "(.*?)"\s+"(.*)"+`)
-       // Regex for parsing header statements
-       headerParser := regexp.MustCompile(`header "(.*)"`)
-
-       // Regex for parsing standard syntax rules
-       ruleParser := regexp.MustCompile(`color (.*?)\s+(?:\((.*?)\)\s+)?"(.*)"`)
-       // Regex for parsing syntax rules with start="..." end="..."
-       ruleStartEndParser := regexp.MustCompile(`color (.*?)\s+(?:\((.*?)\)\s+)?start="(.*)"\s+end="(.*)"`)
-
-       var syntaxRegex *regexp.Regexp
-       var headerRegex *regexp.Regexp
-       var filetype string
-       var rules []SyntaxRule
-       for lineNum, line := range lines {
-               if strings.TrimSpace(line) == "" ||
-                       strings.TrimSpace(line)[0] == '#' {
-                       // Ignore this line
-                       continue
-               }
-
-               if strings.HasPrefix(line, "syntax") {
-                       // Syntax statement
-                       syntaxMatches := syntaxParser.FindSubmatch([]byte(line))
-                       if len(syntaxMatches) == 3 {
-                               if syntaxRegex != nil {
-                                       // Add the current rules to the syntaxFiles variable
-                                       regexes := [2]*regexp.Regexp{syntaxRegex, headerRegex}
-                                       syntaxFiles[regexes] = FileTypeRules{filetype, rules}
-                               }
-                               rules = rules[:0]
-
-                               filetype = string(syntaxMatches[1])
-                               extensions := JoinRule(string(syntaxMatches[2]))
-
-                               syntaxRegex, err = regexp.Compile(extensions)
-                               if err != nil {
-                                       TermError(filename, lineNum, err.Error())
-                                       continue
-                               }
-                       } else {
-                               TermError(filename, lineNum, "Syntax statement is not valid: "+line)
-                               continue
-                       }
-               } else if strings.HasPrefix(line, "header") {
-                       // Header statement
-                       headerMatches := headerParser.FindSubmatch([]byte(line))
-                       if len(headerMatches) == 2 {
-                               header := JoinRule(string(headerMatches[1]))
-
-                               headerRegex, err = regexp.Compile(header)
-                               if err != nil {
-                                       TermError(filename, lineNum, "Regex error: "+err.Error())
-                                       continue
-                               }
-                       } else {
-                               TermError(filename, lineNum, "Header statement is not valid: "+line)
-                               continue
-                       }
-               } else {
-                       // Syntax rule, but it could be standard or start-end
-                       if ruleParser.MatchString(line) {
-                               // Standard syntax rule
-                               // Parse the line
-                               submatch := ruleParser.FindSubmatch([]byte(line))
-                               var color string
-                               var regexStr string
-                               var flags string
-                               if len(submatch) == 4 {
-                                       // If len is 4 then the user specified some additional flags to use
-                                       color = string(submatch[1])
-                                       flags = string(submatch[2])
-                                       regexStr = "(?" + flags + ")" + JoinRule(string(submatch[3]))
-                               } else if len(submatch) == 3 {
-                                       // If len is 3, no additional flags were given
-                                       color = string(submatch[1])
-                                       regexStr = JoinRule(string(submatch[2]))
-                               } else {
-                                       // If len is not 3 or 4 there is a problem
-                                       TermError(filename, lineNum, "Invalid statement: "+line)
-                                       continue
-                               }
-                               // Compile the regex
-                               regex, err := regexp.Compile(regexStr)
-                               if err != nil {
-                                       TermError(filename, lineNum, err.Error())
-                                       continue
-                               }
-
-                               // Get the style
-                               // The user could give us a "color" that is really a part of the colorscheme
-                               // in which case we should look that up in the colorscheme
-                               // They can also just give us a straight up color
-                               st := defStyle
-                               if _, ok := colorscheme[color]; ok {
-                                       st = colorscheme[color]
-                               } else {
-                                       st = StringToStyle(color)
-                               }
-                               // Add the regex, flags, and style
-                               // False because this is not start-end
-                               rules = append(rules, SyntaxRule{regex, flags, false, st})
-                       } else if ruleStartEndParser.MatchString(line) {
-                               // Start-end syntax rule
-                               submatch := ruleStartEndParser.FindSubmatch([]byte(line))
-                               var color string
-                               var start string
-                               var end string
-                               // Use m and s flags by default
-                               flags := "ms"
-                               if len(submatch) == 5 {
-                                       // If len is 5 the user provided some additional flags
-                                       color = string(submatch[1])
-                                       flags += string(submatch[2])
-                                       start = string(submatch[3])
-                                       end = string(submatch[4])
-                               } else if len(submatch) == 4 {
-                                       // If len is 4 the user did not provide additional flags
-                                       color = string(submatch[1])
-                                       start = string(submatch[2])
-                                       end = string(submatch[3])
-                               } else {
-                                       // If len is not 4 or 5 there is a problem
-                                       TermError(filename, lineNum, "Invalid statement: "+line)
-                                       continue
-                               }
-
-                               // Compile the regex
-                               regex, err := regexp.Compile("(?" + flags + ")" + "(" + start + ").*?(" + end + ")")
-                               if err != nil {
-                                       TermError(filename, lineNum, err.Error())
-                                       continue
-                               }
-
-                               // Get the style
-                               // The user could give us a "color" that is really a part of the colorscheme
-                               // in which case we should look that up in the colorscheme
-                               // They can also just give us a straight up color
-                               st := defStyle
-                               if _, ok := colorscheme[color]; ok {
-                                       st = colorscheme[color]
-                               } else {
-                                       st = StringToStyle(color)
-                               }
-                               // Add the regex, flags, and style
-                               // True because this is start-end
-                               rules = append(rules, SyntaxRule{regex, flags, true, st})
-                       }
-               }
-       }
-       if syntaxRegex != nil {
-               // Add the current rules to the syntaxFiles variable
-               regexes := [2]*regexp.Regexp{syntaxRegex, headerRegex}
-               syntaxFiles[regexes] = FileTypeRules{filetype, rules}
-       }
-}
-
-// LoadSyntaxFilesFromDir loads the syntax files from a specified directory
-// To load the syntax files, we must fill the `syntaxFiles` map
-// This involves finding the regex for syntax and if it exists, the regex
-// for the header. Then we must get the text for the file and the filetype.
-func LoadSyntaxFilesFromDir(dir string) {
-       InitColorscheme()
-
-       syntaxFiles = make(map[[2]*regexp.Regexp]FileTypeRules)
-       files, _ := ioutil.ReadDir(dir)
-       for _, f := range files {
-               if filepath.Ext(f.Name()) == ".micro" {
-                       LoadSyntaxFile(dir + "/" + f.Name())
-               }
-       }
-}
-
-// GetRules finds the syntax rules that should be used for the buffer
-// 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) {
-                       return syntaxFiles[r].rules, syntaxFiles[r].filetype
-               } else if r[1] != nil && r[1].MatchString(buf.lines[0]) {
-                       return syntaxFiles[r].rules, syntaxFiles[r].filetype
-               }
-       }
-       return nil, "Unknown"
-}
-
-// SyntaxMatches is an alias to a map from character numbers to styles,
-// so map[3] represents the style of the third character
-type SyntaxMatches [][]tcell.Style
-
-// Match takes a buffer and returns the syntax matches a map specifying how it should be syntax highlighted
-// We need to check the start-end regexes for the entire buffer every time Match is called, but for the
-// non start-end rules, we only have to update the updateLines provided by the view
-func Match(v *View) SyntaxMatches {
-       buf := v.buf
-       rules := v.buf.rules
-
-       viewStart := v.topline
-       viewEnd := v.topline + v.height
-       if viewEnd > len(buf.lines) {
-               viewEnd = len(buf.lines)
-       }
-
-       // updateStart := v.updateLines[0]
-       // updateEnd := v.updateLines[1]
-       //
-       // if updateEnd > len(buf.lines) {
-       //      updateEnd = len(buf.lines)
-       // }
-       // if updateStart < 0 {
-       //      updateStart = 0
-       // }
-       lines := buf.lines[viewStart:viewEnd]
-       // updateLines := buf.lines[updateStart:updateEnd]
-       matches := make(SyntaxMatches, len(lines))
-
-       for i, line := range lines {
-               matches[i] = make([]tcell.Style, len(line)+1)
-       }
-
-       // We don't actually check the entire buffer, just from synLinesUp to synLinesDown
-       totalStart := v.topline - synLinesUp
-       totalEnd := v.topline + v.height + synLinesDown
-       if totalStart < 0 {
-               totalStart = 0
-       }
-       if totalEnd > len(buf.lines) {
-               totalEnd = len(buf.lines)
-       }
-
-       str := strings.Join(buf.lines[totalStart:totalEnd], "\n")
-       startNum := ToCharPos(0, totalStart, v.buf)
-
-       toplineNum := ToCharPos(0, v.topline, v.buf)
-
-       for _, rule := range rules {
-               if rule.startend {
-                       if indicies := rule.regex.FindAllStringIndex(str, -1); indicies != nil {
-                               for _, value := range indicies {
-                                       value[0] += startNum
-                                       value[1] += startNum
-                                       for i := value[0]; i < value[1]; i++ {
-                                               if i < toplineNum {
-                                                       continue
-                                               }
-                                               colNum, lineNum := FromCharPosStart(toplineNum, 0, v.topline, i, buf)
-                                               if lineNum == -1 || colNum == -1 {
-                                                       continue
-                                               }
-                                               lineNum -= viewStart
-                                               if lineNum >= 0 && lineNum < v.height {
-                                                       matches[lineNum][colNum] = rule.style
-                                               }
-                                       }
-                               }
-                       }
-               } else {
-                       for lineN, line := range lines {
-                               if indicies := rule.regex.FindAllStringIndex(line, -1); indicies != nil {
-                                       for _, value := range indicies {
-                                               for i := value[0]; i < value[1]; i++ {
-                                                       // matches[lineN+updateStart][i] = rule.style
-                                                       matches[lineN][i] = rule.style
-                                               }
-                                       }
-                               }
-                       }
-               }
-       }
-
-       return matches
-}
diff --git a/src/messenger.go b/src/messenger.go
deleted file mode 100644 (file)
index bb30ba2..0000000
+++ /dev/null
@@ -1,192 +0,0 @@
-package main
-
-import (
-       "bufio"
-       "fmt"
-       "github.com/gdamore/tcell"
-       "os"
-       "strconv"
-)
-
-// TermMessage sends a message to the user in the terminal. This usually occurs before
-// micro has been fully initialized -- ie if there is an error in the syntax highlighting
-// regular expressions
-// The function must be called when the screen is not initialized
-// This will write the message, and wait for the user
-// to press and key to continue
-func TermMessage(msg string) {
-       fmt.Println(msg)
-       fmt.Print("\nPress enter to continue")
-
-       reader := bufio.NewReader(os.Stdin)
-       reader.ReadString('\n')
-}
-
-// TermError sends an error to the user in the terminal. Like TermMessage except formatted
-// as an error
-func TermError(filename string, lineNum int, err string) {
-       TermMessage(filename + ", " + strconv.Itoa(lineNum) + ": " + err)
-}
-
-// Messenger is an object that makes it easy to send messages to the user
-// and get input from the user
-type Messenger struct {
-       // Are we currently prompting the user?
-       hasPrompt bool
-       // Is there a message to print
-       hasMessage bool
-
-       // Message to print
-       message string
-       // The user's response to a prompt
-       response string
-       // style to use when drawing the message
-       style tcell.Style
-
-       // We have to keep track of the cursor for prompting
-       cursorx int
-}
-
-// Message sends a message to the user
-func (m *Messenger) Message(msg string) {
-       m.message = msg
-       m.style = defStyle
-
-       if _, ok := colorscheme["message"]; ok {
-               m.style = colorscheme["message"]
-       }
-       m.hasMessage = true
-}
-
-// Error sends an error message to the user
-func (m *Messenger) Error(msg string) {
-       m.message = msg
-       m.style = defStyle.
-               Foreground(tcell.ColorBlack).
-               Background(tcell.ColorMaroon)
-
-       if _, ok := colorscheme["error-message"]; ok {
-               m.style = colorscheme["error-message"]
-       }
-       m.hasMessage = true
-}
-
-// YesNoPrompt asks the user a yes or no question (waits for y or n) and returns the result
-func (m *Messenger) YesNoPrompt(prompt string) bool {
-       m.Message(prompt)
-
-       for {
-               m.Clear()
-               m.Display()
-               screen.Show()
-               event := screen.PollEvent()
-
-               switch e := event.(type) {
-               case *tcell.EventKey:
-                       if e.Key() == tcell.KeyRune {
-                               if e.Rune() == 'y' {
-                                       return true
-                               } else if e.Rune() == 'n' {
-                                       return false
-                               }
-                       }
-               }
-       }
-}
-
-// Prompt sends the user a message and waits for a response to be typed in
-// This function blocks the main loop while waiting for input
-func (m *Messenger) Prompt(prompt string) (string, bool) {
-       m.hasPrompt = true
-       m.Message(prompt)
-
-       response, canceled := "", true
-
-       for m.hasPrompt {
-               m.Clear()
-               m.Display()
-
-               event := screen.PollEvent()
-
-               switch e := event.(type) {
-               case *tcell.EventKey:
-                       switch e.Key() {
-                       case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
-                               // Cancel
-                               m.hasPrompt = false
-                       case tcell.KeyEnter:
-                               // User is done entering their response
-                               m.hasPrompt = false
-                               response, canceled = m.response, false
-                       }
-               }
-
-               m.HandleEvent(event)
-
-               if m.cursorx < 0 {
-                       // Cancel
-                       m.hasPrompt = false
-               }
-       }
-
-       m.Reset()
-       return response, canceled
-}
-
-// HandleEvent handles an event for the prompter
-func (m *Messenger) HandleEvent(event tcell.Event) {
-       switch e := event.(type) {
-       case *tcell.EventKey:
-               switch e.Key() {
-               case tcell.KeyLeft:
-                       if m.cursorx > 0 {
-                               m.cursorx--
-                       }
-               case tcell.KeyRight:
-                       if m.cursorx < Count(m.response) {
-                               m.cursorx++
-                       }
-               case tcell.KeyBackspace2:
-                       if m.cursorx > 0 {
-                               m.response = string([]rune(m.response)[:m.cursorx-1]) + string(m.response[m.cursorx:])
-                       }
-                       m.cursorx--
-               case tcell.KeySpace:
-                       m.response += " "
-                       m.cursorx++
-               case tcell.KeyRune:
-                       m.response = Insert(m.response, m.cursorx, string(e.Rune()))
-                       m.cursorx++
-               }
-       }
-}
-
-// Reset resets the messenger's cursor, message and response
-func (m *Messenger) Reset() {
-       m.cursorx = 0
-       m.message = ""
-       m.response = ""
-}
-
-// Clear clears the line at the bottom of the editor
-func (m *Messenger) Clear() {
-       w, h := screen.Size()
-       for x := 0; x < w; x++ {
-               screen.SetContent(x, h-1, ' ', nil, defStyle)
-       }
-}
-
-// Display displays messages or prompts
-func (m *Messenger) Display() {
-       _, h := screen.Size()
-       if m.hasMessage {
-               runes := []rune(m.message + m.response)
-               for x := 0; x < len(runes); x++ {
-                       screen.SetContent(x, h-1, runes[x], nil, m.style)
-               }
-       }
-       if m.hasPrompt {
-               screen.ShowCursor(Count(m.message)+m.cursorx, h-1)
-               screen.Show()
-       }
-}
diff --git a/src/micro.go b/src/micro.go
deleted file mode 100644 (file)
index 8733aa4..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-package main
-
-import (
-       "fmt"
-       "github.com/gdamore/tcell"
-       "github.com/go-errors/errors"
-       "github.com/mattn/go-isatty"
-       "io/ioutil"
-       "os"
-)
-
-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
-
-       // Object to send messages and prompts to the user
-       messenger *Messenger
-
-       // The default style
-       defStyle tcell.Style
-)
-
-// LoadInput loads the file input for the editor
-func LoadInput() (string, []byte, error) {
-       // 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
-
-       // 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
-       // empty 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
-
-       if len(os.Args) > 1 {
-               // Option 1
-               filename = os.Args[1]
-               // Check that the file exists
-               if _, e := os.Stat(filename); e == nil {
-                       input, err = ioutil.ReadFile(filename)
-               }
-       } else if !isatty.IsTerminal(os.Stdin.Fd()) {
-               // Option 2
-               // The input is not a terminal, so something is being piped in
-               // and we should read from stdin
-               input, err = ioutil.ReadAll(os.Stdin)
-       }
-
-       // Option 3, or just return whatever we got
-       return filename, input, err
-}
-
-func main() {
-       filename, input, err := LoadInput()
-       if err != nil {
-               fmt.Println(err)
-               os.Exit(1)
-       }
-
-       InitSettings()
-
-       // Load the syntax files, including the colorscheme
-       LoadSyntaxFiles()
-
-       // Should we enable true color?
-       truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
-
-       // In order to enable true color, we have to set the TERM to `xterm-truecolor` when
-       // initializing tcell, but after that, we can set the TERM back to whatever it was
-       oldTerm := os.Getenv("TERM")
-       if truecolor {
-               os.Setenv("TERM", "xterm-truecolor")
-       }
-
-       // Initilize tcell
-       screen, err = tcell.NewScreen()
-       if err != nil {
-               fmt.Println(err)
-               os.Exit(1)
-       }
-       if err = screen.Init(); err != nil {
-               fmt.Println(err)
-               os.Exit(1)
-       }
-
-       // Now we can put the TERM back to what it was before
-       if truecolor {
-               os.Setenv("TERM", oldTerm)
-       }
-
-       // This is just so if we have an error, we can exit cleanly and not completely
-       // mess up the terminal being worked in
-       defer func() {
-               if err := recover(); err != nil {
-                       screen.Fini()
-                       fmt.Println("Micro encountered an error:", err)
-                       // Print the stack trace too
-                       fmt.Print(errors.Wrap(err, 2).ErrorStack())
-                       os.Exit(1)
-               }
-       }()
-
-       // Default style
-       defStyle = tcell.StyleDefault.
-               Foreground(tcell.ColorDefault).
-               Background(tcell.ColorDefault)
-
-       // There may be another default style defined in the colorscheme
-       if style, ok := colorscheme["default"]; ok {
-               defStyle = style
-       }
-
-       screen.SetStyle(defStyle)
-       screen.EnableMouse()
-
-       messenger = new(Messenger)
-       view := NewView(NewBuffer(string(input), filename))
-
-       for {
-               // Display everything
-               screen.Clear()
-
-               view.Display()
-               messenger.Display()
-
-               screen.Show()
-
-               // Wait for the user's action
-               event := screen.PollEvent()
-
-               if searching {
-                       HandleSearchEvent(event, view)
-               } else {
-                       // Check if we should quit
-                       switch e := event.(type) {
-                       case *tcell.EventKey:
-                               switch e.Key() {
-                               case tcell.KeyCtrlQ:
-                                       // Make sure not to quit if there are unsaved changes
-                                       if view.CanClose("Quit anyway? ") {
-                                               screen.Fini()
-                                               os.Exit(0)
-                                       }
-                               case tcell.KeyCtrlE:
-                                       input, canceled := messenger.Prompt("> ")
-                                       if !canceled {
-                                               HandleCommand(input, view)
-                                       }
-                               case tcell.KeyCtrlH:
-                                       DisplayHelp()
-                                       // Make sure to resize the view if the user resized the terminal while looking at the help text
-                                       view.Resize(screen.Size())
-                               }
-                       }
-
-                       // Send it to the view
-                       view.HandleEvent(event)
-               }
-       }
-}
diff --git a/src/search.go b/src/search.go
deleted file mode 100644 (file)
index f5efce6..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-package main
-
-import (
-       "github.com/gdamore/tcell"
-       "regexp"
-)
-
-var (
-       // What was the last search
-       lastSearch string
-
-       // Where should we start the search down from (or up from)
-       searchStart int
-
-       // Is there currently a search in progress
-       searching bool
-)
-
-// BeginSearch starts a search
-func BeginSearch() {
-       searching = true
-       messenger.hasPrompt = true
-       messenger.Message("Find: ")
-}
-
-// EndSearch stops the current search
-func EndSearch() {
-       searching = false
-       messenger.hasPrompt = false
-       messenger.Clear()
-       messenger.Reset()
-}
-
-// HandleSearchEvent takes an event and a view and will do a real time match from the messenger's output
-// to the current buffer. It searches down the buffer.
-func HandleSearchEvent(event tcell.Event, v *View) {
-       switch e := event.(type) {
-       case *tcell.EventKey:
-               switch e.Key() {
-               case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape, tcell.KeyEnter:
-                       // Done
-                       EndSearch()
-                       return
-               }
-       }
-
-       messenger.HandleEvent(event)
-
-       if messenger.cursorx < 0 {
-               // Done
-               EndSearch()
-               return
-       }
-
-       if messenger.response == "" {
-               v.cursor.ResetSelection()
-               // We don't end the search though
-               return
-       }
-
-       Search(messenger.response, v, true)
-
-       return
-}
-
-// Search searches in the view for the given regex. The down bool
-// specifies whether it should search down from the searchStart position
-// or up from there
-func Search(searchStr string, v *View, down bool) {
-       if searchStr == "" {
-               return
-       }
-       var str string
-       var charPos int
-       if down {
-               str = v.buf.text[searchStart:]
-               charPos = searchStart
-       } else {
-               str = v.buf.text[:searchStart]
-       }
-       r, err := regexp.Compile(searchStr)
-       if err != nil {
-               return
-       }
-       matches := r.FindAllStringIndex(str, -1)
-       var match []int
-       if matches == nil {
-               // Search the entire buffer now
-               matches = r.FindAllStringIndex(v.buf.text, -1)
-               charPos = 0
-               if matches == nil {
-                       v.cursor.ResetSelection()
-                       return
-               }
-
-               if !down {
-                       match = matches[len(matches)-1]
-               } else {
-                       match = matches[0]
-               }
-       }
-
-       if !down {
-               match = matches[len(matches)-1]
-       } else {
-               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)
-       if v.Relocate() {
-               v.matches = Match(v)
-       }
-       lastSearch = searchStr
-}
diff --git a/src/settings.go b/src/settings.go
deleted file mode 100644 (file)
index 0e026f5..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-package main
-
-import (
-       "encoding/json"
-       "github.com/mitchellh/go-homedir"
-       "io/ioutil"
-       "os"
-       "strconv"
-       "strings"
-)
-
-// The options that the user can set
-var settings Settings
-
-// All the possible settings
-var possibleSettings = []string{"colorscheme", "tabsize", "autoindent", "syntax"}
-
-// The Settings struct contains the settings for micro
-type Settings struct {
-       Colorscheme string `json:"colorscheme"`
-       TabSize     int    `json:"tabsize"`
-       AutoIndent  bool   `json:"autoindent"`
-       Syntax      bool   `json:"syntax"`
-}
-
-// InitSettings initializes the options map and sets all options to their default values
-func InitSettings() {
-       home, err := homedir.Dir()
-       if err != nil {
-               TermMessage("Error finding your home directory\nCan't load settings file")
-               return
-       }
-
-       filename := home + "/.micro/settings.json"
-       if _, e := os.Stat(filename); e == nil {
-               input, err := ioutil.ReadFile(filename)
-               if err != nil {
-                       TermMessage("Error reading settings.json file: " + err.Error())
-                       return
-               }
-
-               json.Unmarshal(input, &settings)
-       } else {
-               settings = DefaultSettings()
-               err := WriteSettings(filename)
-               if err != nil {
-                       TermMessage("Error writing settings.json file: " + err.Error())
-               }
-       }
-}
-
-// WriteSettings writes the settings to the specified filename as JSON
-func WriteSettings(filename string) error {
-       var err error
-       home, err := homedir.Dir()
-       if err != nil {
-               return err
-       }
-       if _, e := os.Stat(home + "/.micro"); e == nil {
-               txt, _ := json.MarshalIndent(settings, "", "    ")
-               err = ioutil.WriteFile(filename, txt, 0644)
-       }
-       return err
-}
-
-// DefaultSettings returns the default settings for micro
-func DefaultSettings() Settings {
-       return Settings{
-               Colorscheme: "default",
-               TabSize:     4,
-               AutoIndent:  true,
-               Syntax:      true,
-       }
-}
-
-// SetOption prompts the user to set an option and checks that the response is valid
-func SetOption(view *View, args []string) {
-       home, err := homedir.Dir()
-       if err != nil {
-               messenger.Error("Error finding your home directory\nCan't load settings file")
-       }
-
-       filename := home + "/.micro/settings.json"
-       if len(args) == 2 {
-               option := strings.TrimSpace(args[0])
-               value := strings.TrimSpace(args[1])
-
-               if Contains(possibleSettings, option) {
-                       if option == "tabsize" {
-                               tsize, err := strconv.Atoi(value)
-                               if err != nil {
-                                       messenger.Error("Invalid value for " + option)
-                                       return
-                               }
-                               settings.TabSize = tsize
-                       } else if option == "colorscheme" {
-                               settings.Colorscheme = value
-                               LoadSyntaxFiles()
-                               view.buf.UpdateRules()
-                       } else if option == "syntax" {
-                               if value == "on" {
-                                       settings.Syntax = true
-                               } else if value == "off" {
-                                       settings.Syntax = false
-                               } else {
-                                       messenger.Error("Invalid value for " + option)
-                                       return
-                               }
-                               LoadSyntaxFiles()
-                               view.buf.UpdateRules()
-                       }
-                       err := WriteSettings(filename)
-                       if err != nil {
-                               messenger.Error("Error writing to settings.json: " + err.Error())
-                               return
-                       }
-               } else {
-                       messenger.Error("Option " + option + " does not exist")
-               }
-       } else {
-               messenger.Error("Invalid option, please use option value")
-       }
-}
diff --git a/src/stack.go b/src/stack.go
deleted file mode 100644 (file)
index c7c8fd7..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-package main
-
-// Stack is a simple implementation of a LIFO stack
-type Stack struct {
-       top  *Element
-       size int
-}
-
-// An Element which is stored in the Stack
-type Element struct {
-       value interface{} // All types satisfy the empty interface, so we can store anything here.
-       next  *Element
-}
-
-// Len returns the stack's length
-func (s *Stack) Len() int {
-       return s.size
-}
-
-// Push a new element onto the stack
-func (s *Stack) Push(value interface{}) {
-       s.top = &Element{value, s.top}
-       s.size++
-}
-
-// Pop removes the top element from the stack and returns its value
-// If the stack is empty, return nil
-func (s *Stack) Pop() (value interface{}) {
-       if s.size > 0 {
-               value, s.top = s.top.value, s.top.next
-               s.size--
-               return
-       }
-       return nil
-}
-
-// Peek returns the top element of the stack without removing it
-func (s *Stack) Peek() interface{} {
-       if s.size > 0 {
-               return s.top.value
-       }
-       return nil
-}
diff --git a/src/stack_test.go b/src/stack_test.go
deleted file mode 100644 (file)
index a352694..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package main
-
-import "testing"
-
-func TestStack(t *testing.T) {
-       stack := new(Stack)
-
-       if stack.Len() != 0 {
-               t.Errorf("Len failed")
-       }
-       stack.Push(5)
-       stack.Push("test")
-       stack.Push(10)
-       if stack.Len() != 3 {
-               t.Errorf("Len failed")
-       }
-
-       var popped interface{}
-       popped = stack.Pop()
-       if popped != 10 {
-               t.Errorf("Pop failed")
-       }
-
-       popped = stack.Pop()
-       if popped != "test" {
-               t.Errorf("Pop failed")
-       }
-
-       stack.Push("test")
-       popped = stack.Pop()
-       if popped != "test" {
-               t.Errorf("Pop failed")
-       }
-       stack.Pop()
-       popped = stack.Pop()
-       if popped != nil {
-               t.Errorf("Pop failed")
-       }
-}
diff --git a/src/statusline.go b/src/statusline.go
deleted file mode 100644 (file)
index f02d499..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-package main
-
-import (
-       "strconv"
-)
-
-// Statusline represents the information line at the bottom
-// of each view
-// It gives information such as filename, whether the file has been
-// modified, filetype, cursor location
-type Statusline struct {
-       view *View
-}
-
-// Display draws the statusline to the screen
-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
-       // 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() {
-               file += " +"
-       }
-
-       // Add one to cursor.x and cursor.y because (0,0) is the top left,
-       // 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)
-
-       file += " (" + lineNum + "," + columnNum + ")"
-
-       // Add the filetype
-       file += " " + sline.view.buf.filetype
-
-       centerText := "Press Ctrl-h for help"
-
-       statusLineStyle := defStyle.Reverse(true)
-       if style, ok := colorscheme["statusline"]; ok {
-               statusLineStyle = style
-       }
-
-       // Maybe there is a unicode filename?
-       fileRunes := []rune(file)
-       for x := 0; x < sline.view.width; x++ {
-               if x < len(fileRunes) {
-                       screen.SetContent(x, y, fileRunes[x], nil, statusLineStyle)
-               } else if x >= sline.view.width/2-len(centerText)/2 && x < len(centerText)+sline.view.width/2-len(centerText)/2 {
-                       screen.SetContent(x, y, []rune(centerText)[x-sline.view.width/2+len(centerText)/2], nil, statusLineStyle)
-               } else {
-                       screen.SetContent(x, y, ' ', nil, statusLineStyle)
-               }
-       }
-}
diff --git a/src/util.go b/src/util.go
deleted file mode 100644 (file)
index 069aff6..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-package main
-
-import (
-       "unicode/utf8"
-)
-
-// Util.go is a collection of utility functions that are used throughout
-// the program
-
-// Count returns the length of a string in runes
-// This is exactly equivalent to utf8.RuneCountInString(), just less characters
-func Count(s string) int {
-       return utf8.RuneCountInString(s)
-}
-
-// NumOccurences counts the number of occurences of a byte in a string
-func NumOccurences(s string, c byte) int {
-       var n int
-       for i := 0; i < len(s); i++ {
-               if s[i] == c {
-                       n++
-               }
-       }
-       return n
-}
-
-// Spaces returns a string with n spaces
-func Spaces(n int) string {
-       var str string
-       for i := 0; i < n; i++ {
-               str += " "
-       }
-       return str
-}
-
-// Min takes the min of two ints
-func Min(a, b int) int {
-       if a > b {
-               return b
-       }
-       return a
-}
-
-// Max takes the max of two ints
-func Max(a, b int) int {
-       if a > b {
-               return a
-       }
-       return b
-}
-
-// IsWordChar returns whether or not the string is a 'word character'
-// If it is a unicode character, then it does not match
-// Word characters are defined as [A-Za-z0-9_]
-func IsWordChar(str string) bool {
-       if len(str) > 1 {
-               // Unicode
-               return false
-       }
-       c := str[0]
-       return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
-}
-
-// Contains returns whether or not a string array contains a given string
-func Contains(list []string, a string) bool {
-       for _, b := range list {
-               if b == a {
-                       return true
-               }
-       }
-       return false
-}
-
-// Insert makes a simple insert into a string at the given position
-func Insert(str string, pos int, value string) string {
-       return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
-}
diff --git a/src/util_test.go b/src/util_test.go
deleted file mode 100644 (file)
index 29fc009..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-package main
-
-import "testing"
-
-func TestNumOccurences(t *testing.T) {
-       var tests = []struct {
-               inputStr  string
-               inputChar byte
-               want      int
-       }{
-               {"aaaa", 'a', 4},
-               {"\trfd\ta", '\t', 2},
-               {"∆ƒ\tø Â®Â \t\t", '\t', 3},
-       }
-       for _, test := range tests {
-               if got := NumOccurences(test.inputStr, test.inputChar); got != test.want {
-                       t.Errorf("NumOccurences(%s, %c) = %d", test.inputStr, test.inputChar, got)
-               }
-       }
-}
-
-func TestSpaces(t *testing.T) {
-       var tests = []struct {
-               input int
-               want  string
-       }{
-               {4, "    "},
-               {0, ""},
-       }
-       for _, test := range tests {
-               if got := Spaces(test.input); got != test.want {
-                       t.Errorf("Spaces(%d) = \"%s\"", test.input, got)
-               }
-       }
-}
-
-func TestIsWordChar(t *testing.T) {
-       if IsWordChar("t") == false {
-               t.Errorf("IsWordChar(t) = false")
-       }
-       if IsWordChar("T") == false {
-               t.Errorf("IsWordChar(T) = false")
-       }
-       if IsWordChar("5") == false {
-               t.Errorf("IsWordChar(5) = false")
-       }
-       if IsWordChar("_") == false {
-               t.Errorf("IsWordChar(_) = false")
-       }
-       if IsWordChar("~") == true {
-               t.Errorf("IsWordChar(~) = true")
-       }
-       if IsWordChar(" ") == true {
-               t.Errorf("IsWordChar( ) = true")
-       }
-       if IsWordChar("ß") == true {
-               t.Errorf("IsWordChar(ß) = true")
-       }
-       if IsWordChar(")") == true {
-               t.Errorf("IsWordChar()) = true")
-       }
-       if IsWordChar("\n") == true {
-               t.Errorf("IsWordChar(\n)) = true")
-       }
-}
diff --git a/src/view.go b/src/view.go
deleted file mode 100644 (file)
index 347d50e..0000000
+++ /dev/null
@@ -1,737 +0,0 @@
-package main
-
-import (
-       "github.com/atotto/clipboard"
-       "github.com/gdamore/tcell"
-       "io/ioutil"
-       "strconv"
-       "strings"
-       "time"
-)
-
-// The View struct stores information about a view into a buffer.
-// It has a stores information about the cursor, and the viewport
-// that the user sees the buffer from.
-type View struct {
-       cursor Cursor
-
-       // The topmost line, used for vertical scrolling
-       topline int
-       // The leftmost column, used for horizontal scrolling
-       leftCol int
-
-       // Percentage of the terminal window that this view takes up (from 0 to 100)
-       widthPercent  int
-       heightPercent int
-
-       // Actual with and height
-       width  int
-       height int
-
-       // How much to offset because of line numbers
-       lineNumOffset int
-
-       // The eventhandler for undo/redo
-       eh *EventHandler
-
-       // The buffer
-       buf *Buffer
-       // The statusline
-       sline Statusline
-
-       // Since tcell doesn't differentiate between a mouse release event
-       // and a mouse move event with no keys pressed, we need to keep
-       // track of whether or not the mouse was pressed (or not released) last event to determine
-       // mouse release events
-       mouseReleased bool
-
-       // This stores when the last click was
-       // This is useful for detecting double and triple clicks
-       lastClickTime time.Time
-
-       // Was the last mouse event actually a double click?
-       // Useful for detecting triple clicks -- if a double click is detected
-       // but the last mouse event was actually a double click, it's a triple click
-       doubleClick bool
-       // Same here, just to keep track for mouse move events
-       tripleClick bool
-
-       // Syntax highlighting matches
-       matches SyntaxMatches
-       // The matches from the last frame
-       lastMatches SyntaxMatches
-
-       // This is the range of lines that should have their syntax highlighting updated
-       updateLines [2]int
-}
-
-// NewView returns a new fullscreen view
-func NewView(buf *Buffer) *View {
-       return NewViewWidthHeight(buf, 100, 100)
-}
-
-// NewViewWidthHeight returns a new view with the specified width and height percentages
-// Note that w and h are percentages not actual values
-func NewViewWidthHeight(buf *Buffer, w, h int) *View {
-       v := new(View)
-
-       v.buf = buf
-
-       v.widthPercent = w
-       v.heightPercent = h
-       v.Resize(screen.Size())
-
-       v.topline = 0
-       // Put the cursor at the first spot
-       v.cursor = Cursor{
-               x: 0,
-               y: 0,
-               v: v,
-       }
-       v.cursor.ResetSelection()
-
-       v.eh = NewEventHandler(v)
-
-       v.sline = Statusline{
-               view: v,
-       }
-
-       // Update the syntax highlighting for the entire buffer at the start
-       v.UpdateLines(v.topline, v.topline+v.height)
-       v.matches = Match(v)
-
-       // Set mouseReleased to true because we assume the mouse is not being pressed when
-       // the editor is opened
-       v.mouseReleased = true
-       v.lastClickTime = time.Time{}
-
-       return v
-}
-
-// UpdateLines sets the values for v.updateLines
-func (v *View) UpdateLines(start, end int) {
-       v.updateLines[0] = start
-       v.updateLines[1] = end + 1
-}
-
-// Resize recalculates the actual width and height of the view from the width and height
-// percentages
-// This is usually called when the window is resized, or when a split has been added and
-// the percentages have changed
-func (v *View) Resize(w, h int) {
-       // Always include 1 line for the command line at the bottom
-       h--
-       v.width = int(float32(w) * float32(v.widthPercent) / 100)
-       // We subtract 1 for the statusline
-       v.height = int(float32(h)*float32(v.heightPercent)/100) - 1
-}
-
-// 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--
-       }
-}
-
-// 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 <= len(v.buf.lines)-v.height {
-               v.topline += n
-       } else if v.topline < len(v.buf.lines)-v.height {
-               v.topline++
-       }
-}
-
-// PageUp scrolls the view up a page
-func (v *View) PageUp() {
-       if v.topline > v.height {
-               v.ScrollUp(v.height)
-       } else {
-               v.topline = 0
-       }
-}
-
-// PageDown scrolls the view down a page
-func (v *View) PageDown() {
-       if len(v.buf.lines)-(v.topline+v.height) > v.height {
-               v.ScrollDown(v.height)
-       } else {
-               if len(v.buf.lines) >= v.height {
-                       v.topline = len(v.buf.lines) - v.height
-               }
-       }
-}
-
-// HalfPageUp scrolls the view up half a page
-func (v *View) HalfPageUp() {
-       if v.topline > v.height/2 {
-               v.ScrollUp(v.height / 2)
-       } else {
-               v.topline = 0
-       }
-}
-
-// HalfPageDown scrolls the view down half a page
-func (v *View) HalfPageDown() {
-       if len(v.buf.lines)-(v.topline+v.height) > v.height/2 {
-               v.ScrollDown(v.height / 2)
-       } else {
-               if len(v.buf.lines) >= v.height {
-                       v.topline = len(v.buf.lines) - v.height
-               }
-       }
-}
-
-// CanClose returns whether or not the view can be closed
-// If there are unsaved changes, the user will be asked if the view can be closed
-// 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() {
-               quit, canceled := messenger.Prompt("You have unsaved changes. " + msg)
-               if !canceled {
-                       if strings.ToLower(quit) == "yes" || strings.ToLower(quit) == "y" {
-                               return true
-                       }
-               }
-       } else {
-               return true
-       }
-       return false
-}
-
-// Save the buffer to disk
-func (v *View) Save() {
-       // If this is an empty buffer, ask for a filename
-       if v.buf.path == "" {
-               filename, canceled := messenger.Prompt("Filename: ")
-               if !canceled {
-                       v.buf.path = filename
-                       v.buf.name = filename
-               } else {
-                       return
-               }
-       }
-       err := v.buf.Save()
-       if err != nil {
-               messenger.Error(err.Error())
-       } else {
-               messenger.Message("Saved " + v.buf.path)
-       }
-}
-
-// Copy the selection to the system clipboard
-func (v *View) Copy() {
-       if v.cursor.HasSelection() {
-               if !clipboard.Unsupported {
-                       clipboard.WriteAll(v.cursor.GetSelection())
-               } else {
-                       messenger.Error("Clipboard is not supported on your system")
-               }
-       }
-}
-
-// Cut the selection to the system clipboard
-func (v *View) Cut() {
-       if v.cursor.HasSelection() {
-               if !clipboard.Unsupported {
-                       clipboard.WriteAll(v.cursor.GetSelection())
-                       v.cursor.DeleteSelection()
-                       v.cursor.ResetSelection()
-               } else {
-                       messenger.Error("Clipboard is not supported on your system")
-               }
-       }
-}
-
-// Paste whatever is in the system clipboard into the buffer
-// Delete and paste if the user has a selection
-func (v *View) Paste() {
-       if !clipboard.Unsupported {
-               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))
-       } else {
-               messenger.Error("Clipboard is not supported on your system")
-       }
-}
-
-// SelectAll selects the entire buffer
-func (v *View) SelectAll() {
-       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
-}
-
-// OpenFile opens a new file in the current view
-// It makes sure that the current buffer can be closed first (unsaved changes)
-func (v *View) OpenFile() {
-       if v.CanClose("Continue? ") {
-               filename, canceled := messenger.Prompt("File to open: ")
-               if canceled {
-                       return
-               }
-               file, err := ioutil.ReadFile(filename)
-
-               if err != nil {
-                       messenger.Error(err.Error())
-                       return
-               }
-               v.buf = NewBuffer(string(file), filename)
-       }
-}
-
-// Relocate moves the view window so that the cursor is in view
-// 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
-               ret = true
-       }
-       if cy > v.topline+v.height-1 {
-               v.topline = cy - v.height + 1
-               ret = true
-       }
-
-       cx := v.cursor.GetVisualX()
-       if cx < v.leftCol {
-               v.leftCol = cx
-               ret = true
-       }
-       if cx+v.lineNumOffset+1 > v.leftCol+v.width {
-               v.leftCol = cx - v.width + v.lineNumOffset + 1
-               ret = true
-       }
-       return ret
-}
-
-// 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 {
-               v.ScrollDown(1)
-               y = v.height + v.topline - 1
-       }
-       if y >= len(v.buf.lines) {
-               y = len(v.buf.lines) - 1
-       }
-       if x < 0 {
-               x = 0
-       }
-
-       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()
-}
-
-// HandleEvent handles an event passed by the main loop
-func (v *View) HandleEvent(event tcell.Event) {
-       // This bool determines whether the view is relocated at the end of the function
-       // By default it's true because most events should cause a relocate
-       relocate := true
-
-       // By default we don't update and syntax highlighting
-       v.UpdateLines(-2, 0)
-       switch e := event.(type) {
-       case *tcell.EventResize:
-               // Window resized
-               v.Resize(e.Size())
-       case *tcell.EventKey:
-               switch e.Key() {
-               case tcell.KeyUp:
-                       // Cursor up
-                       v.cursor.ResetSelection()
-                       v.cursor.Up()
-               case tcell.KeyDown:
-                       // Cursor down
-                       v.cursor.ResetSelection()
-                       v.cursor.Down()
-               case tcell.KeyLeft:
-                       // Cursor left
-                       v.cursor.ResetSelection()
-                       v.cursor.Left()
-               case tcell.KeyRight:
-                       // Cursor right
-                       v.cursor.ResetSelection()
-                       v.cursor.Right()
-               case tcell.KeyEnter:
-                       // Insert a newline
-                       if v.cursor.HasSelection() {
-                               v.cursor.DeleteSelection()
-                               v.cursor.ResetSelection()
-                       }
-                       v.eh.Insert(v.cursor.Loc(), "\n")
-                       v.cursor.Right()
-                       // Rehighlight the entire buffer
-                       v.UpdateLines(v.topline, v.topline+v.height)
-                       v.cursor.lastVisualX = v.cursor.GetVisualX()
-                       // v.UpdateLines(v.cursor.y-1, v.cursor.y)
-               case tcell.KeySpace:
-                       // Insert a space
-                       if v.cursor.HasSelection() {
-                               v.cursor.DeleteSelection()
-                               v.cursor.ResetSelection()
-                       }
-                       v.eh.Insert(v.cursor.Loc(), " ")
-                       v.cursor.Right()
-                       v.UpdateLines(v.cursor.y, v.cursor.y)
-               case tcell.KeyBackspace2:
-                       // Delete a character
-                       if v.cursor.HasSelection() {
-                               v.cursor.DeleteSelection()
-                               v.cursor.ResetSelection()
-                               // Rehighlight the entire buffer
-                               v.UpdateLines(v.topline, v.topline+v.height)
-                       } 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
-                               // So instead we move left, save the position, move back, delete
-                               // and restore the position
-                               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
-                               // Rehighlight the entire buffer
-                               v.UpdateLines(v.topline, v.topline+v.height)
-                               // v.UpdateLines(v.cursor.y, v.cursor.y+1)
-                       }
-                       v.cursor.lastVisualX = v.cursor.GetVisualX()
-               case tcell.KeyTab:
-                       // Insert a tab
-                       if v.cursor.HasSelection() {
-                               v.cursor.DeleteSelection()
-                               v.cursor.ResetSelection()
-                       }
-                       v.eh.Insert(v.cursor.Loc(), "\t")
-                       v.cursor.Right()
-                       v.UpdateLines(v.cursor.y, v.cursor.y)
-               case tcell.KeyCtrlS:
-                       v.Save()
-               case tcell.KeyCtrlF:
-                       if v.cursor.HasSelection() {
-                               searchStart = v.cursor.curSelection[1]
-                       } else {
-                               searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
-                       }
-                       BeginSearch()
-               case tcell.KeyCtrlN:
-                       if v.cursor.HasSelection() {
-                               searchStart = v.cursor.curSelection[1]
-                       } else {
-                               searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
-                       }
-                       messenger.Message("Find: " + lastSearch)
-                       Search(lastSearch, v, true)
-               case tcell.KeyCtrlP:
-                       if v.cursor.HasSelection() {
-                               searchStart = v.cursor.curSelection[0]
-                       } else {
-                               searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
-                       }
-                       messenger.Message("Find: " + lastSearch)
-                       Search(lastSearch, v, false)
-               case tcell.KeyCtrlZ:
-                       v.eh.Undo()
-                       // Rehighlight the entire buffer
-                       v.UpdateLines(v.topline, v.topline+v.height)
-               case tcell.KeyCtrlY:
-                       v.eh.Redo()
-                       // Rehighlight the entire buffer
-                       v.UpdateLines(v.topline, v.topline+v.height)
-               case tcell.KeyCtrlC:
-                       v.Copy()
-                       // Rehighlight the entire buffer
-                       v.UpdateLines(v.topline, v.topline+v.height)
-               case tcell.KeyCtrlX:
-                       v.Cut()
-                       // Rehighlight the entire buffer
-                       v.UpdateLines(v.topline, v.topline+v.height)
-               case tcell.KeyCtrlV:
-                       v.Paste()
-                       // Rehighlight the entire buffer
-                       v.UpdateLines(v.topline, v.topline+v.height)
-               case tcell.KeyCtrlA:
-                       v.SelectAll()
-               case tcell.KeyCtrlO:
-                       v.OpenFile()
-                       // Rehighlight the entire buffer
-                       v.UpdateLines(v.topline, v.topline+v.height)
-               case tcell.KeyPgUp:
-                       v.PageUp()
-                       relocate = false
-               case tcell.KeyPgDn:
-                       v.PageDown()
-                       relocate = false
-               case tcell.KeyCtrlU:
-                       v.HalfPageUp()
-                       relocate = false
-               case tcell.KeyCtrlD:
-                       v.HalfPageDown()
-                       relocate = false
-               case tcell.KeyRune:
-                       // Insert a character
-                       if v.cursor.HasSelection() {
-                               v.cursor.DeleteSelection()
-                               v.cursor.ResetSelection()
-                               // Rehighlight the entire buffer
-                               v.UpdateLines(v.topline, v.topline+v.height)
-                       } else {
-                               v.UpdateLines(v.cursor.y, v.cursor.y)
-                       }
-                       v.eh.Insert(v.cursor.Loc(), string(e.Rune()))
-                       v.cursor.Right()
-               }
-       case *tcell.EventMouse:
-               x, y := e.Position()
-               x -= v.lineNumOffset - v.leftCol
-               y += v.topline
-               // Position always seems to be off by one
-               x--
-               y--
-
-               button := e.Buttons()
-
-               switch button {
-               case tcell.Button1:
-                       // Left click
-                       origX, origY := v.cursor.x, v.cursor.y
-                       v.MoveToMouseClick(x, y)
-
-                       if v.mouseReleased {
-                               if (time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold) &&
-                                       (origX == v.cursor.x && origY == v.cursor.y) {
-                                       if v.doubleClick {
-                                               // Triple click
-                                               v.lastClickTime = time.Now()
-
-                                               v.tripleClick = true
-                                               v.doubleClick = false
-
-                                               v.cursor.SelectLine()
-                                       } else {
-                                               // Double click
-                                               v.lastClickTime = time.Now()
-
-                                               v.doubleClick = true
-                                               v.tripleClick = false
-
-                                               v.cursor.SelectWord()
-                                       }
-                               } else {
-                                       v.doubleClick = false
-                                       v.tripleClick = false
-                                       v.lastClickTime = time.Now()
-
-                                       loc := v.cursor.Loc()
-                                       v.cursor.curSelection[0] = loc
-                                       v.cursor.curSelection[1] = loc
-                               }
-                       } else {
-                               if v.tripleClick {
-                                       v.cursor.AddLineToSelection()
-                               } else if v.doubleClick {
-                                       v.cursor.AddWordToSelection()
-                               } else {
-                                       v.cursor.curSelection[1] = v.cursor.Loc()
-                               }
-                       }
-                       v.mouseReleased = false
-               case tcell.ButtonNone:
-                       // Mouse event with no click
-                       if !v.mouseReleased {
-                               // Mouse was just released
-
-                               // Relocating here isn't really necessary because the cursor will
-                               // be in the right place from the last mouse event
-                               // However, if we are running in a terminal that doesn't support mouse motion
-                               // events, this still allows the user to make selections, except only after they
-                               // release the mouse
-
-                               if !v.doubleClick && !v.tripleClick {
-                                       v.MoveToMouseClick(x, y)
-                                       v.cursor.curSelection[1] = v.cursor.Loc()
-                               }
-                               v.mouseReleased = true
-                       }
-                       // We don't want to relocate because otherwise the view will be relocated
-                       // every time the user moves the cursor
-                       relocate = false
-               case tcell.WheelUp:
-                       // Scroll up two lines
-                       v.ScrollUp(2)
-                       // We don't want to relocate if the user is scrolling
-                       relocate = false
-                       // Rehighlight the entire buffer
-                       v.UpdateLines(v.topline, v.topline+v.height)
-               case tcell.WheelDown:
-                       // Scroll down two lines
-                       v.ScrollDown(2)
-                       // We don't want to relocate if the user is scrolling
-                       relocate = false
-                       // Rehighlight the entire buffer
-                       v.UpdateLines(v.topline, v.topline+v.height)
-               }
-       }
-
-       if relocate {
-               v.Relocate()
-       }
-       if settings.Syntax {
-               v.matches = Match(v)
-       }
-}
-
-// DisplayView renders the view to the screen
-func (v *View) DisplayView() {
-       // matches := make(SyntaxMatches, len(v.buf.lines))
-       //
-       // viewStart := v.topline
-       // viewEnd := v.topline + v.height
-       // if viewEnd > len(v.buf.lines) {
-       //      viewEnd = len(v.buf.lines)
-       // }
-       //
-       // lines := v.buf.lines[viewStart:viewEnd]
-       // for i, line := range lines {
-       //      matches[i] = make([]tcell.Style, len(line))
-       // }
-
-       // The character number of the character in the top left of the screen
-
-       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(len(v.buf.lines)))
-       // + 1 for the little space after the line number
-       v.lineNumOffset = maxLineLength + 1
-
-       var highlightStyle tcell.Style
-
-       for lineN := 0; lineN < v.height; lineN++ {
-               var x int
-               // If the buffer is smaller than the view height
-               // and we went too far, break
-               if lineN+v.topline >= len(v.buf.lines) {
-                       break
-               }
-               line := v.buf.lines[lineN+v.topline]
-
-               // Write the line number
-               lineNumStyle := defStyle
-               if style, ok := colorscheme["line-number"]; ok {
-                       lineNumStyle = style
-               }
-               // Write the spaces before the line number if necessary
-               lineNum := strconv.Itoa(lineN + v.topline + 1)
-               for i := 0; i < maxLineLength-len(lineNum); i++ {
-                       screen.SetContent(x, lineN, ' ', nil, lineNumStyle)
-                       x++
-               }
-               // Write the actual line number
-               for _, ch := range lineNum {
-                       screen.SetContent(x, lineN, ch, nil, lineNumStyle)
-                       x++
-               }
-               // Write the extra space
-               screen.SetContent(x, lineN, ' ', nil, lineNumStyle)
-               x++
-
-               // Write the line
-               tabchars := 0
-               runes := []rune(line)
-               for colN := v.leftCol; colN < v.leftCol+v.width; colN++ {
-                       if colN >= len(runes) {
-                               break
-                       }
-                       ch := runes[colN]
-                       var lineStyle tcell.Style
-                       // Does the current character need to be syntax highlighted?
-
-                       // if lineN >= v.updateLines[0] && lineN < v.updateLines[1] {
-                       if settings.Syntax {
-                               highlightStyle = v.matches[lineN][colN]
-                       }
-                       // } else if lineN < len(v.lastMatches) && colN < len(v.lastMatches[lineN]) {
-                       // highlightStyle = v.lastMatches[lineN][colN]
-                       // } else {
-                       // highlightStyle = defStyle
-                       // }
-
-                       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 = defStyle.Reverse(true)
-
-                               if style, ok := colorscheme["selection"]; ok {
-                                       lineStyle = style
-                               }
-                       } else {
-                               lineStyle = highlightStyle
-                       }
-                       // matches[lineN][colN] = highlightStyle
-
-                       if ch == '\t' {
-                               screen.SetContent(x+tabchars, lineN, ' ', nil, lineStyle)
-                               tabSize := settings.TabSize
-                               for i := 0; i < tabSize-1; i++ {
-                                       tabchars++
-                                       if x-v.leftCol+tabchars >= v.lineNumOffset {
-                                               screen.SetContent(x-v.leftCol+tabchars, lineN, ' ', nil, lineStyle)
-                                       }
-                               }
-                       } else {
-                               if x-v.leftCol+tabchars >= v.lineNumOffset {
-                                       screen.SetContent(x-v.leftCol+tabchars, lineN, ch, nil, lineStyle)
-                               }
-                       }
-                       charNum++
-                       x++
-               }
-               // Here we are at a newline
-
-               // 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]) {
-
-                       selectStyle := defStyle.Reverse(true)
-
-                       if style, ok := colorscheme["selection"]; ok {
-                               selectStyle = style
-                       }
-                       screen.SetContent(x-v.leftCol+tabchars, lineN, ' ', nil, selectStyle)
-               }
-
-               charNum++
-       }
-       // v.lastMatches = matches
-}
-
-// Display renders the view, the cursor, and statusline
-func (v *View) Display() {
-       v.DisplayView()
-       v.cursor.Display()
-       v.sline.Display()
-}