Compare commits

..

19 Commits

Author SHA1 Message Date
Zachary Yedidia
5c8a2332d9 Use unicode.Mark for combining unicode range 2020-05-20 18:01:10 -04:00
Zachary Yedidia
ff0683d6d0 Final touches for combining character support 2020-05-20 17:00:56 -04:00
Zachary Yedidia
79c0ea17ad Use CharacterCount over RuneCount 2020-05-20 16:47:08 -04:00
Zachary Yedidia
bdff221870 Use DecodeCharacter over DecodeRune 2020-05-20 16:43:12 -04:00
Zachary Yedidia
65be5efd83 Merge branch 'p-e-w-buffer-benchmarks' 2020-05-20 15:29:02 -04:00
Zachary Yedidia
a491dd1c52 Merge branch 'buffer-benchmarks' of https://github.com/p-e-w/micro into p-e-w-buffer-benchmarks 2020-05-20 15:28:36 -04:00
Zachary Yedidia
d7ab44253f Update tcell and support italics in colorschemes
Closes #1640
2020-05-17 12:48:34 -04:00
Zachary Yedidia
0a6720498f Merge branch 'master' of https://github.com/zyedidia/micro 2020-05-17 12:23:21 -04:00
dmaluka
a150eef6f9 Fix end line number in HighlightMatches (#1662)
There is a bit of mess in the usage of HighlightMatches: in some places
we assume that it updates lines from startline to endline inclusive,
in other places we assume it's non-inclusive.
This fix makes it always inclusive.

In particular, it fixes a bug: when we open a file which has no
newline at the end, the last line isn't highlighted.
2020-05-17 16:05:34 -04:00
Zachary Yedidia
c46257222c Add support for FindLiteral
Use the FindLiteral action to use Find without regex support.

Fixes #1661
2020-05-17 12:22:33 -04:00
jsyedidia
299af4a3db Update hlint to 3.0 syntax (#1659) 2020-05-16 13:06:55 -04:00
Jeff Warner
d0f7ecf9ca Adds command "tabmove ±n", for better tab management (#1636)
* Adds command "tabmove ±n", for better tab management

* Added tabmove to help:commands

* Replace uses of util.Min, util.Max with util.Clamp

Browsing code and discovered `util.Clamp`, ideal for this section of my code

* oops, missed an arg

* Typo, again
2020-05-14 21:51:49 -04:00
dmaluka
fb35e0312a Fix unbind of a rune (#1649)
Fix problem with non-working unbind of a rune key.
E.g. after the following commands:

bind "n" "FindNext"
unbind "n"

Observed result: "n" key still triggers FindNext action
Expected result: "n" key inserts "n" rune
2020-05-14 21:50:28 -04:00
Zachary Yedidia
30395b1f67 Remove outdated c++ highlighter
Fixes #1652
2020-05-14 21:37:19 -04:00
Zachary Yedidia
ddf70953fe Support snake case autocompletion
Fixes #1655
2020-05-14 21:34:17 -04:00
Zachary Yedidia
55e97596d3 Fix movelinesup when selection is not complete 2020-05-07 19:39:17 -04:00
Zachary Yedidia
66dc48ce9b Improve readme 2020-05-04 22:21:46 -04:00
Zachary Yedidia
c490a94700 Update makefile 2020-05-04 10:34:16 -04:00
Philipp Emanuel Weidmann
d038d3040f Add more sophisticated buffer benchmark system 2020-03-01 13:20:10 +05:30
37 changed files with 667 additions and 334 deletions

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@ todo.txt
test.txt
log.txt
*.old
benchmark_results*
tools/build-version
tools/build-date
tools/info-plist

View File

@@ -8,8 +8,8 @@ DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \
GOARCH=$(shell go env GOHOSTARCH))
GOBIN ?= $(shell go env GOPATH)/bin
GOVARS = -X github.com/zyedidia/micro/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/internal/util.CompileDate=$(DATE)'
DEBUGVAR = -X github.com/zyedidia/micro/internal/util.Debug=ON
GOVARS = -X github.com/zyedidia/micro/v2/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/v2/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/v2/internal/util.CompileDate=$(DATE)'
DEBUGVAR = -X github.com/zyedidia/micro/v2/internal/util.Debug=ON
VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/'
# Builds micro after checking dependencies but without updating the runtime
@@ -65,7 +65,21 @@ test:
go test ./internal/...
bench:
go test -bench=. ./internal/...
for i in 1 2 3; do \
go test -bench=. ./internal/...; \
done > benchmark_results
benchstat benchmark_results
bench-baseline:
for i in 1 2 3; do \
go test -bench=. ./internal/...; \
done > benchmark_results_baseline
bench-compare:
for i in 1 2 3; do \
go test -bench=. ./internal/...; \
done > benchmark_results
benchstat benchmark_results_baseline benchmark_results
clean:
rm -f micro

View File

@@ -107,6 +107,10 @@ You can install micro using Homebrew on Mac:
brew install micro
```
**Note for Mac:** All micro keybindings use the control or alt (option) key, not the command
key. By default, macOS terminals do not forward alt key events. To fix this, please see
the section on [macOS terminals](https://github.com/zyedidia/micro#macos-terminal) further below.
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
```
@@ -120,6 +124,10 @@ via `apt`:
sudo apt install micro
```
**Note for Linux:** for interfacing with the local system clipboard, `xclip` or `xsel`
must be installed. Please see the section on [Linux clipboard support](https://github.com/zyedidia/micro#linux-clipboard-support)
further below.
Micro is also available through other package managers on Linux such as AUR, Nix, and package managers
for other operating systems:

2
go.mod
View File

@@ -17,7 +17,7 @@ require (
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
github.com/zyedidia/pty v2.0.0+incompatible // indirect
github.com/zyedidia/tcell v1.4.4
github.com/zyedidia/tcell v1.4.5
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
golang.org/x/text v0.3.2
gopkg.in/sourcemap.v1 v1.0.5 // indirect

2
go.sum
View File

@@ -56,6 +56,8 @@ github.com/zyedidia/pty v2.0.0+incompatible h1:Ou5vXL6tvjst+RV8sUFISbuKDnUJPhnpy
github.com/zyedidia/pty v2.0.0+incompatible/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
github.com/zyedidia/tcell v1.4.4 h1:o34LXujNuSueuyTy+5eoQW+rQr8g0UbY8k1NczZyskQ=
github.com/zyedidia/tcell v1.4.4/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
github.com/zyedidia/tcell v1.4.5 h1:JFmOiWLxr3Fsk2vjRL3n8oRUoJeyrazGhkhZqW31kEY=
github.com/zyedidia/tcell v1.4.5/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@@ -5,7 +5,6 @@ import (
"runtime"
"strings"
"time"
"unicode/utf8"
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/clipboard"
@@ -175,7 +174,7 @@ func (h *BufPane) CursorRight() bool {
if tabstospaces && tabmovement {
tabsize := int(h.Buf.Settings["tabsize"].(float64))
line := h.Buf.LineBytes(h.Cursor.Y)
if h.Cursor.X+tabsize < utf8.RuneCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
if h.Cursor.X+tabsize < util.CharacterCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
for i := 0; i < tabsize; i++ {
h.Cursor.Right()
}
@@ -486,7 +485,7 @@ func (h *BufPane) InsertNewline() bool {
// Remove the whitespaces if keepautoindent setting is off
if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
line := h.Buf.LineBytes(h.Cursor.Y - 1)
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: utf8.RuneCount(line), Y: h.Cursor.Y - 1})
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
}
}
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
@@ -511,7 +510,7 @@ func (h *BufPane) Backspace() bool {
// tab (tabSize number of spaces)
lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
tabSize := int(h.Buf.Settings["tabsize"].(float64))
if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 {
loc := h.Cursor.Loc
h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
} else {
@@ -802,10 +801,23 @@ func (h *BufPane) saveBufToFile(filename string, action string, callback func())
// Find opens a prompt and searches forward for the input
func (h *BufPane) Find() bool {
return h.find(true)
}
// FindLiteral is the same as Find() but does not support regular expressions
func (h *BufPane) FindLiteral() bool {
return h.find(false)
}
func (h *BufPane) find(useRegex bool) bool {
h.searchOrig = h.Cursor.Loc
InfoBar.Prompt("Find (regex): ", "", "Find", func(resp string) {
prompt := "Find: "
if useRegex {
prompt = "Find (regex): "
}
InfoBar.Prompt(prompt, "", "Find", func(resp string) {
// Event callback
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
if found {
h.Cursor.SetSelectionStart(match[0])
h.Cursor.SetSelectionEnd(match[1])
@@ -820,7 +832,7 @@ func (h *BufPane) Find() bool {
}, func(resp string, canceled bool) {
// Finished callback
if !canceled {
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
if err != nil {
InfoBar.Error(err)
}
@@ -831,6 +843,7 @@ func (h *BufPane) Find() bool {
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
h.lastSearch = resp
h.lastSearchRegex = useRegex
} else {
h.Cursor.ResetSelection()
InfoBar.Message("No matches found")
@@ -854,7 +867,7 @@ func (h *BufPane) FindNext() bool {
if h.Cursor.HasSelection() {
searchLoc = h.Cursor.CurSelection[1]
}
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, true)
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex)
if err != nil {
InfoBar.Error(err)
}
@@ -881,7 +894,7 @@ func (h *BufPane) FindPrevious() bool {
if h.Cursor.HasSelection() {
searchLoc = h.Cursor.CurSelection[0]
}
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, true)
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex)
if err != nil {
InfoBar.Error(err)
}
@@ -1027,15 +1040,26 @@ func (h *BufPane) MoveLinesUp() bool {
}
start := h.Cursor.CurSelection[0].Y
end := h.Cursor.CurSelection[1].Y
sel := 1
if start > end {
end, start = start, end
sel = 0
}
compensate := false
if h.Cursor.CurSelection[sel].X != 0 {
end++
} else {
compensate = true
}
h.Buf.MoveLinesUp(
start,
end,
)
h.Cursor.CurSelection[1].Y -= 1
if compensate {
h.Cursor.CurSelection[sel].Y -= 1
}
} else {
if h.Cursor.Loc.Y == 0 {
InfoBar.Message("Cannot move further up")
@@ -1060,8 +1084,14 @@ func (h *BufPane) MoveLinesDown() bool {
}
start := h.Cursor.CurSelection[0].Y
end := h.Cursor.CurSelection[1].Y
sel := 1
if start > end {
end, start = start, end
sel = 0
}
if h.Cursor.CurSelection[sel].X != 0 {
end++
}
h.Buf.MoveLinesDown(

View File

@@ -249,6 +249,7 @@ func UnbindKey(k string) error {
if a, ok := defaults[k]; ok {
BindKey(k, a)
} else if _, ok := config.Bindings[k]; ok {
BufUnmap(key)
delete(config.Bindings, k)
}

View File

@@ -139,6 +139,17 @@ func BufMapMouse(k MouseEvent, action string) {
}
}
// BufUnmap unmaps a key or mouse event from any action
func BufUnmap(k Event) {
delete(BufKeyBindings, k)
delete(BufKeyStrings, k)
switch e := k.(type) {
case MouseEvent:
delete(BufMouseBindings, e)
}
}
// The BufPane connects the buffer and the window
// It provides a cursor (or multiple) and defines a set of actions
// that can be taken on the buffer
@@ -179,7 +190,8 @@ type BufPane struct {
tripleClick bool
// Last search stores the last successful search for FindNext and FindPrev
lastSearch string
lastSearch string
lastSearchRegex bool
// Should the current multiple cursor selection search based on word or
// based on selection (false for selection, true for word)
multiWord bool
@@ -552,6 +564,7 @@ var BufKeyActions = map[string]BufKeyAction{
"SaveAll": (*BufPane).SaveAll,
"SaveAs": (*BufPane).SaveAs,
"Find": (*BufPane).Find,
"FindLiteral": (*BufPane).FindLiteral,
"FindNext": (*BufPane).FindNext,
"FindPrevious": (*BufPane).FindPrevious,
"Center": (*BufPane).Center,

View File

@@ -10,7 +10,6 @@ import (
"regexp"
"strconv"
"strings"
"unicode/utf8"
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/v2/internal/buffer"
@@ -56,6 +55,7 @@ func InitCommands() {
"cd": {(*BufPane).CdCmd, buffer.FileComplete},
"pwd": {(*BufPane).PwdCmd, nil},
"open": {(*BufPane).OpenCmd, buffer.FileComplete},
"tabmove": {(*BufPane).TabMoveCmd, nil},
"tabswitch": {(*BufPane).TabSwitchCmd, nil},
"term": {(*BufPane).TermCmd, nil},
"memusage": {(*BufPane).MemUsageCmd, nil},
@@ -155,6 +155,56 @@ func (h *BufPane) TextFilterCmd(args []string) {
h.Buf.Insert(h.Cursor.Loc, bout.String())
}
// TabMoveCmd moves the current tab to a given index (starts at 1). The
// displaced tabs are moved up.
func (h *BufPane) TabMoveCmd(args []string) {
if len(args) <= 0 {
InfoBar.Error("Not enough arguments: provide an index, starting at 1")
return
}
if len(args[0]) <= 0 {
InfoBar.Error("Invalid argument: empty string")
return
}
num, err := strconv.Atoi(args[0])
if err != nil {
InfoBar.Error("Invalid argument: ", err)
return
}
// Preserve sign for relative move, if one exists
var shiftDirection byte
if strings.Contains("-+", string([]byte{args[0][0]})) {
shiftDirection = args[0][0]
}
// Relative positions -> absolute positions
idxFrom := Tabs.Active()
idxTo := 0
offset := util.Abs(num)
if shiftDirection == '-' {
idxTo = idxFrom - offset
} else if shiftDirection == '+' {
idxTo = idxFrom + offset
} else {
idxTo = offset - 1
}
// Restrain position to within the valid range
idxTo = util.Clamp(idxTo, 0, len(Tabs.List)-1)
activeTab := Tabs.List[idxFrom]
Tabs.RemoveTab(activeTab.ID())
Tabs.List = append(Tabs.List, nil)
copy(Tabs.List[idxTo+1:], Tabs.List[idxTo:])
Tabs.List[idxTo] = activeTab
Tabs.UpdateNames()
Tabs.SetActive(idxTo)
// InfoBar.Message(fmt.Sprintf("Moved tab from slot %d to %d", idxFrom+1, idxTo+1))
}
// TabSwitchCmd switches to a given tab either by name or by number
func (h *BufPane) TabSwitchCmd(args []string) {
if len(args) > 0 {
@@ -652,7 +702,7 @@ func (h *BufPane) GotoCmd(args []string) {
return
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
col = util.Clamp(col-1, 0, utf8.RuneCount(h.Buf.LineBytes(line)))
col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))
h.Cursor.GotoLoc(buffer.Loc{col, line})
} else {
line, err := strconv.Atoi(args[0])
@@ -776,7 +826,7 @@ func (h *BufPane) ReplaceCmd(args []string) {
nreplaced++
} else if !canceled && !yes {
searchLoc = locs[0]
searchLoc.X += utf8.RuneCount(replace)
searchLoc.X += util.CharacterCount(replace)
} else if canceled {
h.Cursor.ResetSelection()
h.Buf.RelocateCursors()

View File

@@ -6,7 +6,6 @@ import (
"os"
"sort"
"strings"
"unicode/utf8"
"github.com/zyedidia/micro/v2/internal/util"
)
@@ -54,7 +53,7 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
start := c.Loc
end := c.Loc
if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
start = end.Move(-utf8.RuneCountInString(b.Completions[prevSuggestion]), b)
start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b)
} else {
// end = start.Move(1, b)
}
@@ -82,7 +81,7 @@ func GetWord(b *Buffer) ([]byte, int) {
args := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
input := args[len(args)-1]
return input, c.X - utf8.RuneCount(input)
return input, c.X - util.CharacterCount(input)
}
// GetArg gets the most recent word (separated by ' ' only)
@@ -98,7 +97,7 @@ func GetArg(b *Buffer) (string, int) {
if i == len(args)-1 {
break
}
argstart += utf8.RuneCount(a) + 1
argstart += util.CharacterCount(a) + 1
}
return input, argstart
@@ -162,7 +161,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
return []string{}, []string{}
}
inputLen := utf8.RuneCount(input)
inputLen := util.CharacterCount(input)
suggestionsSet := make(map[string]struct{})
@@ -171,7 +170,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
l := b.LineBytes(i)
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
for _, w := range words {
if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
strw := string(w)
if _, ok := suggestionsSet[strw]; !ok {
suggestionsSet[strw] = struct{}{}
@@ -184,7 +183,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
l := b.LineBytes(i)
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
for _, w := range words {
if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
strw := string(w)
if _, ok := suggestionsSet[strw]; !ok {
suggestionsSet[strw] = struct{}{}

View File

@@ -15,7 +15,6 @@ import (
"strings"
"sync"
"time"
"unicode/utf8"
luar "layeh.com/gopher-luar"
@@ -152,14 +151,14 @@ func (b *SharedBuffer) MarkModified(start, end int) {
return
}
start = util.Clamp(start, 0, len(b.lines))
end = util.Clamp(end, 0, len(b.lines))
start = util.Clamp(start, 0, len(b.lines)-1)
end = util.Clamp(end, 0, len(b.lines)-1)
l := -1
for i := start; i <= end; i++ {
l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
}
b.Highlighter.HighlightMatches(b, start, l+1)
b.Highlighter.HighlightMatches(b, start, l)
}
// DisableReload disables future reloads of this sharedbuffer
@@ -474,7 +473,7 @@ func (b *Buffer) RuneAt(loc Loc) rune {
if len(line) > 0 {
i := 0
for len(line) > 0 {
r, size := utf8.DecodeRune(line)
r, _, size := util.DecodeCharacter(line)
line = line[size:]
i++
@@ -809,7 +808,7 @@ func (b *Buffer) MoveLinesUp(start int, end int) {
if end == len(b.lines) {
b.Insert(
Loc{
utf8.RuneCount(b.lines[end-1].data),
util.CharacterCount(b.lines[end-1].data),
end - 1,
},
"\n"+l,

View File

@@ -1825,65 +1825,3 @@ func TestMultiInsert9(t *testing.T) {
},
)
}
func BenchmarkBuffer(b *testing.B) {
for i := 0; i < b.N; i++ {
TestAuto1(nil)
TestAuto2(nil)
TestAuto3(nil)
TestAuto4(nil)
TestBug19872UndoIsFunky(nil)
TestBug19872UndoIsFunky_2(nil)
TestInsertEmptyText(nil)
TestLastOpIsNoOp(nil)
TestInsertTextWithoutNewline1(nil)
TestInsertTextWithoutNewline2(nil)
TestInsertOneNewline(nil)
TestInsertTextWithOneNewline(nil)
TestInsertTextWithTwoNewlines(nil)
TestInsertTextWithManyNewlines(nil)
TestInsertMultipleNewlines(nil)
TestDeleteEmptyText(nil)
TestDeleteTextFromOneLine(nil)
TestDeleteTextFromOneLine2(nil)
TestDeleteAllTextFromALine(nil)
TestDeleteTextFromTwoLines(nil)
TestDeleteTextFromManyLines(nil)
TestDeleteEverything(nil)
TestTwoUnrelatedEdits(nil)
TestTwoEditsOnOneLine(nil)
TestManyEdits(nil)
TestManyEditsReversed(nil)
TestReplacingNewlines1(nil)
TestReplacingNewlines2(nil)
TestAdvanced1(nil)
TestAdvancedSimplified(nil)
TestIssue144(nil)
TestIssue2586ReplacingSelectedEndOfLineWithNewlineLocksUpTheDocument(nil)
TestIssue3980(nil)
TestTouchingEditsTwoInsertsAtTheSamePosition(nil)
TestTouchingEditsInsertAndReplaceTouching(nil)
TestTouchingEditsTwoTouchingReplaces(nil)
TestTouchingEditsTwoTouchingDeletes(nil)
TestTouchingEditsInsertAndReplace(nil)
TestTouchingEditsReplaceAndInsert(nil)
TestSingleDelete1(nil)
TestSingleDelete2(nil)
TestSingleDelete3(nil)
TestSingleDelete4(nil)
TestSingleDelete5(nil)
TestMultiDelete6(nil)
TestMultiDelete7(nil)
TestMultiDelete8(nil)
TestMultiDelete9(nil)
TestSingleInsert1(nil)
TestSingleInsert2(nil)
TestSingleInsert3(nil)
TestSingleInsert4(nil)
TestSingleInsert5(nil)
TestMultiInsert6(nil)
TestMultiInsert7(nil)
TestMultiInsert8(nil)
TestMultiInsert9(nil)
}
}

View File

@@ -1,13 +1,15 @@
package buffer
import (
"math/rand"
"strings"
"testing"
testifyAssert "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/assert"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/util"
)
type operation struct {
@@ -16,34 +18,15 @@ type operation struct {
text []string
}
type asserter interface {
Equal(interface{}, interface{}, ...interface{}) bool
NotEqual(interface{}, interface{}, ...interface{}) bool
}
type noOpAsserter struct {
}
func (a *noOpAsserter) Equal(interface{}, interface{}, ...interface{}) bool {
return true
}
func (a *noOpAsserter) NotEqual(interface{}, interface{}, ...interface{}) bool {
return true
}
func init() {
ulua.L = lua.NewState()
config.InitGlobalSettings()
config.GlobalSettings["backup"] = false
config.GlobalSettings["fastdirty"] = true
}
func check(t *testing.T, before []string, operations []operation, after []string) {
var assert asserter
if t == nil {
// Benchmark mode; don't perform assertions
assert = &noOpAsserter{}
} else {
assert = testifyAssert.New(t)
}
assert := assert.New(t)
b := NewBufferFromString(strings.Join(before, "\n"), "", BTDefault)
@@ -111,3 +94,229 @@ func check(t *testing.T, before []string, operations []operation, after []string
b.Close()
}
const maxLineLength = 200
var alphabet = []rune(" abcdeäم📚")
func randomString(length int) string {
runes := make([]rune, length)
for i := range runes {
runes[i] = alphabet[rand.Intn(len(alphabet))]
}
return string(runes)
}
func randomText(nLines int) string {
lines := make([]string, nLines)
for i := range lines {
lines[i] = randomString(rand.Intn(maxLineLength + 1))
}
return strings.Join(lines, "\n")
}
func benchCreateAndClose(testingB *testing.B, nLines int) {
rand.Seed(int64(nLines))
text := randomText(nLines)
testingB.ResetTimer()
for i := 0; i < testingB.N; i++ {
b := NewBufferFromString(text, "", BTDefault)
b.Close()
}
}
func benchRead(testingB *testing.B, nLines int) {
rand.Seed(int64(nLines))
b := NewBufferFromString(randomText(nLines), "", BTDefault)
testingB.ResetTimer()
for i := 0; i < testingB.N; i++ {
b.Bytes()
for j := 0; j < b.LinesNum(); j++ {
b.Line(j)
b.LineBytes(j)
}
}
testingB.StopTimer()
b.Close()
}
func benchEdit(testingB *testing.B, nLines, nCursors int) {
rand.Seed(int64(nLines + nCursors))
b := NewBufferFromString(randomText(nLines), "", BTDefault)
regionSize := nLines / nCursors
operations := make([]operation, nCursors)
for i := range operations {
startLine := (i * regionSize) + rand.Intn(regionSize-5)
startColumn := rand.Intn(util.CharacterCountInString(b.Line(startLine)) + 1)
endLine := startLine + 1 + rand.Intn(5)
endColumn := rand.Intn(util.CharacterCountInString(b.Line(endLine)) + 1)
operations[i] = operation{
start: Loc{startColumn, startLine},
end: Loc{endColumn, endLine},
text: []string{randomText(2 + rand.Intn(4))},
}
}
testingB.ResetTimer()
for i := 0; i < testingB.N; i++ {
b.SetCursors([]*Cursor{})
var cursors []*Cursor
for _, op := range operations {
cursor := NewCursor(b, op.start)
cursor.SetSelectionStart(op.start)
cursor.SetSelectionEnd(op.end)
b.AddCursor(cursor)
cursors = append(cursors, cursor)
}
for j, op := range operations {
cursor := cursors[j]
b.SetCurCursor(cursor.Num)
cursor.DeleteSelection()
b.Insert(cursor.Loc, op.text[0])
}
for b.UndoStack.Peek() != nil {
b.UndoOneEvent()
}
}
testingB.StopTimer()
b.Close()
}
func BenchmarkCreateAndClose10Lines(b *testing.B) {
benchCreateAndClose(b, 10)
}
func BenchmarkCreateAndClose100Lines(b *testing.B) {
benchCreateAndClose(b, 100)
}
func BenchmarkCreateAndClose1000Lines(b *testing.B) {
benchCreateAndClose(b, 1000)
}
func BenchmarkCreateAndClose10000Lines(b *testing.B) {
benchCreateAndClose(b, 10000)
}
func BenchmarkCreateAndClose100000Lines(b *testing.B) {
benchCreateAndClose(b, 100000)
}
func BenchmarkCreateAndClose1000000Lines(b *testing.B) {
benchCreateAndClose(b, 1000000)
}
func BenchmarkRead10Lines(b *testing.B) {
benchRead(b, 10)
}
func BenchmarkRead100Lines(b *testing.B) {
benchRead(b, 100)
}
func BenchmarkRead1000Lines(b *testing.B) {
benchRead(b, 1000)
}
func BenchmarkRead10000Lines(b *testing.B) {
benchRead(b, 10000)
}
func BenchmarkRead100000Lines(b *testing.B) {
benchRead(b, 100000)
}
func BenchmarkRead1000000Lines(b *testing.B) {
benchRead(b, 1000000)
}
func BenchmarkEdit10Lines1Cursor(b *testing.B) {
benchEdit(b, 10, 1)
}
func BenchmarkEdit100Lines1Cursor(b *testing.B) {
benchEdit(b, 100, 1)
}
func BenchmarkEdit100Lines10Cursors(b *testing.B) {
benchEdit(b, 100, 10)
}
func BenchmarkEdit1000Lines1Cursor(b *testing.B) {
benchEdit(b, 1000, 1)
}
func BenchmarkEdit1000Lines10Cursors(b *testing.B) {
benchEdit(b, 1000, 10)
}
func BenchmarkEdit1000Lines100Cursors(b *testing.B) {
benchEdit(b, 1000, 100)
}
func BenchmarkEdit10000Lines1Cursor(b *testing.B) {
benchEdit(b, 10000, 1)
}
func BenchmarkEdit10000Lines10Cursors(b *testing.B) {
benchEdit(b, 10000, 10)
}
func BenchmarkEdit10000Lines100Cursors(b *testing.B) {
benchEdit(b, 10000, 100)
}
func BenchmarkEdit10000Lines1000Cursors(b *testing.B) {
benchEdit(b, 10000, 1000)
}
func BenchmarkEdit100000Lines1Cursor(b *testing.B) {
benchEdit(b, 100000, 1)
}
func BenchmarkEdit100000Lines10Cursors(b *testing.B) {
benchEdit(b, 100000, 10)
}
func BenchmarkEdit100000Lines100Cursors(b *testing.B) {
benchEdit(b, 100000, 100)
}
func BenchmarkEdit100000Lines1000Cursors(b *testing.B) {
benchEdit(b, 100000, 1000)
}
func BenchmarkEdit1000000Lines1Cursor(b *testing.B) {
benchEdit(b, 1000000, 1)
}
func BenchmarkEdit1000000Lines10Cursors(b *testing.B) {
benchEdit(b, 1000000, 10)
}
func BenchmarkEdit1000000Lines100Cursors(b *testing.B) {
benchEdit(b, 1000000, 100)
}
func BenchmarkEdit1000000Lines1000Cursors(b *testing.B) {
benchEdit(b, 1000000, 1000)
}

View File

@@ -1,15 +1,13 @@
package buffer
import (
"unicode/utf8"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/v2/internal/util"
)
// InBounds returns whether the given location is a valid character position in the given buffer
func InBounds(pos Loc, buf *Buffer) bool {
if pos.Y < 0 || pos.Y >= len(buf.lines) || pos.X < 0 || pos.X > utf8.RuneCount(buf.LineBytes(pos.Y)) {
if pos.Y < 0 || pos.Y >= len(buf.lines) || pos.X < 0 || pos.X > util.CharacterCount(buf.LineBytes(pos.Y)) {
return false
}
@@ -76,8 +74,8 @@ func (c *Cursor) GetVisualX() int {
bytes := c.buf.LineBytes(c.Y)
tabsize := int(c.buf.Settings["tabsize"].(float64))
if c.X > utf8.RuneCount(bytes) {
c.X = utf8.RuneCount(bytes) - 1
if c.X > util.CharacterCount(bytes) {
c.X = util.CharacterCount(bytes) - 1
}
return util.StringWidth(bytes, c.X, tabsize)
@@ -102,7 +100,7 @@ func (c *Cursor) Start() {
func (c *Cursor) StartOfText() {
c.Start()
for util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
break
}
c.Right()
@@ -114,7 +112,7 @@ func (c *Cursor) StartOfText() {
func (c *Cursor) IsStartOfText() bool {
x := 0
for util.IsWhitespace(c.RuneUnder(x)) {
if x == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
if x == util.CharacterCount(c.buf.LineBytes(c.Y)) {
break
}
x++
@@ -124,7 +122,7 @@ func (c *Cursor) IsStartOfText() bool {
// End moves the cursor to the end of the line it is on
func (c *Cursor) End() {
c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
c.LastVisualX = c.GetVisualX()
}
@@ -242,8 +240,8 @@ func (c *Cursor) UpN(amount int) {
bytes := c.buf.LineBytes(proposedY)
c.X = c.GetCharPosInLine(bytes, c.LastVisualX)
if c.X > utf8.RuneCount(bytes) || (amount < 0 && proposedY == c.Y) {
c.X = utf8.RuneCount(bytes)
if c.X > util.CharacterCount(bytes) || (amount < 0 && proposedY == c.Y) {
c.X = util.CharacterCount(bytes)
}
c.Y = proposedY
@@ -285,7 +283,7 @@ func (c *Cursor) Right() {
if c.Loc == c.buf.End() {
return
}
if c.X < utf8.RuneCount(c.buf.LineBytes(c.Y)) {
if c.X < util.CharacterCount(c.buf.LineBytes(c.Y)) {
c.X++
} else {
c.Down()
@@ -306,8 +304,8 @@ func (c *Cursor) Relocate() {
if c.X < 0 {
c.X = 0
} else if c.X > utf8.RuneCount(c.buf.LineBytes(c.Y)) {
c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
} else if c.X > util.CharacterCount(c.buf.LineBytes(c.Y)) {
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
}
}
@@ -333,7 +331,7 @@ func (c *Cursor) SelectWord() {
c.SetSelectionStart(Loc{backward, c.Y})
c.OrigSelection[0] = c.CurSelection[0]
lineLen := utf8.RuneCount(c.buf.LineBytes(c.Y)) - 1
lineLen := util.CharacterCount(c.buf.LineBytes(c.Y)) - 1
for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {
forward++
}
@@ -365,7 +363,7 @@ func (c *Cursor) AddWordToSelection() {
if c.Loc.GreaterThan(c.OrigSelection[1]) {
forward := c.X
lineLen := utf8.RuneCount(c.buf.LineBytes(c.Y)) - 1
lineLen := util.CharacterCount(c.buf.LineBytes(c.Y)) - 1
for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {
forward++
}
@@ -392,7 +390,7 @@ func (c *Cursor) SelectTo(loc Loc) {
// WordRight moves the cursor one word to the right
func (c *Cursor) WordRight() {
for util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
c.Right()
return
}
@@ -400,7 +398,7 @@ func (c *Cursor) WordRight() {
}
c.Right()
for util.IsWordChar(c.RuneUnder(c.X)) {
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
return
}
c.Right()
@@ -429,14 +427,14 @@ func (c *Cursor) WordLeft() {
// RuneUnder returns the rune under the given x position
func (c *Cursor) RuneUnder(x int) rune {
line := c.buf.LineBytes(c.Y)
if len(line) == 0 || x >= utf8.RuneCount(line) {
if len(line) == 0 || x >= util.CharacterCount(line) {
return '\n'
} else if x < 0 {
x = 0
}
i := 0
for len(line) > 0 {
r, size := utf8.DecodeRune(line)
r, _, size := util.DecodeCharacter(line)
line = line[size:]
if i == x {

View File

@@ -3,12 +3,12 @@ package buffer
import (
"bytes"
"time"
"unicode/utf8"
dmp "github.com/sergi/go-diff/diffmatchpatch"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
luar "layeh.com/gopher-luar"
)
@@ -62,10 +62,10 @@ func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
var textX int
if t.EventType == TextEventInsert {
linecount := eh.buf.LinesNum() - oldl
textcount := utf8.RuneCount(text)
textcount := util.CharacterCount(text)
lastnl = bytes.LastIndex(text, []byte{'\n'})
if lastnl >= 0 {
endX = utf8.RuneCount(text[lastnl+1:])
endX = util.CharacterCount(text[lastnl+1:])
textX = endX
} else {
endX = start.X + textcount
@@ -123,7 +123,7 @@ func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
t.Deltas[i].Text = buf.remove(d.Start, d.End)
buf.insert(d.Start, d.Text)
t.Deltas[i].Start = d.Start
t.Deltas[i].End = Loc{d.Start.X + utf8.RuneCount(d.Text), d.Start.Y}
t.Deltas[i].End = Loc{d.Start.X + util.CharacterCount(d.Text), d.Start.Y}
}
for i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {
t.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]
@@ -166,12 +166,12 @@ func (eh *EventHandler) ApplyDiff(new string) {
loc := eh.buf.Start()
for _, d := range diff {
if d.Type == dmp.DiffDelete {
eh.Remove(loc, loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray))
eh.Remove(loc, loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray))
} else {
if d.Type == dmp.DiffInsert {
eh.Insert(loc, d.Text)
}
loc = loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray)
loc = loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray)
}
}
}

View File

@@ -5,8 +5,8 @@ import (
"bytes"
"io"
"sync"
"unicode/utf8"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/pkg/highlight"
)
@@ -19,7 +19,7 @@ func runeToByteIndex(n int, txt []byte) int {
count := 0
i := 0
for len(txt) > 0 {
_, size := utf8.DecodeRune(txt)
_, _, size := util.DecodeCharacter(txt)
txt = txt[size:]
count += size
@@ -299,7 +299,7 @@ func (la *LineArray) Start() Loc {
// End returns the location of the last character in the buffer
func (la *LineArray) End() Loc {
numlines := len(la.lines)
return Loc{utf8.RuneCount(la.lines[numlines-1].data), numlines - 1}
return Loc{util.CharacterCount(la.lines[numlines-1].data), numlines - 1}
}
// LineBytes returns line n as an array of bytes

View File

@@ -1,8 +1,6 @@
package buffer
import (
"unicode/utf8"
"github.com/zyedidia/micro/v2/internal/util"
)
@@ -68,9 +66,9 @@ func DiffLA(a, b Loc, buf *LineArray) int {
loc := 0
for i := a.Y + 1; i < b.Y; i++ {
// + 1 for the newline
loc += utf8.RuneCount(buf.LineBytes(i)) + 1
loc += util.CharacterCount(buf.LineBytes(i)) + 1
}
loc += utf8.RuneCount(buf.LineBytes(a.Y)) - a.X + b.X + 1
loc += util.CharacterCount(buf.LineBytes(a.Y)) - a.X + b.X + 1
return loc
}
@@ -80,7 +78,7 @@ func (l Loc) right(buf *LineArray) Loc {
return Loc{l.X + 1, l.Y}
}
var res Loc
if l.X < utf8.RuneCount(buf.LineBytes(l.Y)) {
if l.X < util.CharacterCount(buf.LineBytes(l.Y)) {
res = Loc{l.X + 1, l.Y}
} else {
res = Loc{0, l.Y + 1}
@@ -97,7 +95,7 @@ func (l Loc) left(buf *LineArray) Loc {
if l.X > 0 {
res = Loc{l.X - 1, l.Y}
} else {
res = Loc{utf8.RuneCount(buf.LineBytes(l.Y - 1)), l.Y - 1}
res = Loc{util.CharacterCount(buf.LineBytes(l.Y - 1)), l.Y - 1}
}
return res
}

View File

@@ -11,7 +11,6 @@ import (
"path/filepath"
"runtime"
"unicode"
"unicode/utf8"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
@@ -100,9 +99,9 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
b.UpdateRules()
if b.Settings["rmtrailingws"].(bool) {
for i, l := range b.lines {
leftover := utf8.RuneCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
linelen := utf8.RuneCount(l.data)
linelen := util.CharacterCount(l.data)
b.Remove(Loc{leftover, i}, Loc{linelen, i})
}

View File

@@ -2,7 +2,6 @@ package buffer
import (
"regexp"
"unicode/utf8"
"github.com/zyedidia/micro/v2/internal/util"
)
@@ -20,19 +19,19 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
charpos := 0
if i == start.Y && start.Y == end.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == start.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == end.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
}
@@ -61,19 +60,19 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
charpos := 0
if i == start.Y && start.Y == end.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == start.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == end.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
}
@@ -163,12 +162,12 @@ func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []b
result = search.Expand(result, replace, in, submatches)
}
found++
netrunes += utf8.RuneCount(in) - utf8.RuneCount(result)
netrunes += util.CharacterCount(in) - util.CharacterCount(result)
return result
})
from := Loc{charpos, i}
to := Loc{charpos + utf8.RuneCount(l), i}
to := Loc{charpos + util.CharacterCount(l), i}
deltas = append(deltas, Delta{newText, from, to})
}

View File

@@ -151,6 +151,9 @@ func StringToStyle(str string) tcell.Style {
if strings.Contains(str, "bold") {
style = style.Bold(true)
}
if strings.Contains(str, "italic") {
style = style.Italic(true)
}
if strings.Contains(str, "reverse") {
style = style.Reverse(true)
}

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,6 @@ package display
import (
"strconv"
"unicode/utf8"
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/v2/internal/buffer"
@@ -73,9 +72,9 @@ func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style)
curStyle := config.DefStyle
var s *tcell.Style
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := util.DecodeCharacter(b)
curStyle, found := w.getStyle(curStyle, bloc, r)
curStyle, found := w.getStyle(curStyle, bloc)
if found {
s = &curStyle
}
@@ -237,7 +236,7 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
return bloc
}
r, size := utf8.DecodeRune(line)
r, _, size := util.DecodeCharacter(line)
draw()
width := 0
@@ -360,7 +359,7 @@ func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxL
// getStyle returns the highlight style for the given character position
// If there is no change to the current highlight style it just returns that
func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.Style, bool) {
func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc) (tcell.Style, bool) {
if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok {
s := config.GetColor(group.String())
return s, true
@@ -510,7 +509,7 @@ func (w *BufWindow) displayBuffer() {
}
bloc.X = bslice
draw := func(r rune, style tcell.Style, showcursor bool) {
draw := func(r rune, combc []rune, style tcell.Style, showcursor bool) {
if nColsBeforeStart <= 0 {
for _, c := range cursors {
if c.HasSelection() &&
@@ -568,7 +567,7 @@ func (w *BufWindow) displayBuffer() {
}
}
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)
if showcursor {
for _, c := range cursors {
@@ -584,10 +583,11 @@ func (w *BufWindow) displayBuffer() {
totalwidth := w.StartCol - nColsBeforeStart
for len(line) > 0 {
r, size := utf8.DecodeRune(line)
curStyle, _ = w.getStyle(curStyle, bloc, r)
r, combc, size := util.DecodeCharacter(line)
draw(r, curStyle, true)
curStyle, _ = w.getStyle(curStyle, bloc)
draw(r, combc, curStyle, true)
width := 0
@@ -604,7 +604,7 @@ func (w *BufWindow) displayBuffer() {
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if width > 1 {
for i := 1; i < width; i++ {
draw(char, curStyle, false)
draw(char, nil, curStyle, false)
}
}
bloc.X++
@@ -659,7 +659,7 @@ func (w *BufWindow) displayBuffer() {
}
if vloc.X != bufWidth {
draw(' ', curStyle, true)
draw(' ', nil, curStyle, true)
}
bloc.X = w.StartCol

View File

@@ -1,8 +1,6 @@
package display
import (
"unicode/utf8"
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
@@ -70,7 +68,7 @@ func (i *InfoWindow) IsActive() bool { return true }
func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
c := i.Buffer.GetActiveCursor()
l := i.Buffer.LineBytes(0)
n := utf8.RuneCountInString(i.Msg)
n := util.CharacterCountInString(i.Msg)
return buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 0}
}
@@ -86,13 +84,13 @@ func (i *InfoWindow) displayBuffer() {
activeC := b.GetActiveCursor()
blocX := 0
vlocX := utf8.RuneCountInString(i.Msg)
vlocX := util.CharacterCountInString(i.Msg)
tabsize := 4
line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, blocX, tabsize)
blocX = bslice
draw := func(r rune, style tcell.Style) {
draw := func(r rune, combc []rune, style tcell.Style) {
if nColsBeforeStart <= 0 {
bloc := buffer.Loc{X: blocX, Y: 0}
if activeC.HasSelection() &&
@@ -112,8 +110,9 @@ func (i *InfoWindow) displayBuffer() {
c := r
if j > 0 {
c = ' '
combc = nil
}
screen.SetContent(vlocX, i.Y, c, nil, style)
screen.SetContent(vlocX, i.Y, c, combc, style)
}
vlocX++
}
@@ -124,9 +123,9 @@ func (i *InfoWindow) displayBuffer() {
for len(line) > 0 {
curVX := vlocX
curBX := blocX
r, size := utf8.DecodeRune(line)
r, combc, size := util.DecodeCharacter(line)
draw(r, i.defStyle())
draw(r, combc, i.defStyle())
width := 0
@@ -146,7 +145,7 @@ func (i *InfoWindow) displayBuffer() {
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if width > 1 {
for j := 1; j < width; j++ {
draw(char, i.defStyle())
draw(char, nil, i.defStyle())
}
}
if activeC.X == curBX {
@@ -191,7 +190,7 @@ func (i *InfoWindow) scrollToSuggestion() {
s := i.totalSize()
for j, n := range i.Suggestions {
c := utf8.RuneCountInString(n)
c := util.CharacterCountInString(n)
if j == i.CurSuggestion {
if x+c >= i.hscroll+i.Width {
i.hscroll = util.Clamp(x+c+1-i.Width, 0, s-i.Width)

View File

@@ -6,7 +6,6 @@ import (
"regexp"
"strconv"
"strings"
"unicode/utf8"
luar "layeh.com/gopher-luar"
@@ -168,34 +167,36 @@ func (s *StatusLine) Display() {
statusLineStyle = style
}
leftLen := util.StringWidth(leftText, utf8.RuneCount(leftText), 1)
rightLen := util.StringWidth(rightText, utf8.RuneCount(rightText), 1)
leftLen := util.StringWidth(leftText, util.CharacterCount(leftText), 1)
rightLen := util.StringWidth(rightText, util.CharacterCount(rightText), 1)
winX := s.win.X
for x := 0; x < s.win.Width; x++ {
if x < leftLen {
r, size := utf8.DecodeRune(leftText)
r, combc, size := util.DecodeCharacter(leftText)
leftText = leftText[size:]
rw := runewidth.RuneWidth(r)
for j := 0; j < rw; j++ {
c := r
if j > 0 {
c = ' '
combc = nil
x++
}
screen.SetContent(winX+x, y, c, nil, statusLineStyle)
screen.SetContent(winX+x, y, c, combc, statusLineStyle)
}
} else if x >= s.win.Width-rightLen && x < rightLen+s.win.Width-rightLen {
r, size := utf8.DecodeRune(rightText)
r, combc, size := util.DecodeCharacter(rightText)
rightText = rightText[size:]
rw := runewidth.RuneWidth(r)
for j := 0; j < rw; j++ {
c := r
if j > 0 {
c = ' '
combc = nil
x++
}
screen.SetContent(winX+x, y, c, nil, statusLineStyle)
screen.SetContent(winX+x, y, c, combc, statusLineStyle)
}
} else {
screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)

View File

@@ -1,8 +1,6 @@
package display
import (
"unicode/utf8"
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
@@ -34,7 +32,7 @@ func (w *TabWindow) LocFromVisual(vloc buffer.Loc) int {
for i, n := range w.Names {
x++
s := utf8.RuneCountInString(n)
s := util.CharacterCountInString(n)
if vloc.Y == w.Y && vloc.X < x+s {
return i
}
@@ -75,7 +73,7 @@ func (w *TabWindow) SetActive(a int) {
s := w.TotalSize()
for i, n := range w.Names {
c := utf8.RuneCountInString(n)
c := util.CharacterCountInString(n)
if i == a {
if x+c >= w.hscroll+w.Width {
w.hscroll = util.Clamp(x+c+1-w.Width, 0, s-w.Width)

View File

@@ -1,12 +1,11 @@
package display
import (
"unicode/utf8"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
"github.com/zyedidia/terminal"
)
@@ -98,12 +97,12 @@ func (w *TermWindow) Display() {
}
text := []byte(w.Name())
textLen := utf8.RuneCount(text)
textLen := util.CharacterCount(text)
for x := 0; x < w.Width; x++ {
if x < textLen {
r, size := utf8.DecodeRune(text)
r, combc, size := util.DecodeCharacter(text)
text = text[size:]
screen.SetContent(w.X+x, w.Y+w.Height, r, nil, statusLineStyle)
screen.SetContent(w.X+x, w.Y+w.Height, r, combc, statusLineStyle)
} else {
screen.SetContent(w.X+x, w.Y+w.Height, ' ', nil, statusLineStyle)
}

View File

@@ -1,15 +1,11 @@
package util
import (
"unicode/utf8"
)
// LuaRuneAt is a helper function for lua plugins to return the rune
// at an index within a string
func LuaRuneAt(str string, runeidx int) string {
i := 0
for len(str) > 0 {
r, size := utf8.DecodeRuneInString(str)
r, _, size := DecodeCharacterInString(str)
str = str[size:]
@@ -26,7 +22,7 @@ func LuaRuneAt(str string, runeidx int) string {
func LuaGetLeadingWhitespace(s string) string {
ws := []byte{}
for len(s) > 0 {
r, size := utf8.DecodeRuneInString(s)
r, _, size := DecodeCharacterInString(s)
if r == ' ' || r == '\t' {
ws = append(ws, byte(r))
} else {
@@ -40,6 +36,6 @@ func LuaGetLeadingWhitespace(s string) string {
// LuaIsWordChar returns true if the first rune in a string is a word character
func LuaIsWordChar(s string) bool {
r, _ := utf8.DecodeRuneInString(s)
r, _, _ := DecodeCharacterInString(s)
return IsWordChar(r)
}

86
internal/util/unicode.go Normal file
View File

@@ -0,0 +1,86 @@
package util
import (
"unicode"
"unicode/utf8"
)
// Unicode is annoying. A "code point" (rune in Go-speak) may need up to
// 4 bytes to represent it. In general, a code point will represent a
// complete character, but this is not always the case. A character with
// accents may be made up of multiple code points (the code point for the
// original character, and additional code points for each accent/marking).
// The functions below are meant to help deal with these additional "combining"
// code points. In underlying operations (search, replace, etc...), micro will
// treat a character with combining code points as just the original code point.
// For rendering, micro will display the combining characters. It's not perfect
// but it's pretty good.
// DecodeCharacter returns the next character from an array of bytes
// A character is a rune along with any accompanying combining runes
func DecodeCharacter(b []byte) (rune, []rune, int) {
r, size := utf8.DecodeRune(b)
b = b[size:]
c, s := utf8.DecodeRune(b)
var combc []rune
for unicode.In(c, unicode.Mark) {
combc = append(combc, c)
size += s
b = b[s:]
c, s = utf8.DecodeRune(b)
}
return r, combc, size
}
// DecodeCharacterInString returns the next character from a string
// A character is a rune along with any accompanying combining runes
func DecodeCharacterInString(str string) (rune, []rune, int) {
r, size := utf8.DecodeRuneInString(str)
str = str[size:]
c, s := utf8.DecodeRuneInString(str)
var combc []rune
for unicode.In(c, unicode.Mark) {
combc = append(combc, c)
size += s
str = str[s:]
c, s = utf8.DecodeRuneInString(str)
}
return r, combc, size
}
// CharacterCount returns the number of characters in a byte array
// Similar to utf8.RuneCount but for unicode characters
func CharacterCount(b []byte) int {
s := 0
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
if !unicode.In(r, unicode.Mark) {
s++
}
b = b[size:]
}
return s
}
// CharacterCount returns the number of characters in a string
// Similar to utf8.RuneCountInString but for unicode characters
func CharacterCountInString(str string) int {
s := 0
for _, r := range str {
if !unicode.In(r, unicode.Mark) {
s++
}
}
return s
}

View File

@@ -13,7 +13,6 @@ import (
"strings"
"time"
"unicode"
"unicode/utf8"
"github.com/blang/semver"
runewidth "github.com/mattn/go-runewidth"
@@ -64,7 +63,7 @@ func SliceEnd(slc []byte, index int) []byte {
return slc[totalSize:]
}
_, size := utf8.DecodeRune(slc[totalSize:])
_, _, size := DecodeCharacter(slc[totalSize:])
totalSize += size
i++
}
@@ -82,7 +81,7 @@ func SliceEndStr(str string, index int) string {
return str[totalSize:]
}
_, size := utf8.DecodeRuneInString(str[totalSize:])
_, _, size := DecodeCharacterInString(str[totalSize:])
totalSize += size
i++
}
@@ -101,7 +100,7 @@ func SliceStart(slc []byte, index int) []byte {
return slc[:totalSize]
}
_, size := utf8.DecodeRune(slc[totalSize:])
_, _, size := DecodeCharacter(slc[totalSize:])
totalSize += size
i++
}
@@ -119,7 +118,7 @@ func SliceStartStr(str string, index int) string {
return str[:totalSize]
}
_, size := utf8.DecodeRuneInString(str[totalSize:])
_, _, size := DecodeCharacterInString(str[totalSize:])
totalSize += size
i++
}
@@ -135,7 +134,7 @@ func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int, int) {
width := 0
i := 0
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := DecodeCharacter(b)
w := 0
switch r {
@@ -172,7 +171,7 @@ func StringWidth(b []byte, n, tabsize int) int {
i := 0
width := 0
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := DecodeCharacter(b)
b = b[size:]
switch r {
@@ -265,7 +264,7 @@ func IsBytesWhitespace(b []byte) bool {
// RunePos returns the rune index of a given byte index
// Make sure the byte index is not between code points
func RunePos(b []byte, i int) int {
return utf8.RuneCount(b[:i])
return CharacterCount(b[:i])
}
// MakeRelative will attempt to make a relative path between path and base
@@ -344,7 +343,7 @@ func EscapePath(path string) string {
func GetLeadingWhitespace(b []byte) []byte {
ws := []byte{}
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := DecodeCharacter(b)
if r == ' ' || r == '\t' {
ws = append(ws, byte(r))
} else {
@@ -370,7 +369,7 @@ func GetCharPosInLine(b []byte, visualPos int, tabsize int) int {
i := 0 // char pos
width := 0 // string visual width
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := DecodeCharacter(b)
b = b[size:]
switch r {
@@ -416,7 +415,7 @@ func Clamp(val, min, max int) int {
}
func IsNonAlphaNumeric(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_'
}
func ParseSpecial(s string) string {

View File

@@ -3,7 +3,6 @@ package highlight
import (
"regexp"
"strings"
"unicode/utf8"
)
func sliceStart(slc []byte, index int) []byte {
@@ -15,7 +14,7 @@ func sliceStart(slc []byte, index int) []byte {
return slc[totalSize:]
}
_, size := utf8.DecodeRune(slc[totalSize:])
_, _, size := DecodeCharacter(slc[totalSize:])
totalSize += size
i++
}
@@ -32,7 +31,7 @@ func sliceEnd(slc []byte, index int) []byte {
return slc[:totalSize]
}
_, size := utf8.DecodeRune(slc[totalSize:])
_, _, size := DecodeCharacter(slc[totalSize:])
totalSize += size
i++
}
@@ -47,9 +46,9 @@ func runePos(p int, str []byte) int {
return 0
}
if p >= len(str) {
return utf8.RuneCount(str)
return CharacterCount(str)
}
return utf8.RuneCount(str[:p])
return CharacterCount(str[:p])
}
func combineLineMatch(src, dst LineMatch) LineMatch {
@@ -112,7 +111,7 @@ func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte, canMatchSt
var strbytes []byte
if skip != nil {
strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
res := make([]byte, utf8.RuneCount(match))
res := make([]byte, CharacterCount(match))
return res
})
} else {
@@ -148,7 +147,7 @@ func findAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd b
}
func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, curRegion *region, statesOnly bool) LineMatch {
lineLen := utf8.RuneCount(line)
lineLen := CharacterCount(line)
if start == 0 {
if !statesOnly {
if _, ok := highlights[0]; !ok {
@@ -236,7 +235,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
}
func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, statesOnly bool) LineMatch {
lineLen := utf8.RuneCount(line)
lineLen := CharacterCount(line)
if lineLen == 0 {
if canMatchEnd {
h.lastRegion = nil
@@ -336,11 +335,11 @@ func (h *Highlighter) HighlightStates(input LineStates) {
}
}
// HighlightMatches sets the matches for each line in between startline and endline
// HighlightMatches sets the matches for each line from startline to endline
// It sets all other matches in the buffer to nil to conserve memory
// This assumes that all the states are set correctly
func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) {
for i := startline; i < endline; i++ {
for i := startline; i <= endline; i++ {
if i >= input.LinesNum() {
break
}

75
pkg/highlight/unicode.go Normal file
View File

@@ -0,0 +1,75 @@
package highlight
import (
"unicode"
"unicode/utf8"
)
// DecodeCharacter returns the next character from an array of bytes
// A character is a rune along with any accompanying combining runes
func DecodeCharacter(b []byte) (rune, []rune, int) {
r, size := utf8.DecodeRune(b)
b = b[size:]
c, s := utf8.DecodeRune(b)
var combc []rune
for unicode.In(c, unicode.Mark) {
combc = append(combc, c)
size += s
b = b[s:]
c, s = utf8.DecodeRune(b)
}
return r, combc, size
}
// DecodeCharacterInString returns the next character from a string
// A character is a rune along with any accompanying combining runes
func DecodeCharacterInString(str string) (rune, []rune, int) {
r, size := utf8.DecodeRuneInString(str)
str = str[size:]
c, s := utf8.DecodeRuneInString(str)
var combc []rune
for unicode.In(c, unicode.Mark) {
combc = append(combc, c)
size += s
str = str[s:]
c, s = utf8.DecodeRuneInString(str)
}
return r, combc, size
}
// CharacterCount returns the number of characters in a byte array
// Similar to utf8.RuneCount but for unicode characters
func CharacterCount(b []byte) int {
s := 0
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
if !unicode.In(r, unicode.Mark) {
s++
}
b = b[size:]
}
return s
}
// CharacterCount returns the number of characters in a string
// Similar to utf8.RuneCountInString but for unicode characters
func CharacterCountInString(str string) int {
s := 0
for _, r := range str {
if !unicode.In(r, unicode.Mark) {
s++
}
}
return s
}

View File

@@ -132,7 +132,7 @@ If you would like no foreground you can just use a comma with nothing in front:
color-link comment ",blue"
```
You can also put bold, or underline in front of the color:
You can also put bold, italic, or underline in front of the color:
```
color-link comment "bold red"

View File

@@ -62,6 +62,11 @@ quotes here but these are not necessary when entering the command in micro.
* `tab 'filename'`: opens the given file in a new tab.
* `tabmove '[-+]?n'`: Moves the active tab to another slot. `n` is an integer.
If `n` is prefixed with `-` or `+`, then it represents a relative position
(e.g. `tabmove +2` moves the tab to the right by `2`). If `n` has no prefix,
it represents an absolute position (e.g. `tabmove 2` moves the tab to slot `2`).
* `tabswitch 'tab'`: This command will switch to the specified tab. The `tab`
can either be a tab number, or a name of a tab.

View File

@@ -185,6 +185,7 @@ Save
SaveAll
SaveAs
Find
FindLiteral
FindNext
FindPrevious
Undo

View File

@@ -70,7 +70,7 @@ function init()
makeLinter("dmd", "d", "dmd", {"-color=off", "-o-", "-w", "-wi", "-c", "%f"}, "%f%(%l%):.+: %m")
makeLinter("gobuild", "go", "go", {"build", "-o", devnull, "%d"}, "%f:%l:%c:? %m")
-- makeLinter("golint", "go", "golint", {"%f"}, "%f:%l:%c: %m")
makeLinter("hlint", "haskell", "hlint", {"%f"}, "%f:%l:%c: %m")
makeLinter("hlint", "haskell", "hlint", {"%f"}, "%f:%l:%c.-: %m")
makeLinter("javac", "java", "javac", {"-d", "%d", "%f"}, "%f:%l: error: %m")
makeLinter("jshint", "javascript", "jshint", {"%f"}, "%f: line %l,.+, %m")
makeLinter("literate", "literate", "lit", {"-c", "%f"}, "%f:%l:%m", {}, false, true)

View File

@@ -1,28 +0,0 @@
filetype: c++
detect:
filename: "\\.c(c|pp|xx)$|\\.h(h|pp|xx)$|\\.inl$|\\.ii?$|\\.(def)$"
rules:
- identifier: "\\b[A-Z_][0-9A-Z_]+\\b"
- type: "\\b(auto|float|double|bool|char|int|short|long|sizeof|enum|void|static|const|constexpr|struct|union|typedef|extern|(un)?signed|inline)\\b"
- type: "\\b((s?size)|((u_?)?int(8|16|32|64|ptr)))_t\\b"
- statement: "\\b(class|namespace|template|public|protected|private|typename|this|friend|virtual|using|mutable|volatile|register|explicit)\\b"
- statement: "\\b(for|if|while|do|else|case|default|switch)\\b"
- statement: "\\b(try|throw|catch|operator|new|delete)\\b"
- special: "\\b(goto|continue|break|return)\\b"
- preproc: "^[[:space:]]*#[[:space:]]*(define|pragma|include|(un|ifn?)def|endif|el(if|se)|if|warning|error)"
- constant: "'([^'\\\\]|(\\\\[\"'abfnrtv\\\\]))'|'\\\\(([0-3]?[0-7]{1,2}))'|'\\\\x[0-9A-Fa-f]{1,2}'"
- statement: "__attribute__[[:space:]]*\\(\\([^)]*\\)\\)|__(aligned|asm|builtin|hidden|inline|packed|restrict|section|typeof|weak)__"
- symbol.operator: "[.:;,+*|=!\\%]|<|>|/|-|&"
- symbol.brackets: "[(){}]|\\[|\\]"
- constant.number: "\\b[0-9]+\\b|\\b0x[0-9A-Fa-f]+\\b"
- constant.bool: "\\b(true|false)\\b|NULL"
- constant.string: "\"(\\\\.|[^\"])*\""
- comment: "//.*"
- comment:
start: "/\\*"
end: "\\*/"
rules: []
- indent-char.whitespace: "[[:space:]]+$"

View File

@@ -233,8 +233,6 @@ func main() {
re := regexp.MustCompile(`[^\w]`)
usedNames := map[string]bool{}
var b strings.Builder
for _, test := range tests {
name := strings.Title(strings.ToLower(test.description))
name = re.ReplaceAllLiteralString(name, "")
@@ -253,15 +251,5 @@ func main() {
usedNames[name] = true
fmt.Println(testToGoTest(test, name))
b.WriteString("Test")
b.WriteString(name)
b.WriteString("(nil)\n")
}
fmt.Println("func BenchmarkBuffer(b *testing.B) {")
fmt.Println("for i := 0; i < b.N; i++ {")
fmt.Print(b.String())
fmt.Println("}")
fmt.Println("}")
}