1
/* kate-script
2
 * author: Dominik Haumann <dhdev@gmx.de>, Milian Wolff <mail@milianw.de>
3
 * license: LGPL
4
 * revision: 3
5
 * kate-version: 3.4
6
 * type: commands
7
 * functions: sort, moveLinesDown, moveLinesUp, natsort, uniq, rtrim, ltrim, trim, join, rmblank, unwrap, each, filter, map, duplicateLinesUp, duplicateLinesDown
8
 */
9
10
function sort()
11
{
12
    each(function(lines){return lines.sort()});
13
}
14
15
function uniq()
16
{
17
    each(function(lines) {
18
        for ( var i = 1; i < lines.length; ++i ) {
19
          for ( var j = i - 1; j >= 0; --j ) {
20
            if ( lines[i] == lines[j] ) {
21
              lines.splice(i, 1);
22
              // gets increased in the for
23
              --i;
24
              break;
25
            }
26
          }
27
        }
28
        return lines;
29
    });
30
}
31
32
function natsort()
33
{
34
    each(function(lines){return lines.sort(natcompare);});
35
}
36
37
function rtrim()
38
{
39
    map(function(l){ return l.replace(/\s+$/, ''); });
40
}
41
42
function ltrim()
43
{
44
    map(function(l){ return l.replace(/^\s+/, ''); });
45
}
46
47
function trim()
48
{
49
    map(function(l){ return l.replace(/^\s+|\s+$/, ''); });
50
}
51
52
function rmblank()
53
{
54
    filter(function(l) { return l.length > 0; });
55
}
56
57
function join(separator)
58
{
59
    if (typeof(separator) != "string") {
60
        separator = "";
61
    }
62
    each(function(lines){
63
        return [lines.join(separator)];
64
    });
65
}
66
67
// unwrap does the opposite of the script word wrap
68
function unwrap ()
69
{
70
    var selectionRange = view.selection();
71
    if (selectionRange.isValid()) {
72
        // unwrap all paragraphs in the selection range
73
        var currentLine = selectionRange.start.line;
74
        var count = selectionRange.end.line - selectionRange.start.line;
75
76
        document.editBegin();
77
        while (count >= 0) {
78
            // skip empty lines
79
            while (count >= 0 && document.firstColumn(currentLine) == -1) {
80
                --count;
81
                ++currentLine;
82
            }
83
84
            // find block of text lines to join
85
            var anchorLine = currentLine;
86
            while (count >= 0 && document.firstColumn(currentLine) != -1) {
87
                --count;
88
                ++currentLine;
89
            }
90
91
            if (currentLine != anchorLine) {
92
                document.joinLines(anchorLine, currentLine - 1);
93
                currentLine -= currentLine - anchorLine - 1;
94
            }
95
        }
96
        document.editEnd();
97
    } else {
98
        // unwrap paragraph under the cursor
99
        var cursorPosition = view.cursorPosition();
100
        if (document.firstColumn(cursorPosition.line) != -1) {
101
            var startLine = cursorPosition.line;
102
            while (startLine > 0) {
103
                if (document.firstColumn(startLine - 1) == -1) {
104
                    break;
105
                }
106
                --startLine;
107
            }
108
109
            var endLine = cursorPosition.line;
110
            var lineCount = document.lines();
111
            while (endLine < lineCount) {
112
                if (document.firstColumn(endLine + 1) == -1) {
113
                    break;
114
                }
115
                ++endLine;
116
            }
117
118
            if (startLine != endLine) {
119
                document.editBegin();
120
                document.joinLines(startLine, endLine);
121
                document.editEnd();
122
            }
123
        }
124
    }
125
}
126
127
function moveLinesDown()
128
{
129
    var fromLine = -1;
130
    var toLine = -1;
131
132
    var selectionRange = view.selection();
133
    if (selectionRange.isValid() && selectionRange.end.line < document.lines() - 1) {
134
        toLine = selectionRange.start.line;
135
        fromLine = selectionRange.end.line + 1;
136
    } else if (view.cursorPosition().line < document.lines() - 1) {
137
        toLine = view.cursorPosition().line;
138
        fromLine = toLine + 1;
139
    }
140
    if (fromLine != -1 && toLine != -1) {
141
        var text = document.line(fromLine);
142
143
        document.editBegin();
144
        document.removeLine(fromLine);
145
        document.insertLine(toLine, text);
146
        document.editEnd();
147
    }
148
}
149
150
function moveLinesUp()
151
{
152
    var fromLine = -1;
153
    var toLine = -1;
154
155
    var selectionRange = view.selection();
156
    if (selectionRange.isValid() && selectionRange.start.line > 0) {
157
        fromLine = selectionRange.start.line - 1;
158
        toLine = selectionRange.end.line;
159
    } else if (view.cursorPosition().line > 0) {
160
        toLine = view.cursorPosition().line;
161
        fromLine = toLine - 1;
162
    }
163
164
    if (fromLine != -1 && toLine != -1) {
165
        var text = document.line(fromLine);
166
167
        document.editBegin();
168
        document.removeLine(fromLine);
169
        document.insertLine(toLine, text);
170
        document.editEnd();
171
    }
172
}
173
174
//private
175
function _duplicateLines(up)
176
{
177
    var fromLine = -1;
178
    var toLine = -1;
179
180
    var selectionRange = view.selection();
181
    if (selectionRange.isValid() && selectionRange.start.line > 0) {
182
        fromLine = selectionRange.start.line;
183
        toLine = selectionRange.end.line;
184
        if (selectionRange.end.column==0) toLine--; //if selection ends at beginning of line, skip that line
185
    } else if (view.cursorPosition().line > 0) {
186
        toLine = view.cursorPosition().line;
187
        fromLine = toLine;
188
    }
189
190
    var text = [];
191
    for (var i=fromLine; i<=toLine; ++i) {
192
        text.push(document.line(i));
193
    }
194
195
    document.editBegin();
196
    for (var i=text.length-1; i>=0; --i) {
197
        if (up) {
198
            document.insertLine(fromLine, text[i]);
199
        } else {
200
            document.insertLine(toLine+1, text[i]);
201
        }
202
    }
203
    document.editEnd();
204
205
    if (up) {
206
        view.setSelection(new Range(new Cursor(fromLine, 0), new Cursor(toLine+1, 0)));
207
        view.setCursorPosition(new Cursor(fromLine, 0));
208
    } else {
209
        view.setSelection(new Range(new Cursor(toLine+1, 0), new Cursor(toLine+text.length+1, 0)));
210
        view.setCursorPosition(new Cursor(toLine+1, 0));
211
    }
212
}
213
214
function duplicateLinesUp()
215
{
216
    _duplicateLines(true);
217
}
218
219
function duplicateLinesDown()
220
{
221
    _duplicateLines(false);
222
}
223
224
function action(cmd)
225
{
226
    var a = new Object();
227
    if (cmd == "sort") {
228
        a.text = i18n("Sort Selected Text");
229
        a.icon = "";
230
        a.category = "";
231
        a.interactive = false;
232
        a.shortcut = "";
233
    } else if (cmd == "moveLinesDown") {
234
        a.text = i18n("Move Lines Down");
235
        a.icon = "";
236
        a.category = "";
237
        a.interactive = false;
238
        a.shortcut = "Ctrl+Shift+Down";
239
    } else if (cmd == "moveLinesUp") {
240
        a.text = i18n("Move Lines Up");
241
        a.icon = "";
242
        a.category = "";
243
        a.interactive = false;
244
        a.shortcut = "Ctrl+Shift+Up";
245
    } else if (cmd == "duplicateLinesUp") {
246
        a.text = i18n("Duplicate Selected Lines Up");
247
        a.icon = "";
248
        a.category = "";
249
        a.interactive = false;
250
        a.shortcut = "Ctrl+Alt+Up";
251
    } else if (cmd == "duplicateLinesDown") {
252
        a.text = i18n("Duplicate Selected Lines Down");
253
        a.icon = "";
254
        a.category = "";
255
        a.interactive = false;
256
        a.shortcut = "Ctrl+Alt+Down";
257
    }
258
259
    return a;
260
}
261
262
function help(cmd)
263
{
264
    if (cmd == "sort") {
265
        return i18n("Sort the selected text or whole document.");
266
    } else if (cmd == "moveLinesDown") {
267
        return i18n("Move selected lines down.");
268
    } else if (cmd == "moveLinesUp") {
269
        return i18n("Move selected lines up.");
270
    } else if (cmd == "uniq") {
271
        return i18n("Remove duplicate lines from the selected text or whole document.");
272
    } else if (cmd == "natsort") {
273
        return i18n("Sort the selected text or whole document in natural order.<br>"
274
              +"Here's an example to show the difference to the normal sort method:<br>"
275
              +"sort(a10, a1, a2) => a1, a10, a2<br>"
276
              +"natsort(a10, a1, a2) => a1, a2, a10");
277
    } else if (cmd == "rtrim") {
278
        return i18n("Trims trailing whitespace from selection or whole document.");
279
    } else if (cmd == "ltrim") {
280
        return i18n("Trims leading whitespace from selection or whole document.");
281
    } else if (cmd == "trim") {
282
        return i18n("Trims leading and trailing whitespace from selection or whole document.");
283
    } else if (cmd == "join") {
284
        return i18n("Joins selected lines or whole document. Optionally pass a separator to put between each line:<br>" +
285
                    "<code>join ', '</code> will e.g. join lines and separate them by a comma.");
286
    } else if (cmd == "rmblank") {
287
        return i18n("Removes empty lines from selection or whole document.");
288
    } else if (cmd == "unwrap") {
289
        return "Unwraps all paragraphs in the text selection, or the paragraph under the text cursor if there is no selected text.";
290
    } else if (cmd == "each") {
291
        return i18n("Given a JavaScript function as argument, call that for the list of (selected) lines and" +
292
                    "replace them with the return value of that callback.<br>" +
293
                    "Example (join selected lines):<br>" +
294
                    "<code>each 'function(lines){return lines.join(\", \"}'</code><br>" +
295
                    "To save you some typing, you can also do this to achieve the same:<br>" +
296
                    "<code>each 'lines.join(\", \")'</code>");
297
    } else if (cmd == "filter") {
298
        return i18n("Given a JavaScript function as argument, call that for the list of (selected) lines " +
299
                    "and remove those where the callback returns false.<br>" +
300
                    "Example (see also <code>rmblank</code>):<br>" +
301
                    "<code>filter 'function(l){return l.length > 0;}'</code><br>" +
302
                    "To save you some typing, you can also do this to achieve the same:<br>" +
303
                    "<code>filter 'line.length > 0'</code>");
304
    } else if (cmd == "map") {
305
        return i18n("Given a JavaScript function as argument, call that for the list of (selected) lines " +
306
                    "and replace the line with the return value of the callback.<br>" +
307
                    "Example (see also <code>ltrim</code>):<br>" +
308
                    "<code>map 'function(line){return line.replace(/^\s+/, \"\");}'</code><br>" +
309
                    "To save you some typing, you can also do this to achieve the same:<br>" +
310
                    "<code>map 'line.replace(/^\s+/, \"\")'</code>");
311
    } else if (cmd == "duplicateLinesUp") {
312
        return i18n("Duplicates the selected lines up.");
313
    } else if (cmd == "duplicateLinesDown") {
314
        return i18n("Duplicates the selected lines down.");
315
    }
316
}
317
318
/// helper code below:
319
320
function __toFunc(func, defaultArgName)
321
{
322
    if ( typeof(func) != "function" ) {
323
        try {
324
            func = eval("(" + func + ")");
325
        } catch(e) {}
326
        debug(func, typeof(func))
327
        if ( typeof(func) != "function" ) {
328
            try {
329
                // one more try to support e.g.:
330
                // map 'l+l'
331
                // or:
332
                // each 'lines.join("\n")'
333
                func = eval("(function(" + defaultArgName + "){ return " + func + ";})");
334
                debug(func, typeof(func))
335
            } catch(e) {}
336
            if ( typeof(func) != "function" ) {
337
                throw "parameter is not a valid JavaScript callback function: " + typeof(func);
338
            }
339
        }
340
    }
341
    return func;
342
}
343
344
function each(func)
345
{
346
    func = __toFunc(func, 'lines');
347
348
    var selection = view.selection();
349
    if (!selection.isValid()) {
350
        // use whole range
351
        selection = document.documentRange();
352
    } else {
353
        selection.start.column = 0;
354
        selection.end.column = document.lineLength(selection.end.line);
355
    }
356
357
    var text = document.text(selection);
358
359
    var lines = text.split("\n");
360
    lines = func(lines);
361
    if ( typeof(lines) == "object" ) {
362
      text = lines.join("\n");
363
    } else if ( typeof(lines) == "string" ) {
364
      text = lines
365
    } else {
366
      throw "callback function for each has to return object or array of lines";
367
    }
368
369
    view.clearSelection();
370
371
    document.editBegin();
372
    document.removeText(selection);
373
    document.insertText(selection.start, text);
374
    document.editEnd();
375
}
376
377
function filter(func)
378
{
379
    each(function(lines) { return lines.filter(__toFunc(func, 'line')); });
380
}
381
382
function map(func)
383
{
384
    each(function(lines) { return lines.map(__toFunc(func, 'line')); });
385
}
386
387
/*
388
natcompare.js -- Perform 'natural order' comparisons of strings in JavaScript.
389
Copyright (C) 2005 by SCK-CEN (Belgian Nucleair Research Centre)
390
Written by Kristof Coomans <kristof[dot]coomans[at]sckcen[dot]be>
391
392
Based on the Java version by Pierre-Luc Paour, of which this is more or less a straight conversion.
393
Copyright (C) 2003 by Pierre-Luc Paour <natorder@paour.com>
394
395
The Java version was based on the C version by Martin Pool.
396
Copyright (C) 2000 by Martin Pool <mbp@humbug.org.au>
397
398
This software is provided 'as-is', without any express or implied
399
warranty.  In no event will the authors be held liable for any damages
400
arising from the use of this software.
401
402
Permission is granted to anyone to use this software for any purpose,
403
including commercial applications, and to alter it and redistribute it
404
freely, subject to the following restrictions:
405
406
1. The origin of this software must not be misrepresented; you must not
407
claim that you wrote the original software. If you use this software
408
in a product, an acknowledgment in the product documentation would be
409
appreciated but is not required.
410
2. Altered source versions must be plainly marked as such, and must not be
411
misrepresented as being the original software.
412
3. This notice may not be removed or altered from any source distribution.
413
*/
414
415
416
function isWhitespaceChar(a)
417
{
418
    var charCode;
419
    charCode = a.charCodeAt(0);
420
421
    if ( charCode <= 32 )
422
    {
423
        return true;
424
    }
425
    else
426
    {
427
        return false;
428
    }
429
}
430
431
function isDigitChar(a)
432
{
433
    var charCode;
434
    charCode = a.charCodeAt(0);
435
436
    if ( charCode >= 48  && charCode <= 57 )
437
    {
438
        return true;
439
    }
440
    else
441
    {
442
        return false;
443
    }
444
}
445
446
function compareRight(a,b)
447
{
448
    var bias = 0;
449
    var ia = 0;
450
    var ib = 0;
451
452
    var ca;
453
    var cb;
454
455
    // The longest run of digits wins.  That aside, the greatest
456
    // value wins, but we can't know that it will until we've scanned
457
    // both numbers to know that they have the same magnitude, so we
458
    // remember it in BIAS.
459
    for (;; ia++, ib++) {
460
        ca = a.charAt(ia);
461
        cb = b.charAt(ib);
462
463
        if (!isDigitChar(ca)
464
                && !isDigitChar(cb)) {
465
            return bias;
466
        } else if (!isDigitChar(ca)) {
467
            return -1;
468
        } else if (!isDigitChar(cb)) {
469
            return +1;
470
        } else if (ca < cb) {
471
            if (bias == 0) {
472
                bias = -1;
473
            }
474
        } else if (ca > cb) {
475
            if (bias == 0)
476
                bias = +1;
477
        } else if (ca == 0 && cb == 0) {
478
            return bias;
479
        }
480
    }
481
}
482
483
function natcompare(a,b) {
484
485
    var ia = 0, ib = 0;
486
        var nza = 0, nzb = 0;
487
        var ca, cb;
488
        var result;
489
490
    while (true)
491
    {
492
        // only count the number of zeroes leading the last number compared
493
        nza = nzb = 0;
494
495
        ca = a.charAt(ia);
496
        cb = b.charAt(ib);
497
498
        // skip over leading spaces or zeros
499
        while ( isWhitespaceChar( ca ) || ca =='0' ) {
500
            if (ca == '0') {
501
                nza++;
502
            } else {
503
                // only count consecutive zeroes
504
                nza = 0;
505
            }
506
507
            ca = a.charAt(++ia);
508
        }
509
510
        while ( isWhitespaceChar( cb ) || cb == '0') {
511
            if (cb == '0') {
512
                nzb++;
513
            } else {
514
                // only count consecutive zeroes
515
                nzb = 0;
516
            }
517
518
            cb = b.charAt(++ib);
519
        }
520
521
        // process run of digits
522
        if (isDigitChar(ca) && isDigitChar(cb)) {
523
            if ((result = compareRight(a.substring(ia), b.substring(ib))) != 0) {
524
                return result;
525
            }
526
        }
527
528
        if (ca == 0 && cb == 0) {
529
            // The strings compare the same.  Perhaps the caller
530
            // will want to call strcmp to break the tie.
531
            return nza - nzb;
532
        }
533
534
        if (ca < cb) {
535
            return -1;
536
        } else if (ca > cb) {
537
            return +1;
538
        }
539
540
        ++ia; ++ib;
541
    }
542
}
543
544
// kate: space-indent on; indent-width 4; replace-tabs on;