mirror of
https://github.com/zyedidia/micro.git
synced 2026-04-05 01:19:53 +09:00
Compare commits
22 Commits
performanc
...
nightly
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c88f24ad7 | ||
|
|
25faa84fcb | ||
|
|
4d04ad7290 | ||
|
|
4ddf4fe2b1 | ||
|
|
1be75cc697 | ||
|
|
d976b3f170 | ||
|
|
4f32b47075 | ||
|
|
bcd6c81f50 | ||
|
|
6760768b9e | ||
|
|
42d0ddf73d | ||
|
|
5ddb05b5ec | ||
|
|
ad43a5da99 | ||
|
|
dc10f6d53c | ||
|
|
518a274980 | ||
|
|
184dd259e9 | ||
|
|
f1a7f0d392 | ||
|
|
d38f0dfe7a | ||
|
|
4aa706cbc5 | ||
|
|
1317a2deb1 | ||
|
|
fda43aff15 | ||
|
|
7ef8ca476d | ||
|
|
3a7403bde4 |
@@ -363,6 +363,12 @@ func main() {
|
||||
fmt.Println("Fatal: Micro could not initialize a Screen.")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
util.Sigterm = make(chan os.Signal, 1)
|
||||
sighup = make(chan os.Signal, 1)
|
||||
signal.Notify(util.Sigterm, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGABRT)
|
||||
signal.Notify(sighup, syscall.SIGHUP)
|
||||
|
||||
m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
|
||||
clipErr := clipboard.Initialize(m)
|
||||
|
||||
@@ -400,6 +406,8 @@ func main() {
|
||||
action.InitBindings()
|
||||
action.InitCommands()
|
||||
|
||||
timerChan = make(chan func())
|
||||
|
||||
err = config.RunPluginFn("preinit")
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
@@ -444,13 +452,6 @@ func main() {
|
||||
|
||||
screen.Events = make(chan tcell.Event)
|
||||
|
||||
util.Sigterm = make(chan os.Signal, 1)
|
||||
sighup = make(chan os.Signal, 1)
|
||||
signal.Notify(util.Sigterm, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGABRT)
|
||||
signal.Notify(sighup, syscall.SIGHUP)
|
||||
|
||||
timerChan = make(chan func())
|
||||
|
||||
// Here is the event loop which runs in a separate thread
|
||||
go func() {
|
||||
for {
|
||||
|
||||
@@ -14,9 +14,8 @@ import (
|
||||
func (*BufPane) Suspend() bool {
|
||||
screenb := screen.TempFini()
|
||||
|
||||
// suspend the process
|
||||
pid := syscall.Getpid()
|
||||
err := syscall.Kill(pid, syscall.SIGSTOP)
|
||||
// suspend the process group
|
||||
err := syscall.Kill(0, syscall.SIGSTOP)
|
||||
if err != nil {
|
||||
screen.TermMessage(err)
|
||||
}
|
||||
|
||||
@@ -141,9 +141,11 @@ func (h *BufPane) TextFilterCmd(args []string) {
|
||||
InfoBar.Error("usage: textfilter arguments")
|
||||
return
|
||||
}
|
||||
|
||||
for _, c := range h.Buf.GetCursors() {
|
||||
sel := c.GetSelection()
|
||||
if len(sel) == 0 {
|
||||
fromSelection := len(sel) > 0
|
||||
if !fromSelection {
|
||||
c.SelectWord()
|
||||
sel = c.GetSelection()
|
||||
}
|
||||
@@ -158,7 +160,18 @@ func (h *BufPane) TextFilterCmd(args []string) {
|
||||
return
|
||||
}
|
||||
c.DeleteSelection()
|
||||
h.Buf.Insert(c.Loc, bout.String())
|
||||
insertStart := c.Loc
|
||||
insertedText := bout.String()
|
||||
h.Buf.Insert(c.Loc, insertedText)
|
||||
|
||||
if fromSelection {
|
||||
// Select the pasted output if the input was selected
|
||||
charCount := util.CharacterCountInString(insertedText)
|
||||
insertEnd := insertStart.Move(charCount, h.Buf)
|
||||
c.SetSelectionStart(insertStart)
|
||||
c.SetSelectionEnd(insertEnd)
|
||||
c.Loc = insertEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ func (b *SharedBuffer) calcHash(out *[md5.Size]byte) {
|
||||
h := md5.New()
|
||||
|
||||
if len(b.lines) > 0 {
|
||||
h.Write(b.lines[0].data())
|
||||
h.Write(b.lines[0].data)
|
||||
|
||||
for _, l := range b.lines[1:] {
|
||||
if b.Endings == FFDos {
|
||||
@@ -173,7 +173,7 @@ func (b *SharedBuffer) calcHash(out *[md5.Size]byte) {
|
||||
} else {
|
||||
h.Write([]byte{'\n'})
|
||||
}
|
||||
h.Write(l.data())
|
||||
h.Write(l.data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -866,7 +866,7 @@ func (b *Buffer) UpdateRules() {
|
||||
if header.MatchFileName(b.Path) {
|
||||
matchedFileName = true
|
||||
}
|
||||
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data()) {
|
||||
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data) {
|
||||
matchedFileHeader = true
|
||||
}
|
||||
} else if header.FileType == ft {
|
||||
@@ -920,7 +920,7 @@ func (b *Buffer) UpdateRules() {
|
||||
if header.MatchFileName(b.Path) {
|
||||
fnameMatches = append(fnameMatches, syntaxFileInfo{header, f.Name(), nil})
|
||||
}
|
||||
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data()) {
|
||||
if len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data) {
|
||||
headerMatches = append(headerMatches, syntaxFileInfo{header, f.Name(), nil})
|
||||
}
|
||||
} else if header.FileType == ft {
|
||||
@@ -953,7 +953,7 @@ func (b *Buffer) UpdateRules() {
|
||||
for _, m := range matches {
|
||||
if m.header.HasFileSignature() {
|
||||
for i := 0; i < limit; i++ {
|
||||
if m.header.MatchFileSignature(b.lines[i].data()) {
|
||||
if m.header.MatchFileSignature(b.lines[i].data) {
|
||||
syntaxFile = m.fileName
|
||||
if m.syntaxDef != nil {
|
||||
b.SyntaxDef = m.syntaxDef
|
||||
@@ -1130,11 +1130,11 @@ func (b *Buffer) MoveLinesUp(start int, end int) {
|
||||
if start < 1 || start >= end || end > len(b.lines) {
|
||||
return
|
||||
}
|
||||
l := b.LineString(start - 1)
|
||||
l := string(b.LineBytes(start - 1))
|
||||
if end == len(b.lines) {
|
||||
b.insert(
|
||||
Loc{
|
||||
len(b.lines[end-1].runes),
|
||||
util.CharacterCount(b.lines[end-1].data),
|
||||
end - 1,
|
||||
},
|
||||
[]byte{'\n'},
|
||||
@@ -1155,7 +1155,7 @@ func (b *Buffer) MoveLinesDown(start int, end int) {
|
||||
if start < 0 || start >= end || end >= len(b.lines) {
|
||||
return
|
||||
}
|
||||
l := b.LineString(end)
|
||||
l := string(b.LineBytes(end))
|
||||
b.Insert(
|
||||
Loc{0, start},
|
||||
l+"\n",
|
||||
@@ -1196,7 +1196,7 @@ func (b *Buffer) findMatchingBrace(braceType [2]rune, start Loc, char rune) (Loc
|
||||
}
|
||||
} else if char == braceType[1] {
|
||||
for y := start.Y; y >= 0; y-- {
|
||||
l := []rune(string(b.LineBytes(y)))
|
||||
l := []rune(string(b.lines[y].data))
|
||||
xInit := len(l) - 1
|
||||
if y == start.Y {
|
||||
xInit = start.X
|
||||
@@ -1281,14 +1281,7 @@ func (b *Buffer) Retab() {
|
||||
l = bytes.TrimLeft(l, " \t")
|
||||
|
||||
b.Lock()
|
||||
ws = append(ws, l...)
|
||||
var runes []Character
|
||||
for len(ws) > 0 {
|
||||
combc, s := util.DecodeCombinedCharacter(ws)
|
||||
runes = append(runes, Character{combc})
|
||||
ws = ws[s:]
|
||||
}
|
||||
b.lines[i].runes = runes
|
||||
b.lines[i].data = append(ws, l...)
|
||||
b.Unlock()
|
||||
|
||||
b.MarkModified(i, i)
|
||||
@@ -1324,7 +1317,7 @@ func ParseCursorLocation(cursorPositions []string) (Loc, error) {
|
||||
|
||||
// Line returns the string representation of the given line number
|
||||
func (b *Buffer) Line(i int) string {
|
||||
return b.LineString(i)
|
||||
return string(b.LineBytes(i))
|
||||
}
|
||||
|
||||
func (b *Buffer) Write(bytes []byte) (n int, err error) {
|
||||
|
||||
@@ -601,13 +601,24 @@ func (c *Cursor) SubWordLeft() {
|
||||
|
||||
// RuneUnder returns the rune under the given x position
|
||||
func (c *Cursor) RuneUnder(x int) rune {
|
||||
line := c.buf.LineCharacters(c.Y)
|
||||
if len(line) == 0 || x >= len(line) {
|
||||
line := c.buf.LineBytes(c.Y)
|
||||
if len(line) == 0 || x >= util.CharacterCount(line) {
|
||||
return '\n'
|
||||
} else if x < 0 {
|
||||
x = 0
|
||||
}
|
||||
return line[x].combc[0]
|
||||
i := 0
|
||||
for len(line) > 0 {
|
||||
r, _, size := util.DecodeCharacter(line)
|
||||
line = line[size:]
|
||||
|
||||
if i == x {
|
||||
return r
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
return '\n'
|
||||
}
|
||||
|
||||
func (c *Cursor) StoreVisualX() {
|
||||
|
||||
@@ -10,6 +10,28 @@ import (
|
||||
"github.com/micro-editor/micro/v2/pkg/highlight"
|
||||
)
|
||||
|
||||
// Finds the byte index of the nth rune in a byte slice
|
||||
func runeToByteIndex(n int, txt []byte) int {
|
||||
if n == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
count := 0
|
||||
i := 0
|
||||
for len(txt) > 0 {
|
||||
_, _, size := util.DecodeCharacter(txt)
|
||||
|
||||
txt = txt[size:]
|
||||
count += size
|
||||
i++
|
||||
|
||||
if i == n {
|
||||
break
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// A searchState contains the search match info for a single line
|
||||
type searchState struct {
|
||||
search string
|
||||
@@ -19,14 +41,10 @@ type searchState struct {
|
||||
done bool
|
||||
}
|
||||
|
||||
type Character struct {
|
||||
combc []rune
|
||||
}
|
||||
|
||||
// A Line contains the slice of runes as well as a highlight state, match
|
||||
// A Line contains the data in bytes as well as a highlight state, match
|
||||
// and a flag for whether the highlighting needs to be updated
|
||||
type Line struct {
|
||||
runes []Character
|
||||
data []byte
|
||||
|
||||
state highlight.State
|
||||
match highlight.LineMatch
|
||||
@@ -41,24 +59,6 @@ type Line struct {
|
||||
search map[*Buffer]*searchState
|
||||
}
|
||||
|
||||
// data returns the line as byte slice
|
||||
func (l Line) data() []byte {
|
||||
var runes []rune
|
||||
for _, r := range l.runes {
|
||||
runes = append(runes, r.combc[0:]...)
|
||||
}
|
||||
return []byte(string(runes))
|
||||
}
|
||||
|
||||
// String returns the line as string
|
||||
func (l Line) String() string {
|
||||
var runes []rune
|
||||
for _, r := range l.runes {
|
||||
runes = append(runes, r.combc[0:]...)
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
const (
|
||||
// Line ending file formats
|
||||
FFAuto = 0 // Autodetect format
|
||||
@@ -94,7 +94,7 @@ func Append(slice []Line, data ...Line) []Line {
|
||||
return slice
|
||||
}
|
||||
|
||||
// NewLineArray returns a new line array from an array of runes
|
||||
// NewLineArray returns a new line array from an array of bytes
|
||||
func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray {
|
||||
la := new(LineArray)
|
||||
|
||||
@@ -144,16 +144,10 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
|
||||
loaded += dlen
|
||||
}
|
||||
|
||||
var runes []Character
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
for len(data) > 0 {
|
||||
combc, s := util.DecodeCombinedCharacter(data)
|
||||
runes = append(runes, Character{combc})
|
||||
data = data[s:]
|
||||
}
|
||||
la.lines = Append(la.lines, Line{
|
||||
runes: runes,
|
||||
data: data,
|
||||
state: nil,
|
||||
match: nil,
|
||||
})
|
||||
@@ -161,14 +155,8 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
|
||||
// Last line was read
|
||||
break
|
||||
} else {
|
||||
data = data[:dlen-1]
|
||||
for len(data) > 0 {
|
||||
combc, s := util.DecodeCombinedCharacter(data)
|
||||
runes = append(runes, Character{combc})
|
||||
data = data[s:]
|
||||
}
|
||||
la.lines = Append(la.lines, Line{
|
||||
runes: runes,
|
||||
data: data[:dlen-1],
|
||||
state: nil,
|
||||
match: nil,
|
||||
})
|
||||
@@ -186,7 +174,7 @@ func (la *LineArray) Bytes() []byte {
|
||||
// initsize should provide a good estimate
|
||||
b.Grow(int(la.initsize + 4096))
|
||||
for i, l := range la.lines {
|
||||
b.Write(l.data())
|
||||
b.Write(l.data)
|
||||
if i != len(la.lines)-1 {
|
||||
if la.Endings == FFDos {
|
||||
b.WriteByte('\r')
|
||||
@@ -200,13 +188,13 @@ func (la *LineArray) Bytes() []byte {
|
||||
// newlineBelow adds a newline below the given line number
|
||||
func (la *LineArray) newlineBelow(y int) {
|
||||
la.lines = append(la.lines, Line{
|
||||
runes: []Character{},
|
||||
data: []byte{' '},
|
||||
state: nil,
|
||||
match: nil,
|
||||
})
|
||||
copy(la.lines[y+2:], la.lines[y+1:])
|
||||
la.lines[y+1] = Line{
|
||||
runes: []Character{},
|
||||
data: []byte{},
|
||||
state: la.lines[y].state,
|
||||
match: nil,
|
||||
}
|
||||
@@ -217,65 +205,41 @@ func (la *LineArray) insert(pos Loc, value []byte) {
|
||||
la.lock.Lock()
|
||||
defer la.lock.Unlock()
|
||||
|
||||
var runes []Character
|
||||
for len(value) > 0 {
|
||||
combc, s := util.DecodeCombinedCharacter(value)
|
||||
runes = append(runes, Character{combc})
|
||||
value = value[s:]
|
||||
}
|
||||
x, y := util.Min(pos.X, len(la.lines[pos.Y].runes)), pos.Y
|
||||
start := -1
|
||||
x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
|
||||
for i := 0; i < len(value); i++ {
|
||||
if value[i] == '\n' || (value[i] == '\r' && i < len(value)-1 && value[i+1] == '\n') {
|
||||
la.split(Loc{x, y})
|
||||
x = 0
|
||||
y++
|
||||
|
||||
outer:
|
||||
for i, r := range runes {
|
||||
for j := 0; j < len(r.combc); j++ {
|
||||
if r.combc[j] == '\n' || (r.combc[j] == '\r' && i < len(runes)-1 && r.combc[j+1] == '\n') {
|
||||
la.split(Loc{x, y})
|
||||
if i > 0 && start < len(runes) && start < i {
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
la.insertRunes(Loc{x, y}, runes[start:i])
|
||||
}
|
||||
|
||||
x = 0
|
||||
y++
|
||||
|
||||
if r.combc[j] == '\r' {
|
||||
i++
|
||||
}
|
||||
if i+1 <= len(runes) {
|
||||
start = i + 1
|
||||
}
|
||||
|
||||
continue outer
|
||||
if value[i] == '\r' {
|
||||
i++
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
if start < 0 {
|
||||
la.insertRunes(Loc{x, y}, runes)
|
||||
} else if start < len(runes) {
|
||||
la.insertRunes(Loc{x, y}, runes[start:])
|
||||
la.insertByte(Loc{x, y}, value[i])
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
// Inserts a rune array at a given location
|
||||
func (la *LineArray) insertRunes(pos Loc, runes []Character) {
|
||||
la.lines[pos.Y].runes = append(la.lines[pos.Y].runes, runes...)
|
||||
copy(la.lines[pos.Y].runes[pos.X+len(runes):], la.lines[pos.Y].runes[pos.X:])
|
||||
copy(la.lines[pos.Y].runes[pos.X:], runes)
|
||||
// InsertByte inserts a byte at a given location
|
||||
func (la *LineArray) insertByte(pos Loc, value byte) {
|
||||
la.lines[pos.Y].data = append(la.lines[pos.Y].data, 0)
|
||||
copy(la.lines[pos.Y].data[pos.X+1:], la.lines[pos.Y].data[pos.X:])
|
||||
la.lines[pos.Y].data[pos.X] = value
|
||||
}
|
||||
|
||||
// joinLines joins the two lines a and b
|
||||
func (la *LineArray) joinLines(a, b int) {
|
||||
la.insertRunes(Loc{len(la.lines[a].runes), a}, la.lines[b].runes)
|
||||
la.lines[a].data = append(la.lines[a].data, la.lines[b].data...)
|
||||
la.deleteLine(b)
|
||||
}
|
||||
|
||||
// split splits a line at a given position
|
||||
func (la *LineArray) split(pos Loc) {
|
||||
la.newlineBelow(pos.Y)
|
||||
la.insertRunes(Loc{0, pos.Y + 1}, la.lines[pos.Y].runes[pos.X:])
|
||||
la.lines[pos.Y+1].data = append(la.lines[pos.Y+1].data, la.lines[pos.Y].data[pos.X:]...)
|
||||
la.lines[pos.Y+1].state = la.lines[pos.Y].state
|
||||
la.lines[pos.Y].state = nil
|
||||
la.lines[pos.Y].match = nil
|
||||
@@ -289,10 +253,10 @@ func (la *LineArray) remove(start, end Loc) []byte {
|
||||
defer la.lock.Unlock()
|
||||
|
||||
sub := la.Substr(start, end)
|
||||
startX := util.Min(start.X, len(la.lines[start.Y].runes))
|
||||
endX := util.Min(end.X, len(la.lines[end.Y].runes))
|
||||
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
|
||||
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
|
||||
if start.Y == end.Y {
|
||||
la.lines[start.Y].runes = append(la.lines[start.Y].runes[:startX], la.lines[start.Y].runes[endX:]...)
|
||||
la.lines[start.Y].data = append(la.lines[start.Y].data[:startX], la.lines[start.Y].data[endX:]...)
|
||||
} else {
|
||||
la.deleteLines(start.Y+1, end.Y-1)
|
||||
la.deleteToEnd(Loc{startX, start.Y})
|
||||
@@ -304,12 +268,12 @@ func (la *LineArray) remove(start, end Loc) []byte {
|
||||
|
||||
// deleteToEnd deletes from the end of a line to the position
|
||||
func (la *LineArray) deleteToEnd(pos Loc) {
|
||||
la.lines[pos.Y].runes = la.lines[pos.Y].runes[:pos.X]
|
||||
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X]
|
||||
}
|
||||
|
||||
// deleteFromStart deletes from the start of a line to the position
|
||||
func (la *LineArray) deleteFromStart(pos Loc) {
|
||||
la.lines[pos.Y].runes = la.lines[pos.Y].runes[pos.X+1:]
|
||||
la.lines[pos.Y].data = la.lines[pos.Y].data[pos.X+1:]
|
||||
}
|
||||
|
||||
// deleteLine deletes the line number
|
||||
@@ -323,35 +287,22 @@ func (la *LineArray) deleteLines(y1, y2 int) {
|
||||
|
||||
// Substr returns the string representation between two locations
|
||||
func (la *LineArray) Substr(start, end Loc) []byte {
|
||||
startX := util.Min(start.X, len(la.lines[start.Y].runes))
|
||||
endX := util.Min(end.X, len(la.lines[end.Y].runes))
|
||||
var runes []rune
|
||||
if start.Y == end.Y && startX <= endX {
|
||||
for _, r := range la.lines[start.Y].runes[startX:endX] {
|
||||
runes = append(runes, r.combc[0:]...)
|
||||
}
|
||||
return []byte(string(runes))
|
||||
startX := runeToByteIndex(start.X, la.lines[start.Y].data)
|
||||
endX := runeToByteIndex(end.X, la.lines[end.Y].data)
|
||||
if start.Y == end.Y {
|
||||
src := la.lines[start.Y].data[startX:endX]
|
||||
dest := make([]byte, len(src))
|
||||
copy(dest, src)
|
||||
return dest
|
||||
}
|
||||
|
||||
var str []byte
|
||||
for _, r := range la.lines[start.Y].runes[startX:] {
|
||||
runes = append(runes, r.combc[0:]...)
|
||||
}
|
||||
str = append(str, []byte(string(runes))...)
|
||||
str := make([]byte, 0, len(la.lines[start.Y+1].data)*(end.Y-start.Y))
|
||||
str = append(str, la.lines[start.Y].data[startX:]...)
|
||||
str = append(str, '\n')
|
||||
for i := start.Y + 1; i <= end.Y-1; i++ {
|
||||
runes = runes[:0]
|
||||
for _, r := range la.lines[i].runes {
|
||||
runes = append(runes, r.combc[0:]...)
|
||||
}
|
||||
str = append(str, []byte(string(runes))...)
|
||||
str = append(str, la.lines[i].data...)
|
||||
str = append(str, '\n')
|
||||
}
|
||||
runes = runes[:0]
|
||||
for _, r := range la.lines[end.Y].runes[:endX] {
|
||||
runes = append(runes, r.combc[0:]...)
|
||||
}
|
||||
str = append(str, []byte(string(runes))...)
|
||||
str = append(str, la.lines[end.Y].data[:endX]...)
|
||||
return str
|
||||
}
|
||||
|
||||
@@ -368,38 +319,15 @@ 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{len(la.lines[numlines-1].runes), numlines - 1}
|
||||
}
|
||||
|
||||
// LineCharacters returns line n as an array of characters
|
||||
func (la *LineArray) LineCharacters(n int) []Character {
|
||||
if n >= len(la.lines) || n < 0 {
|
||||
return []Character{}
|
||||
}
|
||||
|
||||
return la.lines[n].runes
|
||||
return Loc{util.CharacterCount(la.lines[numlines-1].data), numlines - 1}
|
||||
}
|
||||
|
||||
// LineBytes returns line n as an array of bytes
|
||||
func (la *LineArray) LineBytes(n int) []byte {
|
||||
if n >= len(la.lines) || n < 0 {
|
||||
func (la *LineArray) LineBytes(lineN int) []byte {
|
||||
if lineN >= len(la.lines) || lineN < 0 {
|
||||
return []byte{}
|
||||
}
|
||||
return la.lines[n].data()
|
||||
}
|
||||
|
||||
// LineString returns line n as an string
|
||||
func (la *LineArray) LineString(n int) string {
|
||||
if n >= len(la.lines) || n < 0 {
|
||||
return string("")
|
||||
}
|
||||
|
||||
var runes []rune
|
||||
for _, r := range la.lines[n].runes {
|
||||
runes = append(runes, r.combc[0:]...)
|
||||
}
|
||||
|
||||
return string(runes)
|
||||
return la.lines[lineN].data
|
||||
}
|
||||
|
||||
// State gets the highlight state for the given line number
|
||||
@@ -481,7 +409,7 @@ func (la *LineArray) SearchMatch(b *Buffer, pos Loc) bool {
|
||||
if !s.done {
|
||||
s.match = nil
|
||||
start := Loc{0, lineN}
|
||||
end := Loc{len(la.lines[lineN].runes), lineN}
|
||||
end := Loc{util.CharacterCount(la.lines[lineN].data), lineN}
|
||||
for start.X < end.X {
|
||||
m, found, _ := b.FindNext(b.LastSearch, start, end, start, true, b.LastSearchRegex)
|
||||
if !found {
|
||||
|
||||
@@ -9,7 +9,7 @@ type Loc struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
// LessThan returns true if b is smaller
|
||||
// LessThan returns true if l is smaller than b
|
||||
func (l Loc) LessThan(b Loc) bool {
|
||||
if l.Y < b.Y {
|
||||
return true
|
||||
@@ -17,7 +17,7 @@ func (l Loc) LessThan(b Loc) bool {
|
||||
return l.Y == b.Y && l.X < b.X
|
||||
}
|
||||
|
||||
// GreaterThan returns true if b is bigger
|
||||
// GreaterThan returns true if l is bigger than b
|
||||
func (l Loc) GreaterThan(b Loc) bool {
|
||||
if l.Y > b.Y {
|
||||
return true
|
||||
@@ -25,7 +25,7 @@ func (l Loc) GreaterThan(b Loc) bool {
|
||||
return l.Y == b.Y && l.X > b.X
|
||||
}
|
||||
|
||||
// GreaterEqual returns true if b is greater than or equal to b
|
||||
// GreaterEqual returns true if l is greater than or equal to b
|
||||
func (l Loc) GreaterEqual(b Loc) bool {
|
||||
if l.Y > b.Y {
|
||||
return true
|
||||
@@ -36,7 +36,7 @@ func (l Loc) GreaterEqual(b Loc) bool {
|
||||
return l == b
|
||||
}
|
||||
|
||||
// LessEqual returns true if b is less than or equal to b
|
||||
// LessEqual returns true if l is less than or equal to b
|
||||
func (l Loc) LessEqual(b Loc) bool {
|
||||
if l.Y < b.Y {
|
||||
return true
|
||||
@@ -113,14 +113,13 @@ func (l Loc) left(buf *LineArray) Loc {
|
||||
// MoveLA moves the cursor n characters to the left or right
|
||||
// It moves the cursor left if n is negative
|
||||
func (l Loc) MoveLA(n int, buf *LineArray) Loc {
|
||||
if n > 0 {
|
||||
for i := 0; i < n; i++ {
|
||||
l = l.right(buf)
|
||||
}
|
||||
return l
|
||||
for n > 0 {
|
||||
l = l.right(buf)
|
||||
n--
|
||||
}
|
||||
for i := 0; i < util.Abs(n); i++ {
|
||||
for n < 0 {
|
||||
l = l.left(buf)
|
||||
n++
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package buffer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
@@ -10,7 +11,6 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
@@ -156,7 +156,7 @@ func (wf wrappedFile) Write(b *SharedBuffer) (int, error) {
|
||||
}
|
||||
|
||||
// write lines
|
||||
size, err := file.Write(b.lines[0].data())
|
||||
size, err := file.Write(b.lines[0].data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -165,10 +165,10 @@ func (wf wrappedFile) Write(b *SharedBuffer) (int, error) {
|
||||
if _, err = file.Write(eol); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err = file.Write(l.data()); err != nil {
|
||||
if _, err = file.Write(l.data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
size += len(eol) + len(l.data())
|
||||
size += len(eol) + len(l.data)
|
||||
}
|
||||
|
||||
err = file.Flush()
|
||||
@@ -249,9 +249,10 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error
|
||||
|
||||
if !autoSave && b.Settings["rmtrailingws"].(bool) {
|
||||
for i, l := range b.lines {
|
||||
leftover := strings.TrimRightFunc(l.String(), unicode.IsSpace)
|
||||
linelen := len(l.runes)
|
||||
b.Remove(Loc{len(leftover), i}, Loc{linelen, i})
|
||||
leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
|
||||
|
||||
linelen := util.CharacterCount(l.data)
|
||||
b.Remove(Loc{leftover, i}, Loc{linelen, i})
|
||||
}
|
||||
|
||||
b.RelocateCursors()
|
||||
|
||||
@@ -29,12 +29,16 @@ func (b *Buffer) Serialize() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
buffer := SerializedBuffer{
|
||||
Cursor: b.GetActiveCursor().Loc,
|
||||
ModTime: b.ModTime,
|
||||
}
|
||||
if b.Settings["saveundo"].(bool) {
|
||||
buffer.EventHandler = b.EventHandler
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err := gob.NewEncoder(&buf).Encode(SerializedBuffer{
|
||||
b.EventHandler,
|
||||
b.GetActiveCursor().Loc,
|
||||
b.ModTime,
|
||||
})
|
||||
err := gob.NewEncoder(&buf).Encode(buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -76,7 +80,7 @@ func (b *Buffer) Unserialize() error {
|
||||
b.StartCursor = buffer.Cursor
|
||||
}
|
||||
|
||||
if b.Settings["saveundo"].(bool) {
|
||||
if b.Settings["saveundo"].(bool) && buffer.EventHandler != nil {
|
||||
// We should only use last time's eventhandler if the file wasn't modified by someone else in the meantime
|
||||
if b.ModTime == buffer.ModTime {
|
||||
b.EventHandler = buffer.EventHandler
|
||||
|
||||
@@ -186,11 +186,19 @@ func validateParsedSettings() error {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, e := glob.Compile(k); e != nil {
|
||||
err = errors.New("Error with glob setting " + k + ": " + e.Error())
|
||||
tk := strings.TrimPrefix(k, "glob:")
|
||||
if _, e := glob.Compile(tk); e != nil {
|
||||
err = errors.New("Error with glob setting " + tk + ": " + e.Error())
|
||||
delete(parsedSettings, k)
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(k, "glob:") {
|
||||
// Support non-prefixed glob settings but internally convert
|
||||
// them to prefixed ones for simplicity.
|
||||
delete(parsedSettings, k)
|
||||
k = "glob:" + k
|
||||
parsedSettings[k] = v
|
||||
}
|
||||
for k1, v1 := range v.(map[string]any) {
|
||||
if _, ok := defaults[k1]; ok {
|
||||
if e := verifySetting(k1, v1, defaults[k1]); e != nil {
|
||||
@@ -256,6 +264,9 @@ func ReadSettings() error {
|
||||
func ParsedSettings() map[string]any {
|
||||
s := make(map[string]any)
|
||||
for k, v := range parsedSettings {
|
||||
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
|
||||
continue
|
||||
}
|
||||
s[k] = v
|
||||
}
|
||||
return s
|
||||
@@ -309,8 +320,9 @@ func InitGlobalSettings() error {
|
||||
// Must be called after ReadSettings
|
||||
func UpdatePathGlobLocals(settings map[string]any, path string) {
|
||||
for k, v := range parsedSettings {
|
||||
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && !strings.HasPrefix(k, "ft:") {
|
||||
g, _ := glob.Compile(k)
|
||||
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") && strings.HasPrefix(k, "glob:") {
|
||||
tk := strings.TrimPrefix(k, "glob:")
|
||||
g, _ := glob.Compile(tk)
|
||||
if g.MatchString(path) {
|
||||
for k1, v1 := range v.(map[string]any) {
|
||||
settings[k1] = v1
|
||||
|
||||
@@ -79,7 +79,7 @@ func (w *BufWindow) GetView() *View {
|
||||
return w.View
|
||||
}
|
||||
|
||||
// GetView sets the view.
|
||||
// SetView sets the view.
|
||||
func (w *BufWindow) SetView(view *View) {
|
||||
w.View = view
|
||||
}
|
||||
|
||||
@@ -29,27 +29,11 @@ func isMark(r rune) bool {
|
||||
// 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) {
|
||||
combc, size := DecodeCombinedCharacter(b)
|
||||
return combc[0], combc[1:], 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) {
|
||||
combc, size := DecodeCombinedCharacterInString(str)
|
||||
return combc[0], combc[1:], size
|
||||
}
|
||||
|
||||
// DecodeCombinedCharacter returns the next combined character
|
||||
// from an array of bytes
|
||||
// A character is a rune along with any accompanying combining runes
|
||||
func DecodeCombinedCharacter(b []byte) ([]rune, int) {
|
||||
var combc []rune
|
||||
r, size := utf8.DecodeRune(b)
|
||||
combc = append(combc, r)
|
||||
b = b[size:]
|
||||
c, s := utf8.DecodeRune(b)
|
||||
|
||||
var combc []rune
|
||||
for isMark(c) {
|
||||
combc = append(combc, c)
|
||||
size += s
|
||||
@@ -58,18 +42,17 @@ func DecodeCombinedCharacter(b []byte) ([]rune, int) {
|
||||
c, s = utf8.DecodeRune(b)
|
||||
}
|
||||
|
||||
return combc, size
|
||||
return r, combc, size
|
||||
}
|
||||
|
||||
// DecodeCombinedCharacterInString is the same as DecodeCombinedCharacter
|
||||
// but for strings
|
||||
func DecodeCombinedCharacterInString(str string) ([]rune, int) {
|
||||
var combc []rune
|
||||
// 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)
|
||||
combc = append(combc, r)
|
||||
str = str[size:]
|
||||
c, s := utf8.DecodeRuneInString(str)
|
||||
|
||||
var combc []rune
|
||||
for isMark(c) {
|
||||
combc = append(combc, c)
|
||||
size += s
|
||||
@@ -78,7 +61,7 @@ func DecodeCombinedCharacterInString(str string) ([]rune, int) {
|
||||
c, s = utf8.DecodeRuneInString(str)
|
||||
}
|
||||
|
||||
return combc, size
|
||||
return r, combc, size
|
||||
}
|
||||
|
||||
// CharacterCount returns the number of characters in a byte array
|
||||
|
||||
@@ -413,7 +413,7 @@ func (n *Node) HSplit(bottom bool) uint64 {
|
||||
if !n.IsLeaf() {
|
||||
return 0
|
||||
}
|
||||
if n.Kind == STUndef {
|
||||
if n.parent == nil {
|
||||
n.Kind = STVert
|
||||
}
|
||||
if n.Kind == STVert {
|
||||
@@ -429,13 +429,13 @@ func (n *Node) VSplit(right bool) uint64 {
|
||||
if !n.IsLeaf() {
|
||||
return 0
|
||||
}
|
||||
if n.Kind == STUndef {
|
||||
if n.parent == nil {
|
||||
n.Kind = STHoriz
|
||||
}
|
||||
if n.Kind == STVert {
|
||||
return n.vVSplit(right)
|
||||
if n.Kind == STHoriz {
|
||||
return n.hVSplit(0, right)
|
||||
}
|
||||
return n.hVSplit(0, right)
|
||||
return n.vVSplit(right)
|
||||
}
|
||||
|
||||
// unsplits the child of a split
|
||||
@@ -483,7 +483,20 @@ func (n *Node) Unsplit() bool {
|
||||
// flattens the tree by removing unnecessary intermediate parents that have only one child
|
||||
// and handles the side effect of it
|
||||
func (n *Node) flatten() {
|
||||
if n.parent == nil || len(n.children) != 1 {
|
||||
if len(n.children) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
// Special case for root node
|
||||
if n.parent == nil {
|
||||
*n = *n.children[0]
|
||||
n.parent = nil
|
||||
for _, c := range n.children {
|
||||
c.parent = n
|
||||
}
|
||||
if len(n.children) == 0 {
|
||||
n.Kind = STUndef
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -531,11 +544,19 @@ func (n *Node) flatten() {
|
||||
func (n *Node) String() string {
|
||||
var strf func(n *Node, ident int) string
|
||||
strf = func(n *Node, ident int) string {
|
||||
marker := "|"
|
||||
marker := ""
|
||||
if n.Kind == STHoriz {
|
||||
marker = "-"
|
||||
} else if n.Kind == STVert {
|
||||
marker = "|"
|
||||
}
|
||||
str := fmt.Sprint(strings.Repeat("\t", ident), marker, n.View, n.id)
|
||||
|
||||
var parentId uint64 = 0
|
||||
if n.parent != nil {
|
||||
parentId = n.parent.id
|
||||
}
|
||||
|
||||
str := fmt.Sprint(strings.Repeat("\t", ident), marker, n.View, n.id, parentId)
|
||||
if n.IsLeaf() {
|
||||
str += "🍁"
|
||||
}
|
||||
|
||||
@@ -669,6 +669,21 @@ all files except Go files, and `tabsize` 4 for all files except Ruby files:
|
||||
|
||||
Or similarly you can match with globs:
|
||||
|
||||
```json
|
||||
{
|
||||
"glob:*.go": {
|
||||
"tabstospaces": false
|
||||
},
|
||||
"glob:*.rb": {
|
||||
"tabsize": 2
|
||||
},
|
||||
"tabstospaces": true,
|
||||
"tabsize": 4
|
||||
}
|
||||
```
|
||||
|
||||
You can also omit the `glob:` prefix before globs:
|
||||
|
||||
```json
|
||||
{
|
||||
"*.go": {
|
||||
@@ -681,3 +696,6 @@ Or similarly you can match with globs:
|
||||
"tabsize": 4
|
||||
}
|
||||
```
|
||||
|
||||
But it is generally more recommended to use the `glob:` prefix, as it avoids
|
||||
potential conflicts with option names.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
filetype: git-commit
|
||||
|
||||
detect:
|
||||
filename: "^(.*[\\/])?(COMMIT_EDITMSG|TAG_EDITMSG|MERGE_MSG)$"
|
||||
filename: '^(.*[\\/])?(COMMIT_EDITMSG|TAG_EDITMSG|MERGE_MSG)$'
|
||||
|
||||
rules:
|
||||
# File changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
filetype: git-config
|
||||
|
||||
detect:
|
||||
filename: "git(config|modules)$|\\.git/config$"
|
||||
filename: 'git(config|modules)$|^(.*[\\/])?\.?git[\\/]config$'
|
||||
|
||||
rules:
|
||||
- constant: "\\<(true|false)\\>"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
filetype: git-rebase-todo
|
||||
|
||||
detect:
|
||||
filename: "^(.*[\\/])?git\\-rebase\\-todo$"
|
||||
filename: '^(.*[\\/])?git\-rebase\-todo$'
|
||||
|
||||
rules:
|
||||
# Rebase commands
|
||||
|
||||
69
runtime/syntax/gleam.yaml
Normal file
69
runtime/syntax/gleam.yaml
Normal file
@@ -0,0 +1,69 @@
|
||||
filetype: gleam
|
||||
|
||||
detect:
|
||||
filename: "\\.gleam$"
|
||||
|
||||
rules:
|
||||
- identifier: "\\b[a-z][a-z0-9_]*\\b"
|
||||
|
||||
- statement: "\\b(as|assert|auto|case|const|delegate|derive|echo|else|fn|if|implement|import|let|macro|opaque|panic|pub|test|todo|type|use)\\b"
|
||||
|
||||
- type: "\\b[A-Z][a-zA-Z0-9_]*\\b"
|
||||
|
||||
- type: "\\b(Int|Float|String|Bool|List|Option|Result|BitArray)\\b"
|
||||
|
||||
- constant: "\\b(True|False|Nil)\\b"
|
||||
|
||||
- preproc: "@[a-z][a-z_]*"
|
||||
|
||||
- statement: "(\\|>|->|<-)"
|
||||
- statement: "(\\.\\.|<>)"
|
||||
- statement: "(==|!=|<=\\.|>=\\.|<\\.|>\\.|<=|>=)"
|
||||
- statement: "(&&|\\|\\|)"
|
||||
- statement: "(\\+\\.|-\\.|\\*\\.|/\\.|\\+|-|\\*|/|%)"
|
||||
- statement: "(=|<|>|!|<<|>>)"
|
||||
|
||||
- constant.number: "\\b0b[01](_?[01])*\\b"
|
||||
- constant.number: "\\b0o[0-7](_?[0-7])*\\b"
|
||||
- constant.number: "\\b0x[0-9a-fA-F](_?[0-9a-fA-F])*\\b"
|
||||
- constant.number: "\\b[0-9](_?[0-9])*(\\.[0-9](_?[0-9])*)?([eE][+-]?[0-9](_?[0-9])*)?\\b"
|
||||
|
||||
- default:
|
||||
start: "#\\("
|
||||
end: "\\)"
|
||||
limit-group: special
|
||||
rules:
|
||||
- identifier: "\\b[a-z][a-z0-9_]*\\b"
|
||||
- statement: "\\b(as|assert|auto|case|const|delegate|derive|echo|else|fn|if|implement|import|let|macro|opaque|panic|pub|test|todo|type|use)\\b"
|
||||
- type: "\\b[A-Z][a-zA-Z0-9_]*\\b"
|
||||
- type: "\\b(Int|Float|String|Bool|List|Option|Result|BitArray)\\b"
|
||||
- constant: "\\b(True|False|Nil)\\b"
|
||||
- statement: "(\\|>|->|<-)"
|
||||
- statement: "(\\.\\.|<>)"
|
||||
- statement: "(==|!=|<=\\.|>=\\.|<\\.|>\\.|<=|>=)"
|
||||
- statement: "(&&|\\|\\|)"
|
||||
- statement: "(\\+\\.|-\\.|\\*\\.|/\\.|\\+|-|\\*|/|%)"
|
||||
- statement: "(=|<|>|!|<<|>>)"
|
||||
- constant.number: "\\b0b[01](_?[01])*\\b"
|
||||
- constant.number: "\\b0o[0-7](_?[0-7])*\\b"
|
||||
- constant.number: "\\b0x[0-9a-fA-F](_?[0-9a-fA-F])*\\b"
|
||||
- constant.number: "\\b[0-9](_?[0-9])*(\\.[0-9](_?[0-9])*)?([eE][+-]?[0-9](_?[0-9])*)?\\b"
|
||||
- constant.string:
|
||||
start: '"'
|
||||
end: '"'
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "\\\\."
|
||||
|
||||
- constant.string:
|
||||
start: '"'
|
||||
end: '"'
|
||||
skip: "\\\\."
|
||||
rules:
|
||||
- constant.specialChar: "\\\\."
|
||||
|
||||
- comment:
|
||||
start: "//"
|
||||
end: "$"
|
||||
rules:
|
||||
- todo: "(TODO|FIXME|XXX):?"
|
||||
@@ -1,7 +1,7 @@
|
||||
filetype: nginx
|
||||
|
||||
detect:
|
||||
filename: "nginx.*\\.conf$|\\.nginx$"
|
||||
filename: "nginx.*\\.conf$|\\.nginx$|\\.sub(domain|folder)\\.conf$"
|
||||
header: "^(server|upstream)[a-z ]*\\{$"
|
||||
|
||||
rules:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
filetype: ruby
|
||||
|
||||
detect:
|
||||
filename: "\\.(rb|rake|gemspec)$|^(.*[\\/])?(Gemfile|config.ru|Rakefile|Capfile|Vagrantfile|Guardfile|Appfile|Fastfile|Pluginfile|Podfile|\\.?[Bb]rewfile)$"
|
||||
filename: '\.(rb|rake|gemspec)$|^(.*[\\/])?(Gemfile|config\.ru|Rakefile|Capfile|Vagrantfile|Guardfile|Appfile|Fastfile|Pluginfile|Podfile|\.?[Bb]rewfile)$'
|
||||
header: "^#!.*/(env +)?ruby( |$)"
|
||||
|
||||
rules:
|
||||
|
||||
Reference in New Issue
Block a user