mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-25 18:07:07 +09:00
Merge pull request #3760 from Neko-Box-Coder/MoreCharOptions
Adding indenttabchar, indentspacechar and spacechar options
This commit is contained in:
@@ -72,7 +72,7 @@ var defaultCommonSettings = map[string]any{
|
|||||||
"hltrailingws": false,
|
"hltrailingws": false,
|
||||||
"ignorecase": true,
|
"ignorecase": true,
|
||||||
"incsearch": true,
|
"incsearch": true,
|
||||||
"indentchar": " ",
|
"indentchar": " ", // Deprecated
|
||||||
"keepautoindent": false,
|
"keepautoindent": false,
|
||||||
"matchbrace": true,
|
"matchbrace": true,
|
||||||
"matchbraceleft": true,
|
"matchbraceleft": true,
|
||||||
@@ -90,6 +90,7 @@ var defaultCommonSettings = map[string]any{
|
|||||||
"scrollbar": false,
|
"scrollbar": false,
|
||||||
"scrollmargin": float64(3),
|
"scrollmargin": float64(3),
|
||||||
"scrollspeed": float64(2),
|
"scrollspeed": float64(2),
|
||||||
|
"showchars": "",
|
||||||
"smartpaste": true,
|
"smartpaste": true,
|
||||||
"softwrap": false,
|
"softwrap": false,
|
||||||
"splitbottom": true,
|
"splitbottom": true,
|
||||||
@@ -214,6 +215,7 @@ func validateParsedSettings() error {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := defaults[k]; ok {
|
if _, ok := defaults[k]; ok {
|
||||||
if e := verifySetting(k, v, defaults[k]); e != nil {
|
if e := verifySetting(k, v, defaults[k]); e != nil {
|
||||||
err = e
|
err = e
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package display
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
runewidth "github.com/mattn/go-runewidth"
|
runewidth "github.com/mattn/go-runewidth"
|
||||||
"github.com/micro-editor/tcell/v2"
|
"github.com/micro-editor/tcell/v2"
|
||||||
@@ -450,6 +451,30 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
cursors := b.GetCursors()
|
cursors := b.GetCursors()
|
||||||
|
|
||||||
curStyle := config.DefStyle
|
curStyle := config.DefStyle
|
||||||
|
|
||||||
|
// Parse showchars which is in the format of key1=val1,key2=val2,...
|
||||||
|
spacechars := " "
|
||||||
|
tabchars := b.Settings["indentchar"].(string)
|
||||||
|
var indentspacechars string
|
||||||
|
var indenttabchars string
|
||||||
|
for _, entry := range strings.Split(b.Settings["showchars"].(string), ",") {
|
||||||
|
split := strings.SplitN(entry, "=", 2)
|
||||||
|
if len(split) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key, val := split[0], split[1]
|
||||||
|
switch key {
|
||||||
|
case "space":
|
||||||
|
spacechars = val
|
||||||
|
case "tab":
|
||||||
|
tabchars = val
|
||||||
|
case "ispace":
|
||||||
|
indentspacechars = val
|
||||||
|
case "itab":
|
||||||
|
indenttabchars = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for ; vloc.Y < w.bufHeight; vloc.Y++ {
|
for ; vloc.Y < w.bufHeight; vloc.Y++ {
|
||||||
vloc.X = 0
|
vloc.X = 0
|
||||||
|
|
||||||
@@ -494,133 +519,171 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
}
|
}
|
||||||
bloc.X = bslice
|
bloc.X = bslice
|
||||||
|
|
||||||
draw := func(r rune, combc []rune, style tcell.Style, highlight bool, showcursor bool) {
|
// returns the rune to be drawn, style of it and if the bg should be preserved
|
||||||
if nColsBeforeStart <= 0 && vloc.Y >= 0 {
|
getRuneStyle := func(r rune, style tcell.Style, showoffset int, linex int, isplaceholder bool) (rune, tcell.Style, bool) {
|
||||||
if highlight {
|
if nColsBeforeStart > 0 || vloc.Y < 0 || isplaceholder {
|
||||||
if w.Buf.HighlightSearch && w.Buf.SearchMatch(bloc) {
|
return r, style, false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mb := range matchingBraces {
|
||||||
|
if mb.X == bloc.X && mb.Y == bloc.Y {
|
||||||
|
if b.Settings["matchbracestyle"].(string) == "highlight" {
|
||||||
|
if s, ok := config.Colorscheme["match-brace"]; ok {
|
||||||
|
return r, s, false
|
||||||
|
} else {
|
||||||
|
return r, style.Reverse(true), false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return r, style.Underline(true), false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r != '\t' && r != ' ' {
|
||||||
|
return r, style, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var indentrunes []rune
|
||||||
|
switch r {
|
||||||
|
case '\t':
|
||||||
|
if bloc.X < leadingwsEnd && indenttabchars != "" {
|
||||||
|
indentrunes = []rune(indenttabchars)
|
||||||
|
} else {
|
||||||
|
indentrunes = []rune(tabchars)
|
||||||
|
}
|
||||||
|
case ' ':
|
||||||
|
if linex%tabsize == 0 && bloc.X < leadingwsEnd && indentspacechars != "" {
|
||||||
|
indentrunes = []rune(indentspacechars)
|
||||||
|
} else {
|
||||||
|
indentrunes = []rune(spacechars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var drawrune rune
|
||||||
|
if showoffset < len(indentrunes) {
|
||||||
|
drawrune = indentrunes[showoffset]
|
||||||
|
} else {
|
||||||
|
// use space if no showchars or after we showed showchars
|
||||||
|
drawrune = ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := config.Colorscheme["indent-char"]; ok {
|
||||||
|
fg, _, _ := s.Decompose()
|
||||||
|
style = style.Foreground(fg)
|
||||||
|
}
|
||||||
|
|
||||||
|
preservebg := false
|
||||||
|
if b.Settings["hltaberrors"].(bool) && bloc.X < leadingwsEnd {
|
||||||
|
if s, ok := config.Colorscheme["tab-error"]; ok {
|
||||||
|
if b.Settings["tabstospaces"].(bool) && r == '\t' {
|
||||||
|
fg, _, _ := s.Decompose()
|
||||||
|
style = style.Background(fg)
|
||||||
|
preservebg = true
|
||||||
|
} else if !b.Settings["tabstospaces"].(bool) && r == ' ' {
|
||||||
|
fg, _, _ := s.Decompose()
|
||||||
|
style = style.Background(fg)
|
||||||
|
preservebg = 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)
|
||||||
|
preservebg = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return drawrune, style, preservebg
|
||||||
|
}
|
||||||
|
|
||||||
|
draw := func(r rune, combc []rune, style tcell.Style, highlight bool, showcursor bool, preservebg bool) {
|
||||||
|
defer func() {
|
||||||
|
if nColsBeforeStart <= 0 {
|
||||||
|
vloc.X++
|
||||||
|
}
|
||||||
|
nColsBeforeStart--
|
||||||
|
}()
|
||||||
|
|
||||||
|
if nColsBeforeStart > 0 || vloc.Y < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if highlight {
|
||||||
|
if w.Buf.HighlightSearch && w.Buf.SearchMatch(bloc) {
|
||||||
|
style = config.DefStyle.Reverse(true)
|
||||||
|
if s, ok := config.Colorscheme["hlsearch"]; ok {
|
||||||
|
style = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, origBg, _ := style.Decompose()
|
||||||
|
_, defBg, _ := config.DefStyle.Decompose()
|
||||||
|
|
||||||
|
// syntax or hlsearch highlighting with non-default background takes precedence
|
||||||
|
// over cursor-line and color-column
|
||||||
|
if !preservebg && origBg != defBg {
|
||||||
|
preservebg = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cursors {
|
||||||
|
if c.HasSelection() &&
|
||||||
|
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
|
||||||
|
bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
|
||||||
|
// The current character is selected
|
||||||
style = config.DefStyle.Reverse(true)
|
style = config.DefStyle.Reverse(true)
|
||||||
if s, ok := config.Colorscheme["hlsearch"]; ok {
|
|
||||||
|
if s, ok := config.Colorscheme["selection"]; ok {
|
||||||
style = s
|
style = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, origBg, _ := style.Decompose()
|
if b.Settings["cursorline"].(bool) && w.active && !preservebg &&
|
||||||
_, defBg, _ := config.DefStyle.Decompose()
|
!c.HasSelection() && c.Y == bloc.Y {
|
||||||
|
if s, ok := config.Colorscheme["cursor-line"]; ok {
|
||||||
// syntax or hlsearch highlighting with non-default background takes precedence
|
|
||||||
// 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]) ||
|
|
||||||
bloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {
|
|
||||||
// The current character is selected
|
|
||||||
style = config.DefStyle.Reverse(true)
|
|
||||||
|
|
||||||
if s, ok := config.Colorscheme["selection"]; ok {
|
|
||||||
style = s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Settings["cursorline"].(bool) && w.active && !dontOverrideBackground &&
|
|
||||||
!c.HasSelection() && c.Y == bloc.Y {
|
|
||||||
if s, ok := config.Colorscheme["cursor-line"]; ok {
|
|
||||||
fg, _, _ := s.Decompose()
|
|
||||||
style = style.Background(fg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range b.Messages {
|
|
||||||
if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
|
|
||||||
bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
|
|
||||||
style = style.Underline(true)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r == '\t' {
|
|
||||||
indentrunes := []rune(b.Settings["indentchar"].(string))
|
|
||||||
// if empty indentchar settings, use space
|
|
||||||
if len(indentrunes) == 0 {
|
|
||||||
indentrunes = []rune{' '}
|
|
||||||
}
|
|
||||||
|
|
||||||
r = indentrunes[0]
|
|
||||||
if s, ok := config.Colorscheme["indent-char"]; ok && r != ' ' {
|
|
||||||
fg, _, _ := s.Decompose()
|
|
||||||
style = style.Foreground(fg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s, ok := config.Colorscheme["color-column"]; ok {
|
|
||||||
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !dontOverrideBackground {
|
|
||||||
fg, _, _ := s.Decompose()
|
fg, _, _ := s.Decompose()
|
||||||
style = style.Background(fg)
|
style = style.Background(fg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, mb := range matchingBraces {
|
for _, m := range b.Messages {
|
||||||
if mb.X == bloc.X && mb.Y == bloc.Y {
|
if bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||
|
||||||
if b.Settings["matchbracestyle"].(string) == "highlight" {
|
bloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {
|
||||||
if s, ok := config.Colorscheme["match-brace"]; ok {
|
style = style.Underline(true)
|
||||||
style = s
|
break
|
||||||
} else {
|
|
||||||
style = style.Reverse(true)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
style = style.Underline(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)
|
if s, ok := config.Colorscheme["color-column"]; ok {
|
||||||
|
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !preservebg {
|
||||||
if showcursor {
|
fg, _, _ := s.Decompose()
|
||||||
for _, c := range cursors {
|
style = style.Background(fg)
|
||||||
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
|
|
||||||
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if nColsBeforeStart <= 0 {
|
|
||||||
vloc.X++
|
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)
|
||||||
|
|
||||||
|
if showcursor {
|
||||||
|
for _, c := range cursors {
|
||||||
|
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
|
||||||
|
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nColsBeforeStart--
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wrap := func() {
|
wrap := func() {
|
||||||
@@ -668,6 +731,7 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
|
|
||||||
width := 0
|
width := 0
|
||||||
|
|
||||||
|
linex := totalwidth
|
||||||
switch r {
|
switch r {
|
||||||
case '\t':
|
case '\t':
|
||||||
ts := tabsize - (totalwidth % tabsize)
|
ts := tabsize - (totalwidth % tabsize)
|
||||||
@@ -692,7 +756,7 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
// If a word (or just a wide rune) does not fit in the window
|
// If a word (or just a wide rune) does not fit in the window
|
||||||
if vloc.X+wordwidth > maxWidth && vloc.X > w.gutterOffset {
|
if vloc.X+wordwidth > maxWidth && vloc.X > w.gutterOffset {
|
||||||
for vloc.X < maxWidth {
|
for vloc.X < maxWidth {
|
||||||
draw(' ', nil, config.DefStyle, false, false)
|
draw(' ', nil, config.DefStyle, false, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We either stop or we wrap to draw the word in the next line
|
// We either stop or we wrap to draw the word in the next line
|
||||||
@@ -708,18 +772,17 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range word {
|
for _, r := range word {
|
||||||
draw(r.r, r.combc, r.style, true, true)
|
drawrune, drawstyle, preservebg := getRuneStyle(r.r, r.style, 0, linex, false)
|
||||||
|
draw(drawrune, r.combc, drawstyle, true, true, preservebg)
|
||||||
|
|
||||||
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
|
// Draw extra characters for tabs or wide runes
|
||||||
if r.width > 1 {
|
for i := 1; i < r.width; i++ {
|
||||||
char := ' '
|
if r.r == '\t' {
|
||||||
if r.r != '\t' {
|
drawrune, drawstyle, preservebg = getRuneStyle('\t', r.style, i, linex+i, false)
|
||||||
char = '@'
|
} else {
|
||||||
}
|
drawrune, drawstyle, preservebg = getRuneStyle(' ', r.style, i, linex+i, true)
|
||||||
|
|
||||||
for i := 1; i < r.width; i++ {
|
|
||||||
draw(char, nil, r.style, true, false)
|
|
||||||
}
|
}
|
||||||
|
draw(drawrune, nil, drawstyle, true, false, preservebg)
|
||||||
}
|
}
|
||||||
bloc.X++
|
bloc.X++
|
||||||
}
|
}
|
||||||
@@ -764,7 +827,8 @@ func (w *BufWindow) displayBuffer() {
|
|||||||
|
|
||||||
if vloc.X != maxWidth {
|
if vloc.X != maxWidth {
|
||||||
// Display newline within a selection
|
// Display newline within a selection
|
||||||
draw(' ', nil, config.DefStyle, true, true)
|
drawrune, drawstyle, preservebg := getRuneStyle(' ', config.DefStyle, 0, totalwidth, true)
|
||||||
|
draw(drawrune, nil, drawstyle, true, true, preservebg)
|
||||||
}
|
}
|
||||||
|
|
||||||
bloc.X = w.StartCol
|
bloc.X = w.StartCol
|
||||||
|
|||||||
@@ -203,12 +203,8 @@ Here are the available options:
|
|||||||
|
|
||||||
default value: `true`
|
default value: `true`
|
||||||
|
|
||||||
* `indentchar`: sets the indentation character. This will not be inserted into
|
* `indentchar`: sets the character to be shown to display tab characters.
|
||||||
files; it is only a visual indicator that whitespace is present. If set to a
|
This option is **deprecated**, use the `tab` key in `showchars` option instead.
|
||||||
printing character, it functions as a subset of the "show invisibles"
|
|
||||||
setting available in many other text editors. The color of this character is
|
|
||||||
determined by the `indent-char` field in the current theme rather than the
|
|
||||||
default text color.
|
|
||||||
|
|
||||||
default value: ` ` (space)
|
default value: ` ` (space)
|
||||||
|
|
||||||
@@ -386,6 +382,25 @@ Here are the available options:
|
|||||||
|
|
||||||
default value: `2`
|
default value: `2`
|
||||||
|
|
||||||
|
* `showchars`: sets what characters to be shown to display various invisible
|
||||||
|
characters in the file. The characters shown will not be inserted into files.
|
||||||
|
This option is specified in the form of `key1=value1,key2=value2,...`.
|
||||||
|
|
||||||
|
Here are the list of keys:
|
||||||
|
- `space`: space characters
|
||||||
|
- `tab`: tab characters. If set, overrides the `indentchar` option.
|
||||||
|
- `ispace`: space characters at indent position before the first visible
|
||||||
|
character in a line. If this is not set, `space` will be shown
|
||||||
|
instead.
|
||||||
|
- `itab`: tab characters before the first visible character in a line.
|
||||||
|
If this is not set, `tab` will be shown instead.
|
||||||
|
An example of this option value could be `tab=>,space=.,itab=|>,ispace=|`
|
||||||
|
|
||||||
|
The color of the shown character is determined by the `indent-char`
|
||||||
|
field in the current theme rather than the default text color.
|
||||||
|
|
||||||
|
default value: ``
|
||||||
|
|
||||||
* `smartpaste`: add leading whitespace when pasting multiple lines.
|
* `smartpaste`: add leading whitespace when pasting multiple lines.
|
||||||
This will attempt to preserve the current indentation level when pasting an
|
This will attempt to preserve the current indentation level when pasting an
|
||||||
unindented block.
|
unindented block.
|
||||||
@@ -590,6 +605,7 @@ so that you can see what the formatting should look like.
|
|||||||
"scrollbarchar": "|",
|
"scrollbarchar": "|",
|
||||||
"scrollmargin": 3,
|
"scrollmargin": 3,
|
||||||
"scrollspeed": 2,
|
"scrollspeed": 2,
|
||||||
|
"showchars": "",
|
||||||
"smartpaste": true,
|
"smartpaste": true,
|
||||||
"softwrap": false,
|
"softwrap": false,
|
||||||
"splitbottom": true,
|
"splitbottom": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user