1package copy
2
3// Navigator owns cursor movement policy over a Document. Its positions are
4// always normalized glyph heads after a successful movement.
5type Navigator struct {
6 Pos Pos
7 PreferredCol int
8}
9
10// NewNavigator creates a navigator at at. Set can normalize a position when a
11// document is available.
12func NewNavigator(at Pos) Navigator {
13 return Navigator{Pos: at, PreferredCol: at.Col}
14}
15
16// Left moves to the previous glyph on the current physical row.
17func (n *Navigator) Left(doc *Document) bool {
18 if doc == nil {
19 return false
20 }
21 pos, ok := doc.PrevGlyph(n.Pos)
22 if !ok {
23 return false
24 }
25 return n.set(pos, true)
26}
27
28// Right moves to the next glyph on the current physical row.
29func (n *Navigator) Right(doc *Document) bool {
30 if doc == nil {
31 return false
32 }
33 pos, ok := doc.NextGlyph(n.Pos)
34 if !ok {
35 return false
36 }
37 return n.set(pos, true)
38}
39
40// Up moves to the preceding physical row while retaining PreferredCol.
41func (n *Navigator) Up(doc *Document) bool {
42 if doc == nil {
43 return false
44 }
45 return n.moveVertical(doc, n.Pos.Row-1)
46}
47
48// Down moves to the following physical row while retaining PreferredCol.
49func (n *Navigator) Down(doc *Document) bool {
50 if doc == nil {
51 return false
52 }
53 return n.moveVertical(doc, n.Pos.Row+1)
54}
55
56// WordNext moves to the start of the next word, possibly on another row.
57func (n *Navigator) WordNext(doc *Document) bool {
58 if doc == nil {
59 return false
60 }
61 pos, ok := doc.NextWordStart(n.Pos)
62 if !ok {
63 return false
64 }
65 return n.set(pos, true)
66}
67
68// WordBackward moves to the start of the current or preceding word.
69func (n *Navigator) WordBackward(doc *Document) bool {
70 if doc == nil {
71 return false
72 }
73 pos, ok := doc.PreviousWordStart(n.Pos)
74 if !ok {
75 return false
76 }
77 return n.set(pos, true)
78}
79
80// WordEnd moves to the end of the current or next word.
81func (n *Navigator) WordEnd(doc *Document) bool {
82 if doc == nil {
83 return false
84 }
85 pos, ok := doc.NextWordEnd(n.Pos)
86 if !ok {
87 return false
88 }
89 return n.set(pos, true)
90}
91
92// Top moves to the first physical row at the current column and resets the
93// preferred column to the normalized destination.
94func (n *Navigator) Top(doc *Document) bool {
95 if doc == nil || doc.Len() == 0 {
96 return false
97 }
98 return n.Set(doc, Pos{Row: 0, Col: n.Pos.Col})
99}
100
101// Bottom moves to the last physical row at the current column and resets the
102// preferred column to the normalized destination.
103func (n *Navigator) Bottom(doc *Document) bool {
104 if doc == nil || doc.Len() == 0 {
105 return false
106 }
107 return n.Set(doc, Pos{Row: doc.Len() - 1, Col: n.Pos.Col})
108}
109
110// Page moves rows physical rows while retaining PreferredCol. Its destination
111// clamps to the first or last physical row.
112func (n *Navigator) Page(doc *Document, rows int) bool {
113 if doc == nil || doc.Len() == 0 {
114 return false
115 }
116 return n.moveVertical(doc, min(max(n.Pos.Row+rows, 0), doc.Len()-1))
117}
118
119// Set moves to pos after normalizing it through doc and updates PreferredCol.
120func (n *Navigator) Set(doc *Document, pos Pos) bool {
121 if doc == nil {
122 return false
123 }
124 pos, ok := doc.Normalize(pos)
125 if !ok {
126 return false
127 }
128 return n.set(pos, true)
129}
130
131func (n *Navigator) moveVertical(doc *Document, row int) bool {
132 if row < 0 || row >= doc.Len() {
133 return false
134 }
135 pos, ok := navigatorPosOnRow(doc, row, n.PreferredCol)
136 if !ok {
137 return false
138 }
139 return n.set(pos, false)
140}
141
142func navigatorPosOnRow(doc *Document, row, col int) (Pos, bool) {
143 cells := doc.Row(row)
144 if len(cells) == 0 {
145 return doc.Normalize(Pos{Row: row, Col: 0})
146 }
147 col = min(max(col, 0), len(cells)-1)
148 return doc.Normalize(Pos{Row: row, Col: col})
149}
150
151func (n *Navigator) set(pos Pos, setPreferred bool) bool {
152 moved := n.Pos != pos
153 n.Pos = pos
154 if setPreferred {
155 n.PreferredCol = pos.Col
156 }
157 return moved
158}