1/*
2
3    Minimum Profit - A Text Editor
4    Clipboard routines.
5
6    ttcdt <dev@triptico.com> et al.
7
8    This software is released into the public domain.
9    NO WARRANTY. See file LICENSE for details.
10
11*/
12
13/** editor actions **/
14
15mp_doc.actions += {
16    unmark:             sub (d) { d->unmark(); },
17    mark_tag:           sub (d) { d->start_selection(0); },
18    mark:               sub (d) { d->mark(); },
19    mark_tag_vertical:  sub (d) { d->start_selection(1); },
20    mark_vertical:      sub (d) { d->mark_vertical(); },
21    copy_mark:          sub (d) { d->busy(1)->copy()->unmark()->busy(0); },
22    paste_mark:         sub (d) { d->busy(1)->store_undo()->paste()->busy(0); },
23    cut_mark:           sub (d) { d->busy(1)->store_undo()->cut()->busy(0); },
24    delete_mark:        sub (d) { d->busy(1)->store_undo()->delete_mark()->busy(0); },
25    mark_all:           sub (d) { d->move_bof()->mark()->move_eof()->mark(); },
26    mouse_drag_mark:    sub (d) {
27        /* no selection yet? move to initial click and mark */
28        if (d.txt.mark == NULL)
29            d->mark();
30
31        /* move to drag position */
32        d->move_to_coords_xy(mp.mouse_to_x - mp.xoffset, mp.mouse_to_y);
33
34        /* and mark */
35        d->mark();
36
37        return d;
38    },
39    cut_lines_with_string: sub (d, str) {
40        if (str == NULL) {
41            local r = mp.form(
42                [
43                    {
44                        'label'   => L("Cut lines containing") + ':',
45                        'type'    => 'text',
46                        'history' => 'cut_lines_with_string'
47                    }
48                ]
49            );
50
51            if (r != NULL)
52                str = r[0];
53        }
54
55        if (str != NULL) {
56            d->busy(1)->store_undo()->cut_lines_with_string(str)->busy(0);
57        }
58
59        return d;
60    },
61    cut_line: sub (d) { d->busy(1)->store_undo()->cut_line()->busy(0); }
62};
63
64
65/** default key bindings **/
66
67mp_doc.keycodes += {
68    "f8" =>         "unmark",
69    "f9" =>         "mark_tag",
70    "ctrl-b" =>     "mark_tag_vertical",
71    "ctrl-c" =>     "copy_mark",
72    "ctrl-v" =>     "paste_mark",
73    "ctrl-x" =>     "cut_mark",
74    "mouse-drag" => "mouse_drag_mark"
75};
76
77/** action descriptions **/
78
79mp.actdesc += {
80    unmark:                   LL("Unmark block"),
81    mark_tag:                 LL("Mark beginning/end of block"),
82    mark:                     LL("Mark selection block"),
83    mark_tag_vertical:        LL("Mark vertical block"),
84    mark_vertical:            LL("Mark vertical selection block"),
85    copy_mark:                LL("Copy block"),
86    paste_mark:               LL("Paste block"),
87    cut_mark:                 LL("Cut block"),
88    delete_mark:              LL("Delete block"),
89    mouse_drag_mark:          LL("Mark using mouse dragging"),
90    mark_all:                 LL("Mark all"),
91    cut_lines_with_string:    LL("Cut lines containing a string...")
92};
93
94
95/** code **/
96
97sub mp_doc.unmark(doc)
98/* unmarks the block */
99{
100    /* just destroy the mark */
101    doc.txt.mark = NULL;
102    doc.txt.selecting_mode = NULL;
103
104    return doc;
105}
106
107sub mp_doc.start_selection(doc, vertical)
108/* start a toggle selection mode */
109{
110    local txt = doc.txt;
111
112    /* If there is no selection yet with keys */
113    if (txt.selecting_mode == NULL) {
114        /* Make one */
115        txt.selecting_mode = { trigger: vertical };
116    }
117    doc->mark();
118    if (vertical == 1) doc.txt.mark.vertical = 1;
119
120    return doc;
121}
122
123
124sub mp_doc.mark(doc)
125/* marks the start or end of the block */
126{
127    local txt = doc.txt;
128
129    if (txt.mark == NULL) {
130        /* no mark; create one */
131        txt.mark = {
132            ax:         txt.x,
133            bx:         txt.x,
134            ex:         txt.x,
135            ay:         txt.y,
136            by:         txt.y,
137            ey:         txt.y,
138            vertical:   0,
139            incomplete: 1
140        };
141    }
142    else {
143        /* mark exists; extend current one */
144        if (txt.mark.vertical == 0) {
145            /* normal selection */
146            if (txt.y < txt.mark.ay ||
147                (txt.y == txt.mark.ay && txt.x < txt.mark.ax)) {
148                /* move the beginning of the block */
149                txt.mark += {
150                    bx: txt.x,
151                    by: txt.y,
152                    ex: txt.mark.ax,
153                    ey: txt.mark.ay
154                };
155            }
156            else {
157                /* move the end of the block */
158                txt.mark += {
159                    ex: txt.x,
160                    ey: txt.y,
161                    bx: txt.mark.ax,
162                    by: txt.mark.ay
163                };
164            }
165        }
166        else {
167            /* vertical selection */
168            txt.mark.by = txt.mark.ay;
169            txt.mark.ey = txt.y;
170            if (txt.y < txt.mark.ay) {
171                txt.mark.by = txt.y;
172                txt.mark.ey = txt.mark.ay;
173            }
174
175            txt.mark.bx = txt.mark.ax;
176            txt.mark.ex = txt.x;
177            if (txt.x < txt.mark.ax) {
178                txt.mark.bx = txt.x;
179                txt.mark.ex = txt.mark.ax;
180            }
181        }
182
183        txt.mark.incomplete = 0;
184    }
185
186    /* if the block has zero size, unmark it */
187    if (txt.mark.incomplete == 0 &&
188        txt.mark.bx == txt.mark.ex &&
189        txt.mark.by == txt.mark.ey)
190        txt.mark = NULL;
191
192    return doc;
193}
194
195
196sub mp_doc.mark_vertical(doc)
197/* start vertical block selection */
198{
199    doc->mark();
200    doc.txt.mark.vertical = 1;
201
202    return doc;
203}
204
205
206sub mp_doc.get_active_area(doc)
207/* returns the active area: the selection or the full document */
208{
209    local m;
210
211    if ((m = doc.txt.mark) == NULL)
212        return doc.txt.lines;
213    else
214        return doc->get_range(m.bx, m.by, m.ex, m.ey, m.vertical);
215}
216
217
218sub mp.copy(content)
219/* copies content to the clipboard */
220{
221    /* ensure content is an array */
222    if (type(content) == "string")
223        content = content->split("\n");
224
225    /* ensure it's unwrapped */
226    mp_c.vw_unwrap(content);
227
228    /* store */
229    mp.clipboard = content;
230
231    /* propagate */
232    mp_drv.clip_to_sys();
233}
234
235
236/**
237 * mp_doc.copy - Copies the selected block to the clipboard
238 * @doc: the document
239 *
240 * Copies to the clipboard the content of the selected block,
241 * if one exists.
242 */
243sub mp_doc.copy(doc)
244{
245    if (doc.txt.mark) {
246        mp.copy(doc->get_active_area());
247        mp.clipboard_vertical = doc.txt.mark.vertical;
248    }
249
250    return doc;
251}
252
253
254sub mp_doc.delete_mark(doc)
255/* deletes current selection */
256{
257    local txt = doc.txt;
258
259    /* no mark? done */
260    if (txt.mark != NULL) {
261        /* deletes the range */
262        if (txt.mark.bx != txt.mark.ex || txt.mark.by != txt.mark.ey)
263            doc->delete_range(txt.mark.bx, txt.mark.by,
264                txt.mark.ex, txt.mark.ey, txt.mark.vertical);
265
266        doc->unmark();
267    }
268
269    return doc;
270}
271
272
273sub mp_doc.cut(doc)
274/* cut (copy + delete) selected mark */
275{
276    doc->copy()->delete_mark();
277    mp_drv.clip_to_sys();
278
279    return doc;
280}
281
282
283/**
284 * mp_doc.paste - Pastes from the clipboard into a text or as a value
285 * @doc: the destination of the copy
286 *
287 * If @doc is NULL, returns the content of the clipboard as a
288 * scalar string; if it's not, is assumed to be a document and
289 * pastes the content of the clipboard into the cursor position.
290 */
291sub mp_doc.paste(doc)
292{
293    mp_drv.sys_to_clip();
294
295    if (doc == NULL)
296        return join(mp.clipboard, "\n");
297
298    if (size(mp.clipboard) == 0)
299        return doc;
300
301    local t = mp.config.auto_indent;
302    mp.config.auto_indent = 0;
303
304    /* is there a block? replace it */
305    if (doc.txt.mark != NULL) {
306        /* move there */
307        doc.txt.x = doc.txt.mark.bx;
308        doc.txt.y = doc.txt.mark.by;
309
310        /* and delete the block */
311        doc->delete_mark();
312    }
313
314    if (mp.clipboard_vertical == 0) {
315        /* normal selection in clipboard */
316        doc->insert(mp.clipboard);
317    }
318    else {
319        /* vertical selection in clipboard */
320        local txt = doc.txt;
321        local s = size(mp.clipboard);
322        local i = 0;
323        local w;
324        local e;
325
326        while (i < s) {
327            /* pad out to current x position */
328            e = txt.x - size(txt.lines[txt.y]);
329
330            foreach (_, e)
331                txt.lines[txt.y] = txt.lines[txt.y] + " ";
332
333            /* insert this line of the clipboard */
334            w = splice(txt.lines[txt.y], mp.clipboard[i], txt.x, 0);
335            txt.lines[txt.y] = w;
336
337            i += 1;
338            txt.y += 1;
339        }
340        txt.y -= 1;
341        txt.mod += 1;
342    }
343
344    mp.config.auto_indent = t;
345
346    if (doc->visual_wrap())
347        doc->vw_wrap();
348
349    return doc;
350}
351
352
353/**
354 * mp_doc.cut_lines_with_string - Cuts all lines matching a string
355 * @doc: the document
356 * @str: the string to be matched
357 *
358 * Cuts all lines from the document that matches @str, that is
359 * a regular expression. The deleted lines are left in the clipboard.
360 * If a block is selected, only lines inside it are cut.
361 */
362sub mp_doc.cut_lines_with_string(doc, str)
363{
364    local r = [];
365    local p;
366
367    if (str == NULL || str == '')
368        str = '^$';
369
370    str = '/' + str + '/';
371
372    if (doc.txt.mark) {
373        doc->cut();
374
375        /* create a temporary work document */
376        p = new(mp_doc, { name: '<wrk>' });
377        p.txt.lines = mp.clipboard;
378    }
379    else
380        p = doc;
381
382    p->move_bof();
383
384    while (p.txt.y < size(p.txt.lines) - 1) {
385        local l = p.txt.lines[p.txt.y];
386
387        if (regex(l, str) != NULL) {
388            push(r, l);
389            p->delete_line();
390        }
391        else
392            p->move_down();
393    }
394
395    /* if p is the working document, move content back to doc */
396    if (p.name == '<wrk>')
397        doc->insert(p.txt.lines);
398
399    mp.clipboard = r;
400    mp_drv.clip_to_sys();
401
402    return doc;
403}
404
405/**
406 * mp_doc.cut_line - Cut current line to clipboard
407 * @doc: the document
408 *
409 * Cut the current line to the clipboard.
410 */
411sub mp_doc.cut_line(doc)
412{
413    if (doc.txt.mark) {
414        doc->cut();
415        doc->unmark();
416
417        return doc;
418    }
419
420    doc.txt.x = 0;
421    doc->mark();
422    if (doc.txt.y < size(doc.txt.lines) - 1)
423        doc.txt.y += 1;
424    else
425        doc->move_eol();
426    doc->mark();
427    doc->cut();
428    doc->unmark();
429
430    return doc;
431}
432