Merge pull request #1897 from dmaluka/wserrors
New feature: Highlighting whitespace errors
This commit is contained in:
commit
7b718cb87c
34 changed files with 205 additions and 2 deletions
|
@ -68,6 +68,7 @@ You can also check out the website for Micro at https://micro-editor.github.io.
|
|||
- Small and simple.
|
||||
- Easily configurable.
|
||||
- Macros.
|
||||
- Smart highlighting of trailing whitespace and tab vs space errors.
|
||||
- Common editor features such as undo/redo, line numbers, Unicode support, soft wrapping, …
|
||||
|
||||
## Installation
|
||||
|
|
|
@ -509,6 +509,14 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
|
|||
InfoBar.ClearGutter()
|
||||
}
|
||||
}
|
||||
|
||||
cursors := h.Buf.GetCursors()
|
||||
for _, c := range cursors {
|
||||
if c.NewTrailingWsY != c.Y && (!c.HasSelection() ||
|
||||
(c.NewTrailingWsY != c.CurSelection[0].Y && c.NewTrailingWsY != c.CurSelection[1].Y)) {
|
||||
c.NewTrailingWsY = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bindings returns the current bindings tree for this buffer.
|
||||
|
|
|
@ -30,6 +30,11 @@ type Cursor struct {
|
|||
// to know what the original selection was
|
||||
OrigSelection [2]Loc
|
||||
|
||||
// The line number where a new trailing whitespace has been added
|
||||
// or -1 if there is no new trailing whitespace at this cursor.
|
||||
// This is used for checking if a trailing whitespace should be highlighted
|
||||
NewTrailingWsY int
|
||||
|
||||
// Which cursor index is this (for multiple cursors)
|
||||
Num int
|
||||
}
|
||||
|
@ -38,6 +43,8 @@ func NewCursor(b *Buffer, l Loc) *Cursor {
|
|||
c := &Cursor{
|
||||
buf: b,
|
||||
Loc: l,
|
||||
|
||||
NewTrailingWsY: -1,
|
||||
}
|
||||
c.StoreVisualX()
|
||||
return c
|
||||
|
|
|
@ -106,6 +106,10 @@ func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
|
|||
c.Relocate()
|
||||
c.LastVisualX = c.GetVisualX()
|
||||
}
|
||||
|
||||
if useUndo {
|
||||
eh.updateTrailingWs(t)
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteTextEvent runs a text event
|
||||
|
@ -290,6 +294,7 @@ func (eh *EventHandler) UndoOneEvent() {
|
|||
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
|
||||
t.C = *eh.cursors[teCursor.Num]
|
||||
eh.cursors[teCursor.Num].Goto(teCursor)
|
||||
eh.cursors[teCursor.Num].NewTrailingWsY = teCursor.NewTrailingWsY
|
||||
} else {
|
||||
teCursor.Num = -1
|
||||
}
|
||||
|
@ -333,6 +338,7 @@ func (eh *EventHandler) RedoOneEvent() {
|
|||
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
|
||||
t.C = *eh.cursors[teCursor.Num]
|
||||
eh.cursors[teCursor.Num].Goto(teCursor)
|
||||
eh.cursors[teCursor.Num].NewTrailingWsY = teCursor.NewTrailingWsY
|
||||
} else {
|
||||
teCursor.Num = -1
|
||||
}
|
||||
|
@ -342,3 +348,58 @@ func (eh *EventHandler) RedoOneEvent() {
|
|||
|
||||
eh.UndoStack.Push(t)
|
||||
}
|
||||
|
||||
// updateTrailingWs updates the cursor's trailing whitespace status after a text event
|
||||
func (eh *EventHandler) updateTrailingWs(t *TextEvent) {
|
||||
if len(t.Deltas) != 1 {
|
||||
return
|
||||
}
|
||||
text := t.Deltas[0].Text
|
||||
start := t.Deltas[0].Start
|
||||
end := t.Deltas[0].End
|
||||
|
||||
c := eh.cursors[eh.active]
|
||||
isEol := func(loc Loc) bool {
|
||||
return loc.X == util.CharacterCount(eh.buf.LineBytes(loc.Y))
|
||||
}
|
||||
if t.EventType == TextEventInsert && c.Loc == end && isEol(end) {
|
||||
var addedTrailingWs bool
|
||||
addedAfterWs := false
|
||||
addedWsOnly := false
|
||||
if start.Y == end.Y {
|
||||
addedTrailingWs = util.HasTrailingWhitespace(text)
|
||||
addedWsOnly = util.IsBytesWhitespace(text)
|
||||
addedAfterWs = start.X > 0 && util.IsWhitespace(c.buf.RuneAt(Loc{start.X - 1, start.Y}))
|
||||
} else {
|
||||
lastnl := bytes.LastIndex(text, []byte{'\n'})
|
||||
addedTrailingWs = util.HasTrailingWhitespace(text[lastnl+1:])
|
||||
}
|
||||
|
||||
if addedTrailingWs && !(addedAfterWs && addedWsOnly) {
|
||||
c.NewTrailingWsY = c.Y
|
||||
} else if !addedTrailingWs {
|
||||
c.NewTrailingWsY = -1
|
||||
}
|
||||
} else if t.EventType == TextEventRemove && c.Loc == start && isEol(start) {
|
||||
removedAfterWs := util.HasTrailingWhitespace(eh.buf.LineBytes(start.Y))
|
||||
var removedWsOnly bool
|
||||
if start.Y == end.Y {
|
||||
removedWsOnly = util.IsBytesWhitespace(text)
|
||||
} else {
|
||||
firstnl := bytes.Index(text, []byte{'\n'})
|
||||
removedWsOnly = util.IsBytesWhitespace(text[:firstnl])
|
||||
}
|
||||
|
||||
if removedAfterWs && !removedWsOnly {
|
||||
c.NewTrailingWsY = c.Y
|
||||
} else if !removedAfterWs {
|
||||
c.NewTrailingWsY = -1
|
||||
}
|
||||
} else if c.NewTrailingWsY != -1 && start.Y != end.Y && c.Loc.GreaterThan(start) &&
|
||||
((t.EventType == TextEventInsert && c.Y == c.NewTrailingWsY+(end.Y-start.Y)) ||
|
||||
(t.EventType == TextEventRemove && c.Y == c.NewTrailingWsY-(end.Y-start.Y))) {
|
||||
// The cursor still has its new trailingws
|
||||
// but its line number was shifted by insert or remove of lines above
|
||||
c.NewTrailingWsY = c.Y
|
||||
}
|
||||
}
|
||||
|
|
|
@ -289,6 +289,8 @@ var defaultCommonSettings = map[string]interface{}{
|
|||
"fileformat": defaultFileFormat(),
|
||||
"filetype": "unknown",
|
||||
"hlsearch": false,
|
||||
"hltaberrors": false,
|
||||
"hltrailingws": false,
|
||||
"incsearch": true,
|
||||
"ignorecase": true,
|
||||
"indentchar": " ",
|
||||
|
|
|
@ -495,6 +495,12 @@ func (w *BufWindow) displayBuffer() {
|
|||
vloc.X = w.gutterOffset
|
||||
}
|
||||
|
||||
bline := b.LineBytes(bloc.Y)
|
||||
blineLen := util.CharacterCount(bline)
|
||||
|
||||
leadingwsEnd := len(util.GetLeadingWhitespace(bline))
|
||||
trailingwsStart := blineLen - util.CharacterCount(util.GetTrailingWhitespace(bline))
|
||||
|
||||
line, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y)
|
||||
if startStyle != nil {
|
||||
curStyle = *startStyle
|
||||
|
@ -518,6 +524,37 @@ func (w *BufWindow) displayBuffer() {
|
|||
// over cursor-line and color-column
|
||||
dontOverrideBackground := origBg != defBg
|
||||
|
||||
if b.Settings["hltaberrors"].(bool) {
|
||||
if s, ok := config.Colorscheme["tab-error"]; ok {
|
||||
isTab := (r == '\t') || (r == ' ' && !showcursor)
|
||||
if (b.Settings["tabstospaces"].(bool) && isTab) ||
|
||||
(!b.Settings["tabstospaces"].(bool) && bloc.X < leadingwsEnd && r == ' ' && !isTab) {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
dontOverrideBackground = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if b.Settings["hltrailingws"].(bool) {
|
||||
if s, ok := config.Colorscheme["trailingws"]; ok {
|
||||
if bloc.X >= trailingwsStart && bloc.X < blineLen {
|
||||
hl := true
|
||||
for _, c := range cursors {
|
||||
if c.NewTrailingWsY == bloc.Y {
|
||||
hl = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if hl {
|
||||
fg, _, _ := s.Decompose()
|
||||
style = style.Background(fg)
|
||||
dontOverrideBackground = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range cursors {
|
||||
if c.HasSelection() &&
|
||||
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/blang/semver"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
|
@ -363,6 +364,28 @@ func GetLeadingWhitespace(b []byte) []byte {
|
|||
return ws
|
||||
}
|
||||
|
||||
// GetTrailingWhitespace returns the trailing whitespace of the given byte array
|
||||
func GetTrailingWhitespace(b []byte) []byte {
|
||||
ws := []byte{}
|
||||
for len(b) > 0 {
|
||||
r, size := utf8.DecodeLastRune(b)
|
||||
if IsWhitespace(r) {
|
||||
ws = append([]byte(string(r)), ws...)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
b = b[:len(b)-size]
|
||||
}
|
||||
return ws
|
||||
}
|
||||
|
||||
// HasTrailingWhitespace returns true if the given byte array ends with a whitespace
|
||||
func HasTrailingWhitespace(b []byte) bool {
|
||||
r, _ := utf8.DecodeLastRune(b)
|
||||
return IsWhitespace(r)
|
||||
}
|
||||
|
||||
// IntOpt turns a float64 setting to an int
|
||||
func IntOpt(opt interface{}) int {
|
||||
return int(opt.(float64))
|
||||
|
|
|
@ -29,3 +29,5 @@ color-link color-column "#2D2F31"
|
|||
#color-link type.extended "default"
|
||||
#Plain brackets
|
||||
color-link match-brace "#1D1F21,#62B1FE"
|
||||
color-link tab-error "#D75F5F"
|
||||
color-link trailingws "#D75F5F"
|
||||
|
|
|
@ -27,3 +27,5 @@ color-link color-column "254"
|
|||
color-link type.extended "241,231"
|
||||
color-link symbol.brackets "241,231"
|
||||
color-link match-brace "231,28"
|
||||
color-link tab-error "210"
|
||||
color-link trailingws "210"
|
||||
|
|
|
@ -43,3 +43,5 @@ color-link color-column "cyan"
|
|||
color-link underlined.url "underline blue, white"
|
||||
color-link divider "blue"
|
||||
color-link match-brace "black,cyan"
|
||||
color-link tab-error "brightred"
|
||||
color-link trailingws "brightred"
|
||||
|
|
|
@ -39,3 +39,5 @@ color-link constant.bool "bold #55ffff"
|
|||
color-link constant.bool.true "bold #85ff85"
|
||||
color-link constant.bool.false "bold #ff8585"
|
||||
color-link match-brace "#1e2124,#55ffff"
|
||||
color-link tab-error "#d75f5f"
|
||||
color-link trailingws "#d75f5f"
|
||||
|
|
|
@ -30,3 +30,5 @@ color-link type.extended "default"
|
|||
#color-link symbol.brackets "default"
|
||||
color-link symbol.tag "#AE81FF,#242424"
|
||||
color-link match-brace "#242424,#7A9EC2"
|
||||
color-link tab-error "#D75F5F"
|
||||
color-link trailingws "#D75F5F"
|
||||
|
|
|
@ -30,3 +30,5 @@ color-link type.extended "default"
|
|||
#color-link symbol.brackets "default"
|
||||
color-link symbol.tag "#AE81FF,#282828"
|
||||
color-link match-brace "#282828,#AE81FF"
|
||||
color-link tab-error "#D75F5F"
|
||||
color-link trailingws "#D75F5F"
|
||||
|
|
|
@ -44,3 +44,6 @@ color-link color-column "#44475A"
|
|||
color-link type.extended "default"
|
||||
|
||||
color-link match-brace "#282A36,#FF79C6"
|
||||
|
||||
color-link tab-error "#D75F5F"
|
||||
color-link trailingws "#D75F5F"
|
||||
|
|
|
@ -34,3 +34,5 @@ color-link type.keyword "bold #5aaae6,#001e28"
|
|||
color-link type.extended "#ffffff,#001e28"
|
||||
color-link underlined "#608b4e,#001e28"
|
||||
color-link match-brace "#001e28,#5aaae6"
|
||||
color-link tab-error "#d75f5f"
|
||||
color-link trailingws "#d75f5f"
|
||||
|
|
|
@ -34,3 +34,5 @@ color-link type.keyword "bold #780050,#f0f0f0"
|
|||
color-link type.extended "#000000,#f0f0f0"
|
||||
color-link underlined "#3f7f5f,#f0f0f0"
|
||||
color-link match-brace "#f0f0f0,#780050"
|
||||
color-link tab-error "#ff8787"
|
||||
color-link trailingws "#ff8787"
|
||||
|
|
|
@ -34,3 +34,5 @@ color-link type.keyword "bold #5aaae6,#2d0023"
|
|||
color-link type.extended "#ffffff,#2d0023"
|
||||
color-link underlined "#886484,#2d0023"
|
||||
color-link match-brace "#2d0023,#5aaae6"
|
||||
color-link tab-error "#d75f5f"
|
||||
color-link trailingws "#d75f5f"
|
||||
|
|
|
@ -25,3 +25,5 @@ color-link diff-deleted "red"
|
|||
color-link gutter-error ",red"
|
||||
color-link gutter-warning "red"
|
||||
color-link match-brace "black,cyan"
|
||||
color-link tab-error "brightred"
|
||||
color-link trailingws "brightred"
|
||||
|
|
|
@ -25,3 +25,5 @@ color-link cursor-line "#091F2E"
|
|||
color-link color-column "#11151C"
|
||||
color-link symbol "#99D1CE,#0C1014"
|
||||
color-link match-brace "#0C1014,#D26937"
|
||||
color-link tab-error "#D75F5F"
|
||||
color-link trailingws "#D75F5F"
|
||||
|
|
|
@ -25,3 +25,5 @@ color-link color-column "#79740e"
|
|||
color-link statusline "#ebdbb2,#665c54"
|
||||
color-link tabbar "#ebdbb2,#665c54"
|
||||
color-link match-brace "#282828,#d3869b"
|
||||
color-link tab-error "#d75f5f"
|
||||
color-link trailingws "#d75f5f"
|
||||
|
|
|
@ -22,3 +22,5 @@ color-link color-column "237"
|
|||
color-link statusline "223,237"
|
||||
color-link tabbar "223,237"
|
||||
color-link match-brace "235,72"
|
||||
color-link tab-error "167"
|
||||
color-link trailingws "167"
|
||||
|
|
|
@ -31,3 +31,5 @@ color-link todo "bold #C792EA,#263238"
|
|||
color-link type "#FFCB6B,#263238"
|
||||
color-link underlined "underline #EEFFFF,#263238"
|
||||
color-link match-brace "#263238,#C792EA"
|
||||
color-link tab-error "#D75F5F"
|
||||
color-link trailingws "#D75F5F"
|
||||
|
|
|
@ -24,3 +24,5 @@ color-link gutter-warning "#E6DB74"
|
|||
color-link cursor-line "#323232"
|
||||
color-link color-column "#323232"
|
||||
color-link match-brace "#1D0000,#AE81FF"
|
||||
color-link tab-error "#D75F5F"
|
||||
color-link trailingws "#D75F5F"
|
||||
|
|
|
@ -30,3 +30,5 @@ color-link type.extended "default"
|
|||
#color-link symbol.brackets "default"
|
||||
color-link symbol.tag "#AE81FF,#282828"
|
||||
color-link match-brace "#282828,#AE81FF"
|
||||
color-link tab-error "#D75F5F"
|
||||
color-link trailingws "#D75F5F"
|
||||
|
|
|
@ -35,3 +35,5 @@ color-link type "#66D9EF"
|
|||
color-link type.keyword "#C678DD"
|
||||
color-link underlined "#8996A8"
|
||||
color-link match-brace "#21252C,#C678DD"
|
||||
color-link tab-error "#D75F5F"
|
||||
color-link trailingws "#D75F5F"
|
||||
|
|
|
@ -28,6 +28,8 @@ color-link tabbar "bold #b1b1b1,#232323"
|
|||
color-link cursor-line "#353535"
|
||||
color-link color-column "#353535"
|
||||
color-link space "underline #e6e1dc,#2b2b2b"
|
||||
color-link tab-error "#d75f5f"
|
||||
color-link trailingws "#d75f5f"
|
||||
|
||||
#the Python syntax definition are wrong. This is not how you should do decorators!
|
||||
color-link brightgreen "#edb753,#2b2b2b"
|
||||
|
|
|
@ -29,3 +29,5 @@ color-link symbol.brackets "default"
|
|||
#Color shebangs the comment color
|
||||
color-link preproc.shebang "comment"
|
||||
color-link match-brace ",magenta"
|
||||
color-link tab-error "brightred"
|
||||
color-link trailingws "brightred"
|
||||
|
|
|
@ -27,3 +27,5 @@ color-link color-column "#003541"
|
|||
color-link type.extended "#839496,#002833"
|
||||
color-link symbol.brackets "#839496,#002833"
|
||||
color-link match-brace "#002833,#268BD2"
|
||||
color-link tab-error "#D75F5F"
|
||||
color-link trailingws "#D75F5F"
|
||||
|
|
|
@ -26,3 +26,5 @@ color-link color-column "black"
|
|||
color-link type.extended "default"
|
||||
color-link symbol.brackets "default"
|
||||
color-link match-brace ",blue"
|
||||
color-link tab-error "brightred"
|
||||
color-link trailingws "brightred"
|
||||
|
|
|
@ -25,3 +25,5 @@ color-link cursor-line "229"
|
|||
#color-link color-column "196"
|
||||
color-link current-line-number "246"
|
||||
color-line match-brace "230,22"
|
||||
color-link tab-error "210"
|
||||
color-link trailingws "210"
|
||||
|
|
|
@ -36,3 +36,5 @@ color-link type "#F9EE98"
|
|||
color-link type.keyword "#CDA869"
|
||||
color-link underlined "#8996A8"
|
||||
color-link match-brace "#141414,#E0C589"
|
||||
color-link tab-error "#D75F5F"
|
||||
color-link trailingws "#D75F5F"
|
||||
|
|
|
@ -26,3 +26,5 @@ color-link cursor-line "238"
|
|||
color-link color-column "238"
|
||||
color-link current-line-number "188,237"
|
||||
color-link match-brace "237,223"
|
||||
color-link tab-error "167"
|
||||
color-link trailingws "167"
|
||||
|
|
|
@ -174,6 +174,19 @@ Here are the available options:
|
|||
|
||||
default value: `false`
|
||||
|
||||
* `hltaberrors`: highlight tabs when spaces are expected, and spaces when tabs
|
||||
are expected. More precisely: if `tabstospaces` option is on, highlight
|
||||
all tab characters; if `tabstospaces` is off, highlight space characters
|
||||
in the initial indent part of the line.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `hltrailingws`: highlight trailing whitespaces at ends of lines. Note that
|
||||
it doesn't highlight newly added trailing whitespaces that naturally occur
|
||||
while typing text. It highlights only nasty forgotten trailing whitespaces.
|
||||
|
||||
default value: `false`
|
||||
|
||||
* `incsearch`: enable incremental search in "Find" prompt (matching as you type).
|
||||
|
||||
default value: `true`
|
||||
|
|
|
@ -50,11 +50,11 @@ function preInsertNewline(bp)
|
|||
for i = 1, #autoNewlinePairs do
|
||||
if curRune == charAt(autoNewlinePairs[i], 1) then
|
||||
if nextRune == charAt(autoNewlinePairs[i], 2) then
|
||||
bp:InsertNewline()
|
||||
bp:InsertTab()
|
||||
bp.Buf:Insert(-bp.Cursor.Loc, "\n" .. ws)
|
||||
bp:StartOfLine()
|
||||
bp:CursorLeft()
|
||||
bp:InsertNewline()
|
||||
bp:InsertTab()
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue