Implement word wrapping

Fixes #264
Fixes #1644
This commit is contained in:
Dmitry Maluka
2021-03-17 22:34:30 +01:00
parent f2613eeb3b
commit 965e43ebf1
4 changed files with 119 additions and 24 deletions

View File

@@ -297,6 +297,7 @@ var defaultCommonSettings = map[string]interface{}{
"tabsize": float64(4),
"tabstospaces": false,
"useprimary": true,
"wordwrap": false,
}
func GetInfoBarOffset() int {

View File

@@ -420,6 +420,8 @@ func (w *BufWindow) displayBuffer() {
}
softwrap := b.Settings["softwrap"].(bool)
wordwrap := softwrap && b.Settings["wordwrap"].(bool)
tabsize := util.IntOpt(b.Settings["tabsize"])
colorcolumn := util.IntOpt(b.Settings["colorcolumn"])
@@ -571,15 +573,31 @@ func (w *BufWindow) displayBuffer() {
}
}
type glyph struct {
r rune
combc []rune
style tcell.Style
width int
}
var word []glyph
if wordwrap {
word = make([]glyph, 0, w.bufWidth)
} else {
word = make([]glyph, 0, 1)
}
wordwidth := 0
totalwidth := w.StartCol - nColsBeforeStart
for len(line) > 0 {
r, combc, size := util.DecodeCharacter(line)
line = line[size:]
curStyle, _ = w.getStyle(curStyle, bloc)
loc := buffer.Loc{X: bloc.X + len(word), Y: bloc.Y}
curStyle, _ = w.getStyle(curStyle, loc)
width := 0
char := ' '
switch r {
case '\t':
ts := tabsize - (totalwidth % tabsize)
@@ -587,17 +605,27 @@ func (w *BufWindow) displayBuffer() {
totalwidth += ts
default:
width = runewidth.RuneWidth(r)
char = '@'
totalwidth += width
}
// If a wide rune does not fit in the window
if vloc.X+width > maxWidth && vloc.X > w.gutterOffset {
word = append(word, glyph{r, combc, curStyle, width})
wordwidth += width
// Collect a complete word to know its width.
// If wordwrap is off, every single character is a complete "word".
if wordwrap {
if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
continue
}
}
// If a word (or just a wide rune) does not fit in the window
if vloc.X+wordwidth > maxWidth && vloc.X > w.gutterOffset {
for vloc.X < maxWidth {
draw(' ', nil, config.DefStyle, false)
}
// We either stop or we wrap to draw the rune in the next line
// We either stop or we wrap to draw the word in the next line
if !softwrap {
break
} else {
@@ -609,16 +637,25 @@ func (w *BufWindow) displayBuffer() {
}
}
draw(r, combc, curStyle, true)
for _, r := range word {
draw(r.r, r.combc, r.style, true)
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if width > 1 {
for i := 1; i < width; i++ {
draw(char, nil, curStyle, false)
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if r.width > 1 {
char := ' '
if r.r != '\t' {
char = '@'
}
for i := 1; i < r.width; i++ {
draw(char, nil, r.style, false)
}
}
bloc.X++
}
bloc.X++
line = line[size:]
word = word[:0]
wordwidth = 0
// If we reach the end of the window then we either stop or we wrap for softwrap
if vloc.X >= maxWidth {

View File

@@ -56,14 +56,19 @@ func (w *BufWindow) getVLocFromLoc(loc buffer.Loc) VLoc {
return vloc
}
wordwrap := w.Buf.Settings["wordwrap"].(bool)
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
line := w.Buf.LineBytes(loc.Y)
x := 0
totalwidth := 0
wordwidth := 0
wordoffset := 0
for len(line) > 0 {
r, _, size := util.DecodeCharacter(line)
line = line[size:]
width := 0
switch r {
@@ -76,19 +81,37 @@ func (w *BufWindow) getVLocFromLoc(loc buffer.Loc) VLoc {
totalwidth += width
}
// If a wide rune does not fit in the window
if vloc.VisualX+width > w.bufWidth && vloc.VisualX > 0 {
wordwidth += width
// Collect a complete word to know its width.
// If wordwrap is off, every single character is a complete "word".
if wordwrap {
if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
if x < loc.X {
wordoffset += width
x++
}
continue
}
}
// If a word (or just a wide rune) does not fit in the window
if vloc.VisualX+wordwidth > w.bufWidth && vloc.VisualX > 0 {
vloc.Row++
vloc.VisualX = 0
}
if x == loc.X {
vloc.VisualX += wordoffset
return vloc
}
x++
line = line[size:]
vloc.VisualX += width
vloc.VisualX += wordwidth
wordwidth = 0
wordoffset = 0
if vloc.VisualX >= w.bufWidth {
vloc.Row++
vloc.VisualX = 0
@@ -104,6 +127,7 @@ func (w *BufWindow) getLocFromVLoc(svloc VLoc) buffer.Loc {
return loc
}
wordwrap := w.Buf.Settings["wordwrap"].(bool)
tabsize := util.IntOpt(w.Buf.Settings["tabsize"])
line := w.Buf.LineBytes(svloc.Line)
@@ -111,8 +135,17 @@ func (w *BufWindow) getLocFromVLoc(svloc VLoc) buffer.Loc {
totalwidth := 0
var widths []int
if wordwrap {
widths = make([]int, 0, w.bufWidth)
} else {
widths = make([]int, 0, 1)
}
wordwidth := 0
for len(line) > 0 {
r, _, size := util.DecodeCharacter(line)
line = line[size:]
width := 0
switch r {
@@ -125,21 +158,40 @@ func (w *BufWindow) getLocFromVLoc(svloc VLoc) buffer.Loc {
totalwidth += width
}
// If a wide rune does not fit in the window
if vloc.VisualX+width > w.bufWidth && vloc.VisualX > 0 {
widths = append(widths, width)
wordwidth += width
// Collect a complete word to know its width.
// If wordwrap is off, every single character is a complete "word".
if wordwrap {
if !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {
continue
}
}
// If a word (or just a wide rune) does not fit in the window
if vloc.VisualX+wordwidth > w.bufWidth && vloc.VisualX > 0 {
if vloc.Row == svloc.Row {
if wordwrap {
// it's a word, not a wide rune
loc.X--
}
return loc
}
vloc.Row++
vloc.VisualX = 0
}
vloc.VisualX += width
if vloc.Row == svloc.Row && vloc.VisualX > svloc.VisualX {
return loc
for i := range widths {
vloc.VisualX += widths[i]
if vloc.Row == svloc.Row && vloc.VisualX > svloc.VisualX {
return loc
}
loc.X++
}
loc.X++
line = line[size:]
widths = widths[:0]
wordwidth = 0
if vloc.VisualX >= w.bufWidth {
vloc.Row++

View File

@@ -365,6 +365,11 @@ Here are the available options:
default value: `true`
* `wordwrap`: wrap long lines by words, i.e. break at spaces. This option
only does anything if `softwrap` is on.
default value: `false`
* `xterm`: micro will assume that the terminal it is running in conforms to
`xterm-256color` regardless of what the `$TERM` variable actually contains.
Enabling this option may cause unwanted effects if your terminal in fact