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