don't center cursor on message tree rebuild
[knntp.git] / editor.d
blobe02acc6a3c9f87d0cb6a1f1d9e6ef30a31100cb8
1 /* DigitalMars NNTP reader
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module editor is aliced;
20 import iv.cmdcon;
21 import iv.strex;
22 import iv.utfutil;
23 import iv.vfs;
26 // ////////////////////////////////////////////////////////////////////////// //
27 // very simple wrapping text editor, without much features
28 class Editor {
29 private:
30 int linewrap = 72; // "normal" line length
31 int maxlinelen = 79; // maximum line length
33 string[] lines;
35 int cx, cy;
37 public:
38 enum SpecCh : dchar {
39 Left = '\x01',
40 Right = '\x02',
41 Up = '\x03',
42 Down = '\x04',
43 Home = '\x05',
44 End = '\x06',
45 PageUp = '\x07',
46 Backspace = '\x08',
47 Tab = '\x09',
48 Enter = '\x0a',
49 PageDown = '\x0b',
50 KillLine = '\x0c',
51 Delete = cast(dchar)127,
54 public:
55 this () {
58 void addLine (const(char)[] s) {
59 lines ~= s.idup;
62 @property int lineCount () const pure nothrow @safe @nogc { return cast(int)lines.length; }
64 @property int curx () const pure nothrow @safe @nogc { return cx; }
65 @property int cury () const pure nothrow @safe @nogc { return cy; }
67 int quoteLevel (int lidx) const pure nothrow @safe @nogc {
68 if (lidx < 0 || lidx >= lines.length) return 0;
69 string s = lines[lidx];
70 int res = 0;
71 while (s.length) {
72 if (s[0] <= ' ') { s = s[1..$]; continue; }
73 if (s[0] != '>') break;
74 ++res;
75 s = s[1..$];
77 return res;
80 string opIndex (int idx) const pure nothrow @safe @nogc { return (idx >= 0 && idx < lines.length ? lines[idx] : null); }
82 int linelen (int idx) const pure nothrow @safe @nogc {
83 if (idx < 0 || idx >= lines.length) return 0;
84 string s = lines[idx];
85 if (s.length == 0) return 0;
86 Utf8DecoderFast dc;
87 int res = 0;
88 while (s.length) {
89 if (dc.decode(cast(ubyte)s[0])) ++res;
90 s = s[1..$];
92 return res;
95 // x position to line offset
96 int lineofs (int x, int idx) const pure nothrow @safe @nogc {
97 if (x <= 0 || idx < 0 || idx >= lines.length) return 0;
98 string s = lines[idx];
99 if (x >= s.length) return cast(int)s.length;
100 Utf8DecoderFast dc;
101 int res = 0;
102 while (s.length) {
103 ++res;
104 if (dc.decode(cast(ubyte)s[0])) {
105 if (--x == 0) break;
107 s = s[1..$];
109 return res;
112 dchar chatAt (int x, int y) const pure nothrow @safe @nogc {
113 if (x < 0 || y < 0 || y >= lines.length) return 0;
114 string s = lines[y];
115 if (s.length == 0 || x >= s.length) return 0;
116 Utf8DecoderFast dc;
117 while (s.length) {
118 if (dc.decode(cast(ubyte)s[0])) {
119 if (x-- == 0) return dc.codepoint;
121 s = s[1..$];
123 return 0;
126 // reformat whole text
127 void reformat () {
128 string curline;
129 bool justFlushed = true;
131 string[] newlines;
132 int lastql = 0; // quote level
134 void addWord (string w) {
135 if (w.length == 0) return;
136 if (justFlushed) foreach (immutable _; 0..lastql) curline ~= '>';
137 if (curline.length+w.length > maxlinelen) {
138 if (!justFlushed) {
139 if (curline.length == 0 || curline[$-1] != ' ') curline ~= ' ';
140 newlines ~= curline;
141 curline = null;
142 justFlushed = true;
143 foreach (immutable _; 0..lastql) curline ~= '>';
146 curline ~= w;
147 justFlushed = false;
150 foreach (string s; lines) {
151 if (s.length == 0) {
152 if (!justFlushed) { newlines ~= curline; curline = null; justFlushed = true; }
153 newlines ~= null;
154 } else {
155 bool hardCr = (s.length == 0 || s[$-1] > ' ');
156 int ql = 0;
157 usize pos = 0;
158 while (pos < s.length) {
159 if (s[pos] <= ' ') ++pos;
160 else if (s[pos] == '>') { ++ql; ++pos; }
161 else break;
163 if (ql > 0) s = s[pos..$];
164 if (ql != lastql) {
165 if (!justFlushed) { newlines ~= curline; curline = null; justFlushed = true; }
166 lastql = ql;
168 while (s.length) {
169 if (s[0] <= ' ') { s = s[1..$]; continue; }
170 pos = 0;
171 while (pos < s.length && s[pos] > ' ') ++pos;
172 if (pos < s.length && s[pos] <= ' ') ++pos;
173 addWord(s[0..pos]);
174 s = s[pos..$];
176 if (/*hardCr &&*/ !justFlushed) { newlines ~= curline; curline = null; justFlushed = true; }
180 if (!justFlushed && curline.length) newlines ~= curline;
182 lines = newlines;
184 cx = 0;
185 cy = cast(int)lines.length;
188 void putUtf (const(char)[] s) {
189 Utf8DecoderFast dc;
190 while (s.length) {
191 if (dc.decode(cast(ubyte)s[0])) {
192 if (dc.isValidDC(dc.codepoint)) putChar(dc.codepoint); else putChar('?');
194 s = s[1..$];
198 void putChar (dchar ch) {
199 if (ch == 13 || ch == 10) { doEnter(); return; }
200 if (ch == SpecCh.Backspace) { doBackspace(); return; }
201 if (ch == SpecCh.Delete) { doDelete(); return; }
202 if (ch < ' ') {
203 switch (ch) {
204 case SpecCh.Left: doLeft(); break;
205 case SpecCh.Right: doRight(); break;
206 case SpecCh.Up: doUp(); break;
207 case SpecCh.Down: doDown(); break;
208 case SpecCh.Home: doHome(); break;
209 case SpecCh.End: doEnd(); break;
210 case SpecCh.KillLine:
211 if (cy < lines.length) {
212 import std.algorithm : remove;
213 lines = lines.remove(cy);
214 cx = 0;
216 break;
217 default:
219 return;
221 string s;
222 if (cy >= lines.length) {
223 cx = 0;
224 cy = cast(int)lines.length;
225 s ~= ch;
226 lines ~= s;
227 } else {
228 int len = linelen(cy);
229 if (cx >= len) {
230 cx = len;
231 lines[cy] ~= ch;
232 } else {
233 s = lines[cy];
234 int ofs = lineofs(cx, cy);
235 if (ofs >= s.length) {
236 s ~= ch;
237 } else {
238 string t = s[0..ofs];
239 t ~= ch;
240 s = t~s[ofs..$];
241 lines[cy] = s;
245 doRight();
246 if (linelen(cy) > linewrap) {
247 int pos = linewrap-1;
248 while (pos >= 0 && chatAt(pos, cy) > ' ') --pos;
249 if (pos > 0) {
250 int oldcx = cx;
251 cx = ++pos;
252 doEnter();
253 //conwriteln("oldcx=", oldcx, "; pos=", pos, "; cx=", cx);
254 if (oldcx < pos) {
255 --cy;
256 cx = oldcx;
257 } else {
258 cx += oldcx-pos;
264 void doEnter () {
265 if (cy >= lines.length) {
266 lines ~= null;
267 cx = 0;
268 cy = cast(int)lines.length;
269 } else {
270 string s = lines[cy];
271 int ql = quoteLevel(cy);
272 int ofs = lineofs(cx, cy);
273 lines.length += 1;
274 foreach_reverse (immutable c; cy+1..lines.length) lines[c] = lines[c-1];
275 lines[cy] = s[0..ofs];
276 lines[cy+1] = s[ofs..$];
277 cx = 0;
278 ++cy;
279 while (ql-- > 0) { lines[cy] = ">"~lines[cy]; ++cx; }
283 void doBackspace () {
284 if (cy >= lines.length) {
285 if (cx > 0) --cx;
286 } else if (cx > 0) {
287 string s = lines[cy];
288 int ofs1 = lineofs(cx, cy);
289 int ofs0 = lineofs(--cx, cy);
290 if (ofs1 == 0) return; //FIXME: join
291 lines[cy] = s[0..ofs0];
292 if (ofs1 < s.length) lines[cy] ~= s[ofs1..$];
296 void doDelete () {
297 if (cy < lines.length) {
298 string s = lines[cy];
299 int ofs0 = lineofs(cx, cy);
300 if (ofs0 >= s.length) return; //FIXME: join
301 int ofs1 = lineofs(cx+1, cy);
302 if (ofs0 == ofs1) return; //FIXME: join
303 lines[cy] = s[0..ofs0];
304 if (ofs1 < s.length) lines[cy] ~= s[ofs1..$];
308 void doLeft () {
309 int len = linelen(cy);
310 if (cx > len) cx = len;
311 if (cx == 0) {
312 if (cy > 0) {
313 --cy;
314 cx = linelen(cy);
316 } else {
317 --cx;
321 void doRight () {
322 auto len = linelen(cy);
323 if (cx > len) cx = len;
324 if (cx >= len) {
325 if (cy < lines.length) { ++cy; cx = 0; }
326 } else {
327 ++cx;
331 void doUp () {
332 if (cy > 0) --cy;
335 void doDown () {
336 if (cy < lines.length) ++cy;
339 void doHome () {
340 cx = 0;
343 void doEnd () {
344 cx = linelen(cy);