1 /*******************************************************************************
2  * Copyright (c) 2013-2021, Andrés Martinelli <andmarti@gmail.com>             *
3  * All rights reserved.                                                        *
4  *                                                                             *
5  * This file is a part of SC-IM                                                *
6  *                                                                             *
7  * SC-IM is a spreadsheet program that is based on SC. The original authors    *
8  * of SC are James Gosling and Mark Weiser, and mods were later added by       *
9  * Chuck Martin.                                                               *
10  *                                                                             *
11  * Redistribution and use in source and binary forms, with or without          *
12  * modification, are permitted provided that the following conditions are met: *
13  * 1. Redistributions of source code must retain the above copyright           *
14  *    notice, this list of conditions and the following disclaimer.            *
15  * 2. Redistributions in binary form must reproduce the above copyright        *
16  *    notice, this list of conditions and the following disclaimer in the      *
17  *    documentation and/or other materials provided with the distribution.     *
18  * 3. All advertising materials mentioning features or use of this software    *
19  *    must display the following acknowledgement:                              *
20  *    This product includes software developed by Andrés Martinelli            *
21  *    <andmarti@gmail.com>.                                                    *
22  * 4. Neither the name of the Andrés Martinelli nor the                        *
23  *   names of other contributors may be used to endorse or promote products    *
24  *   derived from this software without specific prior written permission.     *
25  *                                                                             *
26  * THIS SOFTWARE IS PROVIDED BY ANDRES MARTINELLI ''AS IS'' AND ANY            *
27  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED   *
28  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE      *
29  * DISCLAIMED. IN NO EVENT SHALL ANDRES MARTINELLI BE LIABLE FOR ANY           *
30  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES  *
31  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;*
32  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
33  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  *
34  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE       *
35  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.           *
36  *******************************************************************************/
37 
38 /**
39  * \file cmds.c
40  * \author Andrés Martinelli <andmarti@gmail.com>
41  * \date 05/04/2021
42  * \brief TODO Write brief file description
43  */
44 
45 #include <stdlib.h>
46 #include <ctype.h>   // for isdigit
47 #include <wchar.h>
48 #include <wctype.h>
49 #include <limits.h>
50 #include "main.h"
51 #include "maps.h"
52 #include "yank.h"
53 #include "marks.h"
54 #include "cmds.h"
55 #include "buffer.h"
56 #include "tui.h"
57 #include "conf.h"    // for conf parameters
58 #include "xmalloc.h" // for scxfree
59 #include "vmtbl.h"   // for growtbl
60 #include "utils/string.h" // for add_char
61 #include "y.tab.h"   // for yyparse
62 #include "dep_graph.h"
63 #include "freeze.h"
64 #ifdef UNDO
65 #include "undo.h"
66 #endif
67 
68 void syncref(register struct enode *e);
69 extern int shall_quit;
70 char insert_edit_submode;
71 struct ent * freeents = NULL;    // keep deleted ents around before sync_refs
72 wchar_t interp_line[BUFFERSIZE];
73 extern graphADT graph;
74 extern int yyparse(void);
75 
76 int offscr_sc_rows = 0, offscr_sc_cols = 0; // off screen spreadsheet rows and columns
77 int nb_frozen_rows = 0, nb_frozen_cols = 0; // total number of frozen rows/cols
78 int nb_frozen_screenrows = 0; // screen rows occupied by those frozen rows
79 int nb_frozen_screencols = 0; // screen cols occupied by those frozen columns
80 
81 /**
82  * \brief Maintain ent strucs until they are release for deletion by sync_refs.
83  *
84  * \details This structure is used to keep ent structs around before
85  * they are actually deleted (memory freed) to allow the sync_refs
86  * routine a chance to fix the variable references. If delete flag
87  * is set, is_deleted flag of an ent is set.
88  *
89  * \param[in] p
90  * \param[in] delete
91  *
92  * \return none
93  */
mark_ent_as_deleted(register struct ent * p,int delete)94 void mark_ent_as_deleted(register struct ent * p, int delete) {
95     if (p == NULL) return;
96     if (delete) p->flags |= is_deleted;
97 
98     p->next = freeents;     /* put this ent on the front of freeents */
99     freeents = p;
100 
101     return;
102 }
103 
104 
105 /**
106  * \brief TODO Write brief description
107  *
108  * \details Iterates throw freeents (ents marked as deleted). Calls
109  * clearent for freeing ents contents memory and free ent pointer. This
110  * function should always be called at exit. This is mandatory, just in
111  * case we want to UNDO any changes.
112  * TODO: any malloc of ents should check here before asking for memory
113  *
114  * \return none
115  */
flush_saved()116 void flush_saved() {
117     register struct ent * p;
118     register struct ent * q;
119 
120     p = freeents;
121     while (p != NULL) {
122         (void) clearent(p);
123         q = p->next;
124         free(p);
125         p = q;
126     }
127     freeents = NULL;
128     return;
129 }
130 
131 
132 /**
133  * \brief TODO Write brief description
134  *
135  * \details Used to remove references to deleted struct ents.
136  *
137  * \details Note that the deleted structure must still be hanging
138  * around before the call, but not referenced by an entry in tbl.
139  *
140  * \return none
141  */
142 // TODO Improve this function such that it does not traverse the whole table
sync_refs()143 void sync_refs() {
144     int i, j;
145     register struct ent * p;
146     for (i=0; i <= maxrow; i++)
147         for (j=0; j <= maxcol; j++)
148             if ( (p = *ATBL(tbl, i, j)) && p->expr ) {
149                 syncref(p->expr);
150             }
151     return;
152 }
153 
154 
155 /**
156  * \brief TODO Write brief description
157  *
158  * Used to remove a reference to deleted a single struct ent.
159  *
160  * Note that the deleted structure must still be hanging around before the
161  * call, but not referenced by an entry in tbl.
162  *
163  * Example usage:
164  * @code
165  *     syncref(<variable>);
166  * @endcode
167  * returns: none
168  */
syncref(register struct enode * e)169 void syncref(register struct enode * e) {
170     if ( e == NULL ) {
171         return;
172     } else if ( e->op == ERR_ ) {
173         e->e.o.left = NULL;
174         e->e.o.right = NULL;
175         return;
176     } else if ( e->op == REF_ ) {
177         e->op = REF_;
178         e->e.o.left = NULL;
179         e->e.o.right = NULL;
180         return;
181     } else if (e->op & REDUCE) {
182         e->e.r.right.vp = lookat(e->e.r.right.vp->row, e->e.r.right.vp->col);
183         e->e.r.left.vp = lookat(e->e.r.left.vp->row, e->e.r.left.vp->col);
184     } else {
185         switch (e->op) {
186         case 'v':
187             if (e->e.v.vp->flags & is_deleted) {
188                 //e->op = ERR_;
189                 //e->e.o.left = NULL;
190                 //e->e.o.right = NULL;
191                 break;
192             } else if (e->e.v.vp->flags & may_sync)
193                 e->e.v.vp = lookat(e->e.v.vp->row, e->e.v.vp->col);
194             break;
195         case 'k':
196             break;
197         case '$':
198             break;
199         default:
200             syncref(e->e.o.right);
201             syncref(e->e.o.left);
202             break;
203         }
204     }
205     return;
206 }
207 
208 
209 /**
210  * \brief TODO Write brief description
211  *
212  * \param[in] col
213  * \param[in] mult
214  *
215  * \return none
216  */
deletecol(int col,int mult)217 void deletecol(int col, int mult) {
218     if (any_locked_cells(0, col, maxrow, col + mult)) {
219         sc_error("Locked cells encountered. Nothing changed");
220         return;
221     }
222 #ifdef UNDO
223     create_undo_action();
224     // here we save in undostruct, all the ents that depends on the deleted one (before change)
225     ents_that_depends_on_range(0, col, maxrow, col - 1 + mult);
226     copy_to_undostruct(0, col, maxrow, col - 1 + mult, UNDO_DEL, HANDLE_DEPS, NULL);
227     save_undo_range_shift(0, -mult, 0, col, maxrow, col - 1 + mult);
228 
229     int i;
230     for (i=col; i < col + mult; i++) {
231         add_undo_col_format(i, 'R', fwidth[i], precision[i], realfmt[i]);
232         if (col_hidden[i]) undo_hide_show(-1, i, 's', 1);
233         else undo_hide_show(-1, i, 'h', 1);
234         // TODO undo col_frozen
235     }
236 #endif
237 
238     fix_marks(0, -mult, 0, maxrow,  col + mult -1, maxcol);
239     if (!loading) yank_area(0, col, maxrow, col + mult - 1, 'c', mult);
240 
241     // do the job
242     int_deletecol(col, mult);
243 
244     // if (get_conf_int("autocalc")) EvalAll();
245 
246     if (!loading) modflg++;
247 
248 #ifdef UNDO
249     // here we save in undostruct, just the ents that depends on the deleted one (after change)
250     copy_to_undostruct(0, 0, -1, -1, UNDO_ADD, HANDLE_DEPS, NULL);
251 
252     extern struct ent_ptr * deps;
253     if (deps != NULL) free(deps);
254     deps = NULL;
255 
256     end_undo_action();
257 #endif
258     return;
259 }
260 
261 
262 /**
263  * \brief TODO Write a brief description
264  *
265  * \details Delete a column. Parameters col = column to delete
266  * multi = cmds multiplier. (commonly 1)
267  *
268  * \param[in] col
269  * \param[in] mult
270  *
271  * \return none
272  */
int_deletecol(int col,int mult)273 void int_deletecol(int col, int mult) {
274     register struct ent ** pp;
275     int r, c, i;
276 
277     while (mult--) {
278         // mark ent of column to erase with is_deleted flag
279         for (r = 0; r <= maxrow; r++) {
280             pp = ATBL(tbl, r, col);
281             if ( *pp != NULL ) {
282                 mark_ent_as_deleted(*pp, TRUE);
283                 //clearent(*pp);
284                 //free(*pp);
285                 *pp = NULL;
286             }
287         }
288 
289         rebuild_graph(); // Rebuild of graph is needed.
290         //But shouldnt! TODO
291 
292         // Copy references from right column cells to left column (which gets removed)
293         for (r = 0; r <= maxrow; r++) {
294             for (c = col; c < maxcol; c++) {
295                 pp = ATBL(tbl, r, c);
296 
297                 // nota: pp[1] = ATBL(tbl, r, c+1);
298                 if ( pp[1] != NULL ) pp[1]->col--;
299                 pp[0] = pp[1];
300             }
301 
302             // Free last column memory (Could also initialize 'ent' to zero with `cleanent`).
303             pp = ATBL(tbl, r, maxcol);
304             *pp = (struct ent *) 0;
305         }
306 
307         // Fix columns precision and width
308         for (i = col; i < maxcols - 2; i++) {
309             fwidth[i] = fwidth[i+1];
310             precision[i] = precision[i+1];
311             realfmt[i] = realfmt[i+1];
312             col_hidden[i] = col_hidden[i+1];
313             col_frozen[i] = col_frozen[i+1];
314         }
315 
316         for (; i < maxcols - 1; i++) {
317             fwidth[i] = DEFWIDTH;
318             precision[i] = DEFPREC;
319             realfmt[i] = DEFREFMT;
320             col_hidden[i] = FALSE;
321             col_frozen[i] = FALSE;
322         }
323 
324         maxcol--;
325         sync_refs();
326         EvalAll();
327         //flush_saved(); // we have to flush_saved only at exit.
328         //this is because we have to keep ents in case we want to UNDO
329     }
330 
331     return;
332 }
333 
334 
335 /**
336  * \brief TODO Write a brief description
337  *
338  * \details Copy cell (struct ent). "special" indicates special treatment
339  * when merging two cells for the "pm" command, merging formate only for
340  * the "pf" command, or for adjusting cell references when transposing
341  * with the "pt" command. r1, c1, r2, and c2 define the range in which the
342  * dr and dc values should be used. Special =='u' means special copy from
343  * spreadsheet to undo struct. Since its mandatory to make isolated copies
344  * of p->expr->e.o.right.e.v.vp and p->expr->e.o.right.e.v.vp
345  *
346  * \param[in] n
347  * \param[in] p
348  * \param[in] dr
349  * \param[in] dc
350  * \param[in] r1
351  * \param[in] c1
352  * \param[in] r2
353  * \param[in] c2
354  * \param[in] special
355  * \return none
356  */
copyent(register struct ent * n,register struct ent * p,int dr,int dc,int r1,int c1,int r2,int c2,int special)357 void copyent(register struct ent * n, register struct ent * p, int dr, int dc, int r1, int c1, int r2, int c2, int special) {
358     if (!n || !p) {
359         sc_error("copyent: internal error");
360         return;
361     }
362 
363     n->flags = may_sync;
364     if (p->flags & is_deleted)
365         n->flags |= is_deleted;
366 
367     if (special != 'f') {
368         if (p->flags & is_valid) {
369             n->v = p->v;
370             n->flags |= p->flags & is_valid;
371         }
372         if (special != 'v' && p->expr && special != 'u') {
373             n->expr = copye(p->expr, dr, dc, r1, c1, r2, c2, special == 't');
374 #ifdef UNDO
375         } else if (special == 'u' && p->expr) { // from spreadsheet to undo
376             n->expr = copye(p->expr, dr, dc, r1, c1, r2, c2, 2);
377 #endif
378         }
379         if (p->expr && p->flags & is_strexpr)
380             n->flags |= is_strexpr;
381         else if (p->expr)
382             n->flags &= ~is_strexpr;
383 
384         if (p->label) {
385             if (n->label) scxfree(n->label);
386             n->label = scxmalloc((unsigned) (strlen(p->label) + 1));
387             (void) strcpy(n->label, p->label);
388             n->flags &= ~is_leftflush;
389             n->flags |= ((p->flags & is_label) | (p->flags & is_leftflush));
390         }
391         n->flags |= p->flags & is_locked;
392     }
393     if (p->format && special != 'v') {
394         if (n->format) scxfree(n->format);
395             n->format = scxmalloc((unsigned) (strlen(p->format) + 1));
396         (void) strcpy(n->format, p->format);
397     } else if (special != 'v' && special != 'f')
398         n->format = NULL;
399 
400     if (special != 'v') {
401         n->pad = p->pad;
402         if (n->ucolor) { // remove current cellcolor format in n ent
403             free(n->ucolor);
404             n->ucolor = NULL;
405         }
406         if (p->ucolor) { // copy new cellcolor format from p to n ent
407             n->ucolor = (struct ucolor *) malloc (sizeof(struct ucolor));
408             n->ucolor->fg = p->ucolor->fg;
409             n->ucolor->bg = p->ucolor->bg;
410             n->ucolor->bold = p->ucolor->bold;
411             n->ucolor->italic = p->ucolor->italic;
412             n->ucolor->dim = p->ucolor->dim;
413             n->ucolor->reverse = p->ucolor->reverse;
414             n->ucolor->standout = p->ucolor->standout;
415             n->ucolor->underline = p->ucolor->underline;
416             n->ucolor->blink = p->ucolor->blink;
417         }
418     }
419 
420     // used for undoing / redoing cells that has errors
421     n->cellerror = p->cellerror;
422 
423     if (special == 'c' && n->expr)
424         EvalJustOneVertex(n, 0);
425 
426     n->flags |= is_changed;
427     n->row = p->row;
428     n->col = p->col;
429     return;
430 }
431 
432 
433 /**
434  * \brief TODO Write brief description
435  * \return NUM; STR; etc.
436  */
etype(register struct enode * e)437 int etype(register struct enode *e) {
438     if (e == (struct enode *)0)
439         return NUM;
440     switch (e->op) {
441         case UPPER: case LOWER: case CAPITAL:
442         case O_SCONST: case '#': case DATE: case FMT: case STINDEX:
443         case EXT: case LUA: case SVAL: case SUBSTR:
444             return (STR);
445 
446         case '?':
447         case IF:
448             return (etype(e->e.o.right->e.o.left));
449 
450         case 'f':
451             return (etype(e->e.o.right));
452 
453         case O_VAR: {
454             register struct ent *p;
455             p = e->e.v.vp;
456             if (p->expr)
457                 return (p->flags & is_strexpr ? STR : NUM);
458             else if (p->label)
459                 return (STR);
460             else
461                 return (NUM);
462         }
463 
464         default:
465             return (NUM);
466     }
467     return -1;
468 }
469 
470 
471 /**
472  * \brief TODO Write a brief function description
473  * \details ignorelock is used when sorting so that locked cells
474  * can still be sorted
475  * \param[in] sr
476  * \param[in] sc
477  * \param[in] er
478  * \param[in] ec
479  * \param[in] ignorelock
480  * \param[in] mark_as_deleted
481  * \return none
482  */
erase_area(int sr,int sc,int er,int ec,int ignorelock,int mark_as_deleted)483 void erase_area(int sr, int sc, int er, int ec, int ignorelock, int mark_as_deleted) {
484     int r, c;
485     struct ent **pp;
486 
487     if (sr > er) {
488         r = sr; sr = er; er = r;
489     }
490 
491     if (sc > ec) {
492         c = sc; sc = ec; ec = c;
493     }
494 
495     if (sr < 0)
496         sr = 0;
497     if (sc < 0)
498         sc = 0;
499     checkbounds(&er, &ec);
500 
501     /* mark the ent as deleted
502      * Do a lookat() for the upper left and lower right cells of the range
503      * being erased to make sure they are included in the delete buffer so
504      * that pulling cells always works correctly even if the cells at one
505      * or more edges of the range are all empty.
506      */
507     (void) lookat(sr, sc);
508     (void) lookat(er, ec);
509     for (r = sr; r <= er; r++) {
510         for (c = sc; c <= ec; c++) {
511             pp = ATBL(tbl, r, c);
512             if (*pp && (!((*pp)->flags & is_locked) || ignorelock)) {
513 
514                 /* delete vertex in graph
515                    only if this vertex is not referenced by other */
516                 vertexT * v = getVertex(graph, *pp, 0);
517                 if (v != NULL && v->back_edges == NULL )
518                     destroy_vertex(*pp);
519 
520                 if (mark_as_deleted) {
521                     mark_ent_as_deleted(*pp, TRUE);
522                 } else {
523                     clearent(*pp); // free memory
524                     cleanent(*pp); // fill ent with empty values
525                     mark_ent_as_deleted(*pp, FALSE);
526                 }
527                 *pp = NULL;
528             }
529         }
530     }
531     return;
532 }
533 
534 
535 /**
536  * \brief TODO Write a brief function description
537  * \details Function to copy an expression. It returns the copy.
538  * special = 1 means transpose
539  * special = 2 means copy from spreadsheet to undo struct
540  * \param[in] e
541  * \param[in] Rdelta
542  * \param[in] Cdelta
543  * \param[in] r1
544  * \param[in] c1
545  * \param[in] r2
546  * \param[in] c2
547  * \param[in] special
548  * \return none
549  */
copye(register struct enode * e,int Rdelta,int Cdelta,int r1,int c1,int r2,int c2,int special)550 struct enode * copye(register struct enode *e, int Rdelta, int Cdelta, int r1, int c1, int r2, int c2, int special) {
551     register struct enode * ret;
552     static struct enode * range = NULL;
553 
554     if (e == (struct enode *) 0) {
555         ret = (struct enode *) 0;
556 
557     } else if (e->op & REDUCE) {
558         int newrow, newcol;
559         ret = (struct enode *) scxmalloc((unsigned) sizeof (struct enode));
560         ret->op = e->op;
561         ret->e.r.left.expr = e->e.r.left.expr ? copye(e->e.r.left.expr, Rdelta, Cdelta, r1, c1, r2, c2, special) : NULL; // important to initialize
562         ret->e.r.right.expr = e->e.r.right.expr ? copye(e->e.r.right.expr, Rdelta, Cdelta, r1, c1, r2, c2, special) : NULL; // important to initialize
563         newrow = e->e.r.left.vf & FIX_ROW || e->e.r.left.vp->row < r1 || e->e.r.left.vp->row > r2 || e->e.r.left.vp->col < c1 || e->e.r.left.vp->col > c2 ?  e->e.r.left.vp->row : special == 1 ? r1 + Rdelta + e->e.r.left.vp->col - c1 : e->e.r.left.vp->row + Rdelta;
564         newcol = e->e.r.left.vf & FIX_COL || e->e.r.left.vp->row < r1 || e->e.r.left.vp->row > r2 || e->e.r.left.vp->col < c1 || e->e.r.left.vp->col > c2 ?  e->e.r.left.vp->col : special == 1 ? c1 + Cdelta + e->e.r.left.vp->row - r1 : e->e.r.left.vp->col + Cdelta;
565         ret->e.r.left.vp = lookat(newrow, newcol);
566         ret->e.r.left.vf = e->e.r.left.vf;
567         newrow = e->e.r.right.vf & FIX_ROW || e->e.r.right.vp->row < r1 || e->e.r.right.vp->row > r2 || e->e.r.right.vp->col < c1 || e->e.r.right.vp->col > c2 ?  e->e.r.right.vp->row : special == 1 ? r1 + Rdelta + e->e.r.right.vp->col - c1 : e->e.r.right.vp->row + Rdelta;
568         newcol = e->e.r.right.vf & FIX_COL || e->e.r.right.vp->row < r1 || e->e.r.right.vp->row > r2 || e->e.r.right.vp->col < c1 || e->e.r.right.vp->col > c2 ?  e->e.r.right.vp->col : special == 1 ? c1 + Cdelta + e->e.r.right.vp->row - r1 : e->e.r.right.vp->col + Cdelta;
569         ret->e.r.right.vp = lookat(newrow, newcol);
570         ret->e.r.right.vf = e->e.r.right.vf;
571     } else {
572         struct enode *temprange=0;
573         ret = (struct enode *) scxmalloc((unsigned) sizeof (struct enode));
574         ret->e.r.left.expr = e->e.r.left.expr ? copye(e->e.r.left.expr, Rdelta, Cdelta, r1, c1, r2, c2, special) : NULL; // important to initialize
575         ret->e.r.right.expr = e->e.r.right.expr ? copye(e->e.r.right.expr, Rdelta, Cdelta, r1, c1, r2, c2, special) : NULL; // important to initialize
576         ret->e.r.left.vp = e->e.r.left.vp;
577         ret->e.r.right.vp = e->e.r.right.vp;
578         ret->op = e->op;
579         switch (ret->op) {
580             case SUM:
581             case PROD:
582             case AVG:
583             case COUNT:
584             case STDDEV:
585             case MAX:
586             case MIN:
587                 temprange = range;
588                 range = e->e.o.left;
589                 r1 = 0;
590                 c1 = 0;
591                 r2 = maxrow;
592                 c2 = maxcol;
593         }
594         switch (ret->op) {
595             case 'v':
596                 {
597                     int newrow, newcol;
598                     if (range && e->e.v.vp->row >= range->e.r.left.vp->row && e->e.v.vp->row <= range->e.r.right.vp->row && e->e.v.vp->col >= range->e.r.left.vp->col && e->e.v.vp->col <= range->e.r.right.vp->col) {
599                         newrow = range->e.r.left.vf & FIX_ROW ? e->e.v.vp->row : e->e.v.vp->row + Rdelta;
600                         newcol = range->e.r.left.vf & FIX_COL ? e->e.v.vp->col : e->e.v.vp->col + Cdelta;
601                     } else {
602                         newrow = e->e.v.vf & FIX_ROW || e->e.v.vp->row < r1 || e->e.v.vp->row > r2 || e->e.v.vp->col < c1 || e->e.v.vp->col > c2 ?  e->e.v.vp->row : special == 1 ? r1 + Rdelta + e->e.v.vp->col - c1 : e->e.v.vp->row + Rdelta;
603                         newcol = e->e.v.vf & FIX_COL || e->e.v.vp->row < r1 || e->e.v.vp->row > r2 || e->e.v.vp->col < c1 || e->e.v.vp->col > c2 ?  e->e.v.vp->col : special == 1 ? c1 + Cdelta + e->e.v.vp->row - r1 : e->e.v.vp->col + Cdelta;
604                     }
605                     ret->e.v.vp = lookat(newrow, newcol);
606                     ret->e.v.vf = e->e.v.vf;
607                     break;
608                 }
609             case 'k':
610                 ret->e.k = e->e.k;
611                 break;
612             case 'f':
613             case 'F':
614                 if ((range && ret->op == 'F') || (!range && ret->op == 'f'))
615                     Rdelta = Cdelta = 0;
616                 ret->e.o.left = copye(e->e.o.left, Rdelta, Cdelta, r1, c1, r2, c2, special);
617                 ret->e.o.right = (struct enode *)0;
618                 break;
619             case '$':
620             case EXT:
621                 ret->e.s = scxmalloc((unsigned) strlen(e->e.s)+1);
622                 (void) strcpy(ret->e.s, e->e.s);
623                 if (e->op == '$')    /* Drop through if ret->op is EXT */
624                     break;
625             default:
626                 if (e->op == ERR_) {
627                     //ret->e.o.left = NULL;
628                     //ret->e.o.right = NULL;
629                     //ret->e.o.right = (struct enode *)0;
630                     break; /* fix #108 */
631                 }
632                 ret->e.o.left = copye(e->e.o.left, Rdelta, Cdelta, r1, c1, r2, c2, special);
633                 ret->e.o.right = copye(e->e.o.right, Rdelta, Cdelta, r1, c1, r2, c2, special);
634                 break;
635         }
636         switch (ret->op) {
637             case SUM:
638             case PROD:
639             case AVG:
640             case COUNT:
641             case STDDEV:
642             case MAX:
643             case MIN:
644                 range = temprange;
645         }
646     }
647     return ret;
648 }
649 
650 
651 /**
652  * \brief dorowformat()
653  * \details: apply a row format in lines(size) to a row (r)
654  * \param[in] r
655  * \param[in] size
656  * \return none
657  */
dorowformat(int r,unsigned char size)658 void dorowformat(int r, unsigned char size) {
659     if (size < 1 || size > UCHAR_MAX || size > LINES - RESROW - 1) { sc_error("Invalid row format"); return; }
660 
661     if (r >= maxrows && !growtbl(GROWROW, 0, r)) r = maxrows-1 ;
662     checkbounds(&r, &curcol);
663     row_format[r] = size;
664     if (! loading) modflg++;
665     return;
666 }
667 
668 
669 /**
670  * \brief TODO Write brief function description
671  * \details Note: Modified 9/17/90 THA to handle more formats.
672  * \param[in] c1
673  * \param[in] c2
674  * \param[in] w
675  * \param[in] p
676  * \param[in] r
677  * \return none
678  */
doformat(int c1,int c2,int w,int p,int r)679 void doformat(int c1, int c2, int w, int p, int r) {
680     register int i;
681     int crows = 0;
682     int ccols = c2;
683 
684     if (c1 >= maxcols && !growtbl(GROWCOL, 0, c1)) c1 = maxcols-1 ;
685     if (c2 >= maxcols && !growtbl(GROWCOL, 0, c2)) c2 = maxcols-1 ;
686 
687     if (w == 0) {
688         sc_info("Width too small - setting to 1");
689         w = 1;
690     }
691 
692     if (! get_conf_int("nocurses") && w > COLS - rescol - 2) {
693         sc_info("Width too large - Maximum = %d", COLS - rescol - 2);
694         w = COLS - rescol - 2;
695     }
696 
697     if (p > w) {
698         sc_info("Precision too large");
699         p = w;
700     }
701 
702     checkbounds(&crows, &ccols);
703     if (ccols < c2) {
704         sc_error("Format statement failed to create implied column %d", c2);
705         return;
706     }
707 
708     for (i = c1; i <= c2; i++)
709         fwidth[i] = w, precision[i] = p, realfmt[i] = r;
710 
711     modflg++;
712     return;
713 
714 }
715 
716 
717 /**
718  * \brief TODO Document formatcol)
719  * \param[in] c
720  * \return none
721  */
formatcol(int c)722 void formatcol(int c) {
723     int arg = 1;
724     int i;
725 
726     switch (c) {
727         case '<':
728         case 'h':
729         case OKEY_LEFT:
730             for (i = curcol; i < curcol + arg; i++) {
731                 if (fwidth[i] <= 2) {
732                     sc_error("Cannot resize column any longer");
733                     return;
734                 }
735                 fwidth[i]--;
736                 if (fwidth[i] <= 1)
737                     fwidth[i] = 1;
738             }
739             modflg++;
740             break;
741         case '>':
742         case 'l':
743         case OKEY_RIGHT:
744             for (i = curcol; i < curcol + arg; i++) {
745                 fwidth[i]++;
746                 if (fwidth[i] > COLS - rescol - 2)
747                     fwidth[i] = COLS - rescol - 2;
748             }
749             modflg++;
750             break;
751         case '-':
752             for (i = curcol; i < curcol + arg; i++) {
753                 precision[i]--;
754                 if (precision[i] < 0)
755                     precision[i] = 0;
756             }
757             modflg++;
758             break;
759         case '+':
760             for (i = curcol; i < curcol + arg; i++)
761                 precision[i]++;
762             modflg++;
763             break;
764     }
765     sc_info("Current format is %d %d %d", fwidth[curcol], precision[curcol], realfmt[curcol]);
766     ui_update(TRUE);
767     return;
768 }
769 
770 
771 /**
772  * \brief TODO Document insert_row()
773  * \details Insert a single rox. It will be inserted before currow.
774  * if after is 0; after if it is 1.
775  * \param[in] after
776  * \returnsnone
777  */
insert_row(int after)778 void insert_row(int after) {
779     int r, c;
780     struct ent ** tmprow, ** pp, ** qq;
781     struct ent * p;
782     int lim = maxrow - currow + 1;
783 
784     if (currow > maxrow) maxrow = currow;
785     maxrow++;
786     lim = maxrow - lim + after;
787     if (maxrow >= maxrows && ! growtbl(GROWROW, maxrow, 0)) return;
788 
789     tmprow = tbl[maxrow];
790     for (r = maxrow; r > lim; r--) {
791         row_hidden[r] = row_hidden[r-1];
792         row_frozen[r] = row_frozen[r-1];
793         row_format[r] = row_format[r-1];
794         tbl[r] = tbl[r-1];
795         for (c = 0, pp = ATBL(tbl, r, 0); c < maxcols; c++, pp++)
796             if (*pp) (*pp)->row = r;
797     }
798     tbl[r] = tmprow;        // the last row is never used
799     row_format[r] = 1;
800 
801     // if padding exists in the old currow, we copy it to the new row!
802     for (c = 0; c < maxcols; c++) {
803         if (r >= 0 && (qq = ATBL(tbl, r+1, c)) && (*qq) && (*qq)->pad) {
804             p = lookat(r, c);
805             p->pad = (*qq)->pad;
806         }
807     }
808 
809     modflg++;
810     return;
811 }
812 
813 
814 /**
815  * \brief Insert new column
816  * \details Insert a cingle column. The column will be inserted
817  * BEFORE CURCOL if after is 0;
818  * AFTER CURCOL if it is 1.
819  * \param[in] after
820  * \return none
821  */
insert_col(int after)822 void insert_col(int after) {
823     int r, c;
824     register struct ent ** pp, ** qq;
825     struct ent * p;
826     int lim = maxcol - curcol - after + 1;
827 
828     if (curcol + after > maxcol)
829         maxcol = curcol + after;
830     maxcol++;
831 
832     if ((maxcol >= maxcols) && !growtbl(GROWCOL, 0, maxcol))
833         return;
834 
835     for (c = maxcol; c >= curcol + after + 1; c--) {
836         fwidth[c] = fwidth[c-1];
837         precision[c] = precision[c-1];
838         realfmt[c] = realfmt[c-1];
839         col_hidden[c] = col_hidden[c-1];
840         col_frozen[c] = col_frozen[c-1];
841     }
842     for (c = curcol + after; c - curcol - after < 1; c++) {
843         fwidth[c] = DEFWIDTH;
844         precision[c] =  DEFPREC;
845         realfmt[c] = DEFREFMT;
846         col_hidden[c] = FALSE;
847         col_frozen[c] = FALSE;
848     }
849 
850     for (r=0; r <= maxrow; r++) {
851         pp = ATBL(tbl, r, maxcol);
852         for (c = lim; --c >= 0; pp--)
853             if ((pp[0] = pp[-1])) pp[0]->col++;
854 
855         pp = ATBL(tbl, r, curcol + after);
856         for (c = curcol + after; c - curcol - after < 1; c++, pp++)
857             *pp = (struct ent *) 0;
858     }
859 
860     // if padding exists in the old curcol, we copy it to the new col!
861     for (r = 0; r < maxrows; r++) {
862         if (c >= 0 && (qq = ATBL(tbl, r, c+1)) && (*qq) && (*qq)->pad) {
863             p = lookat(r, c);
864             p->pad = (*qq)->pad;
865         }
866     }
867 
868     curcol += after;
869     modflg++;
870     return;
871 }
872 
873 
874 /**
875  * \brief Delete a row
876  * \param[in] row
877  * \param[in] mult
878  * \return none
879  */
deleterow(int row,int mult)880 void deleterow(int row, int mult) {
881     if (any_locked_cells(row, 0, row + mult - 1, maxcol)) {
882         sc_error("Locked cells encountered. Nothing changed");
883         return;
884     }
885 #ifdef UNDO
886     create_undo_action();
887     // here we save in undostruct, all the ents that depends on the deleted one (before change)
888     ents_that_depends_on_range(row, 0, row + mult - 1, maxcol);
889     copy_to_undostruct(row, 0, row + mult - 1, maxcol, UNDO_DEL, HANDLE_DEPS, NULL);
890     save_undo_range_shift(-mult, 0, row, 0, row - 1 + mult, maxcol);
891     int i;
892     for (i=row; i < row + mult; i++) {
893         add_undo_row_format(i, 'R', row_format[currow]);
894         if (row_hidden[i]) undo_hide_show(i, -1, 's', 1);
895         //else undo_hide_show(i, -1, 'h', 1);
896     }
897 #endif
898 
899     fix_marks(-mult, 0, row + mult - 1, maxrow, 0, maxcol);
900     if (!loading) yank_area(row, 0, row + mult - 1, maxcol, 'r', mult);
901 
902     // do the job
903     int_deleterow(row, mult);
904 
905     //flush_saved(); // we have to flush only at exit. this is in case we want to UNDO
906 
907     if (!loading) modflg++;
908 
909 #ifdef UNDO
910     // here we save in undostruct, just the ents that depends on the deleted one (after the change)
911     copy_to_undostruct(0, 0, -1, -1, UNDO_ADD, HANDLE_DEPS, NULL);
912 
913     extern struct ent_ptr * deps;
914     if (deps != NULL) free(deps);
915     deps = NULL;
916 
917     end_undo_action();
918 #endif
919     return;
920 }
921 
922 
923 /**
924  * \brief Delete a row
925  * \details Delete a row - internal function
926  * \param[in] row row to delete
927  * \param[in] multi commands multiplier (usually 1)
928  * \return none
929  */
int_deleterow(int row,int mult)930 void int_deleterow(int row, int mult) {
931     register struct ent ** pp;
932     register struct ent * q;
933     int r, c;
934 
935     //if (currow > maxrow) return;
936 
937     while (mult--) {
938         // we need to decrease row of the row that we delete if
939         // other cells refers to this one.
940         for (c = 0; c < maxcols; c++) {
941             if (row <= maxrow) {
942                 pp = ATBL(tbl, row, c);
943                 if ((q = *ATBL(tbl, row, c)) != NULL && q->row > 0) q->row--;
944             }
945         }
946         sync_refs();
947 
948         // and after that the erase_area of the deleted row
949         erase_area(row, 0, row, maxcol, 0, 1); //important: this mark the ents as deleted
950 
951         // and we decrease ->row of all rows after the deleted one
952         for (r = row; r < maxrows - 1; r++) {
953             for (c = 0; c < maxcols; c++) {
954                 if (r <= maxrow) {
955                     pp = ATBL(tbl, r, c);
956                     pp[0] = *ATBL(tbl, r+1, c);
957                     if ( pp[0] ) pp[0]->row--;
958                 }
959             }
960             //update row_hidden and row_format here
961             row_hidden[r] = row_hidden[r+1];
962             row_frozen[r] = row_frozen[r+1];
963             row_format[r] = row_format[r+1];
964         }
965 
966         rebuild_graph(); //TODO CHECK HERE WHY REBUILD IS NEEDED. See NOTE1 in shift.c
967         sync_refs();
968         EvalAll();
969         maxrow--;
970     }
971     return;
972 }
973 
974 
975 /**
976  * \brief Document ljustify()
977  * \param[in] sr
978  * \param[in] sc
979  * \param[in] er
980  * \param[in] ec
981  * \return none
982  */
ljustify(int sr,int sc,int er,int ec)983 void ljustify(int sr, int sc, int er, int ec) {
984     struct ent *p;
985     int i, j;
986 
987     if (sr > er) {
988         i = sr;
989         sr = er;
990         er = i;
991     }
992     if (sc > ec) {
993         i = sc;
994         sc = ec;
995         ec = i;
996     }
997     for (i = sr; i <= er; i++) {
998         for (j = sc; j <= ec; j++) {
999             p = *ATBL(tbl, i, j);
1000             if (p && p->label) {
1001                 p->flags &= ~is_label;
1002                 p->flags |= is_leftflush | is_changed;
1003                 modflg++;
1004             }
1005         }
1006     }
1007     return;
1008 }
1009 
1010 
1011 /**
1012  * \brief TODO Document rjustify()
1013  * \param[in] sr
1014  * \param[in] sc
1015  * \param[in] er
1016  * \param[in] ec
1017  * \return none
1018  */
rjustify(int sr,int sc,int er,int ec)1019 void rjustify(int sr, int sc, int er, int ec) {
1020     struct ent *p;
1021     int i, j;
1022 
1023     if (sr > er) {
1024         i = sr;
1025         sr = er;
1026         er = i;
1027     }
1028     if (sc > ec) {
1029         i = sc;
1030         sc = ec;
1031         ec = i;
1032     }
1033     for (i = sr; i <= er; i++) {
1034         for (j = sc; j <= ec; j++) {
1035             p = *ATBL(tbl, i, j);
1036             if (p && p->label) {
1037                 p->flags &= ~(is_label | is_leftflush);
1038                 p->flags |= is_changed;
1039                 modflg++;
1040             }
1041         }
1042     }
1043     return;
1044 }
1045 
1046 
1047 /**
1048  * \brief TODO Document center()
1049  * \param[in] sr
1050  * \param[in] sc
1051  * \param[in] er
1052  * \param[in] ec
1053  * \return none
1054  */
center(int sr,int sc,int er,int ec)1055 void center(int sr, int sc, int er, int ec) {
1056     struct ent *p;
1057     int i, j;
1058 
1059     if (sr > er) {
1060         i = sr;
1061         sr = er;
1062         er = i;
1063     }
1064     if (sc > ec) {
1065         i = sc;
1066         sc = ec;
1067         ec = i;
1068     }
1069     for (i = sr; i <= er; i++) {
1070         for (j = sc; j <= ec; j++) {
1071             p = *ATBL(tbl, i, j);
1072             if (p && p->label) {
1073                 p->flags &= ~is_leftflush;
1074                 p->flags |= is_label | is_changed;
1075                 modflg++;
1076             }
1077         }
1078     }
1079     return;
1080 }
1081 
1082 
1083 /**
1084  * @brief TODO Document chg_mode
1085  * \param[in] strcmd
1086  * \return none
1087  */
chg_mode(char strcmd)1088 void chg_mode(char strcmd){
1089     lastmode = curmode;
1090     switch (strcmd) {
1091         case '=':
1092             curmode = INSERT_MODE;
1093             break;
1094         case '<':
1095             curmode = INSERT_MODE;
1096             break;
1097         case '>':
1098             curmode = INSERT_MODE;
1099             break;
1100         case '\\':
1101             curmode = INSERT_MODE;
1102             break;
1103         case 'E':
1104             curmode = EDIT_MODE;
1105             break;
1106         case 'e':
1107             curmode = EDIT_MODE;
1108             break;
1109         case ':':
1110             curmode = COMMAND_MODE;
1111             break;
1112         case '.':
1113             curmode = NORMAL_MODE;
1114             break;
1115         case 'v':
1116             curmode = VISUAL_MODE;
1117             break;
1118     }
1119     return;
1120 }
1121 
1122 
1123 /**
1124  * \brief Delete selected cells
1125  * \details Delete selected cell or range of cells.
1126  * \return none
1127  */
del_selected_cells()1128 void del_selected_cells() {
1129     int tlrow = currow;
1130     int tlcol = curcol;
1131     int brrow = currow;
1132     int brcol = curcol;
1133 
1134     // is we delete a range
1135     if (is_range_selected() != -1) {
1136         srange * r = get_selected_range();
1137         tlrow = r->tlrow;
1138         tlcol = r->tlcol;
1139         brrow = r->brrow;
1140         brcol = r->brcol;
1141     }
1142 
1143     if (any_locked_cells(tlrow, tlcol, brrow, brcol)) {
1144         sc_error("Locked cells encountered. Nothing changed");
1145         return;
1146     }
1147 
1148     if (is_range_selected() != -1)
1149         yank_area(tlrow, tlcol, brrow, brcol, 'a', 1);
1150     else
1151         yank_area(tlrow, tlcol, brrow, brcol, 'e', 1);
1152 
1153 #ifdef UNDO
1154     create_undo_action();
1155     // here we save in undostruct, all the ents that depends on the deleted one (before change)
1156     ents_that_depends_on_range(tlrow, tlcol, brrow, brcol);
1157     copy_to_undostruct(tlrow, tlcol, brrow, brcol, UNDO_DEL, HANDLE_DEPS, NULL);
1158 #endif
1159 
1160     erase_area(tlrow, tlcol, brrow, brcol, 0, 0); //important: this erases the ents, but does NOT mark them as deleted
1161     modflg++;
1162     sync_refs();
1163     //flush_saved(); DO NOT UNCOMMENT! flush_saved shall not be called other than at exit.
1164 
1165     EvalRange(tlrow, tlcol, brrow, brcol);
1166 
1167 #ifdef UNDO
1168     // here we save in undostruct, all the ents that depends on the deleted one (after the change)
1169     copy_to_undostruct(tlrow, tlcol, brrow, brcol, UNDO_ADD, HANDLE_DEPS, NULL);
1170     extern struct ent_ptr * deps;
1171     if (deps != NULL) free(deps);
1172     deps = NULL;
1173     end_undo_action();
1174 #endif
1175 
1176     return;
1177 }
1178 
1179 
1180 /**
1181  * \brief Enter cell content on a cell
1182  * \details Enter cell content on a cell.
1183  * Covers commands LET, LABEL, LEFTSTRING, and RIGHTSTRING
1184  * \param[in] r
1185  * \param[in] c
1186  * \param[in] submode
1187  * \param[in] content
1188  * \return none
1189  */
enter_cell_content(int r,int c,char * submode,wchar_t * content)1190 void enter_cell_content(int r, int c, char * submode,  wchar_t * content) {
1191     (void) swprintf(interp_line, BUFFERSIZE, L"%s %s = %ls", submode, v_name(r, c), content);
1192     send_to_interp(interp_line);
1193     if (get_conf_int("autocalc") && ! loading) EvalRange(r, c, r, c);
1194 }
1195 
1196 
1197 /**
1198  * @brief Send command to interpreter
1199  * \details Send command to interpreter
1200  * wide_char version
1201  * \param[in] oper
1202  * \return none
1203  */
send_to_interp(wchar_t * oper)1204 void send_to_interp(wchar_t * oper) {
1205     if (get_conf_int("nocurses")) {
1206         int pos = -1;
1207         if ((pos = wstr_in_wstr(oper, L"\n")) != -1)
1208             oper[pos] = L'\0';
1209         sc_debug("Interp GOT: %ls", oper);
1210     }
1211     wcstombs(line, oper, BUFFERSIZE);
1212 
1213     linelim = 0;
1214     yyparse();
1215     linelim = -1;
1216     line[0]='\0';
1217     // commented on 28/04/2021. EvalAll should be used only on certain ocasions.
1218     // Use EvalRange instead when possible, and certainly not here everytime sending to interp.
1219     //if (get_conf_int("autocalc") && ! loading) EvalAll();
1220     return;
1221 }
1222 
1223 
1224 /**
1225  * \brief Return a pointer to a cell's [struct ent *]
1226  * Return a pointer to a cell's [struct ent *], creating if needed
1227  * \param[in] row
1228  * \param[in] col
1229  * \return none
1230  */
lookat(int row,int col)1231 struct ent * lookat(int row, int col) {
1232     register struct ent **pp;
1233 
1234     checkbounds(&row, &col);
1235     pp = ATBL(tbl, row, col);
1236     if ( *pp == NULL) {
1237          *pp = (struct ent *) malloc( (unsigned) sizeof(struct ent) );
1238         (*pp)->label = (char *) 0;
1239         (*pp)->flags = may_sync;
1240         (*pp)->expr = (struct enode *) 0;
1241         (*pp)->trigger = (struct trigger *) 0;
1242         (*pp)->v = (double) 0.0;
1243         (*pp)->format = (char *) 0;
1244         (*pp)->cellerror = CELLOK;
1245         (*pp)->next = NULL;
1246         (*pp)->ucolor = NULL;
1247         (*pp)->pad = 0;
1248     }
1249     (*pp)->row = row;
1250     (*pp)->col = col;
1251     if (row > maxrow) maxrow = row;
1252     if (col > maxcol) maxcol = col;
1253     return (*pp);
1254 }
1255 
1256 
1257 /**
1258  * \brief Blank an ent
1259  * \param[in] p
1260  * \return none
1261  */
cleanent(struct ent * p)1262 void cleanent(struct ent * p) {
1263     if (!p) return;
1264     p->label = (char *) 0;
1265     // do not uncomment. if so mod erase_area.
1266     //p->row = 0;
1267     //p->col = 0;
1268 
1269     p->flags = may_sync;
1270     p->expr = (struct enode *) 0;
1271     p->v = (double) 0.0;
1272     p->format = (char *) 0;
1273     p->cellerror = CELLOK;
1274     p->next = NULL;
1275     p->ucolor = NULL;
1276     p->pad = 0;
1277     return;
1278 }
1279 
1280 
1281 /**
1282  * \brief Free memory of an ent and its contents
1283  * \param[in] v
1284  * \return none
1285  */
clearent(struct ent * v)1286 void clearent(struct ent * v) {
1287     if (!v) return;
1288 
1289     label(v, "", -1);
1290     v->v = (double)0;
1291 
1292     if (v->expr) efree(v->expr);
1293     v->expr = NULL;
1294 
1295     if (v->format) scxfree(v->format);
1296     v->format = NULL;
1297 
1298     if (v->ucolor) free(v->ucolor);
1299     v->ucolor = NULL;
1300 
1301     v->flags = is_changed;
1302 
1303     modflg++;
1304 
1305     return;
1306 }
1307 
1308 
1309 /**
1310  * \brief Moves curcol back one displayed column
1311  * \param[in] arg
1312  * \return lookat
1313  */
back_col(int arg)1314 struct ent * back_col(int arg) {
1315     int c = curcol;
1316 
1317     while (--arg >= 0) {
1318         if (c) {
1319             curcol = --c;
1320         } else {
1321             sc_info ("At column A");
1322             break;
1323         }
1324         while (col_hidden[c] && c) {
1325             curcol = --c;
1326         }
1327     }
1328     if (curcol < offscr_sc_cols)
1329         offscr_sc_cols = curcol;
1330 
1331     return lookat(currow, c);
1332 }
1333 
1334 
1335 /**
1336  * \brief Moves curcol forward one displayed column
1337  * \param[in] arg
1338  * \return lookat
1339  */
forw_col(int arg)1340 struct ent * forw_col(int arg) {
1341     int c = curcol;
1342 
1343     while (--arg >= 0) {
1344         if (c < maxcols - 1) {
1345             c++;
1346         } else {
1347             if (! growtbl(GROWCOL, 0, arg)) {    /* get as much as needed */
1348                 sc_error("cannot grow");
1349                 return lookat(currow, curcol);
1350             } else {
1351                 c++;
1352             }
1353         }
1354         while (col_hidden[c] && c < maxcols - 1) {
1355             c++;
1356         }
1357 
1358     }
1359     return lookat(currow, c);
1360 }
1361 
1362 
1363 /**
1364  * \brief Move currow forward one displayed row
1365  * \param[in] arg
1366  * \return lookat
1367  */
forw_row(int arg)1368 struct ent * forw_row(int arg) {
1369     int r = currow;
1370 
1371     while (arg--) {
1372         if (r < maxrows - 1)
1373             r++;
1374         else {
1375             if (! growtbl(GROWROW, arg, 0)) {
1376                 sc_error("cannot grow");
1377                 return lookat(currow, curcol);
1378             } else
1379                 r++;
1380         }
1381         while (row_hidden[r] && r < maxrows - 1) r++;
1382     }
1383     return lookat(r, curcol);
1384 }
1385 
1386 
1387 /**
1388  * \brief Moves currow backward on displayed row
1389  * \return lookat
1390  */
back_row(int arg)1391 struct ent * back_row(int arg) {
1392     int r = currow;
1393 
1394     while (--arg >= 0) {
1395         if (r) {
1396             --r;
1397         } else {
1398             sc_info ("At row 0");
1399             break;
1400         }
1401         while (row_hidden[r] && r) --r;
1402     }
1403     return lookat(r, curcol);
1404 }
1405 
1406 
1407 /**
1408  * \brief Document scroll_down()
1409  * \param[in] n
1410  * \return none
1411  */
scroll_down(int n)1412 void scroll_down(int n) {
1413     while (n--) {
1414         int last, currow_orig = currow;
1415         /* find last mobile row */
1416         calc_mobile_rows(&last);
1417         /* move to next non-hidden non-frozen row */
1418         do {
1419             lookat(++last, curcol);
1420             if (last >= maxrows)
1421                 return;
1422         } while (row_hidden[last] || row_frozen[last]);
1423         /* this will adjust offscr_sc_rows */
1424         currow = last;
1425         calc_mobile_rows(NULL);
1426         /* restore currow */
1427         currow = currow_orig;
1428         if (currow < offscr_sc_rows)
1429             currow = offscr_sc_rows;
1430         unselect_ranges();
1431     }
1432 }
1433 
1434 
1435 /**
1436  * @brief Document scroll_up()
1437  * \param[in] n
1438  * \return none
1439  */
scroll_up(int n)1440 void scroll_up(int n) {
1441     while (n--) {
1442         int first, last, currow_orig = currow;
1443         /* move to previous non-hidden non-frozen row */
1444         first = offscr_sc_rows;
1445         do {
1446             if (--first < 0)
1447                 return;
1448         } while (row_hidden[first] || row_frozen[first]);
1449         offscr_sc_rows = first;
1450         /* find corresponding last mobile row */
1451         currow = first;
1452         calc_mobile_rows(&last);
1453         /* restore/adjust currow */
1454         currow = currow_orig;
1455         if (currow > last)
1456             currow = last;
1457         unselect_ranges();
1458     }
1459 }
1460 
1461 
1462 /**
1463  * \brief TODO Document go_home()
1464  * \return lookat
1465  */
go_home()1466 struct ent * go_home() {
1467     return lookat(0, 0);
1468 }
1469 
1470 
1471 /**
1472  * \brief vert_top() - for command H in normal mode
1473  * \return lookat
1474  */
vert_top()1475 struct ent * vert_top() {
1476     int r = offscr_sc_rows;
1477     while (row_hidden[r] || row_frozen[r]) r++;
1478     return lookat(r, curcol);
1479 }
1480 
1481 
1482 /**
1483  * \brief vert_bottom() - for command L in normal mode
1484  * \return lookat
1485  */
vert_bottom()1486 struct ent * vert_bottom() {
1487     int last;
1488     calc_mobile_rows(&last);
1489     return lookat(last, curcol);
1490 }
1491 
1492 
1493 /**
1494  * \brief vert_middle() - for command M in normal mode
1495  * \return lookat
1496  */
vert_middle()1497 struct ent * vert_middle() {
1498     int i;
1499     int midscreen_pos = (LINES - RESROW - RESCOLHEADER - 1)/2;
1500     int curr_pos = 0;
1501     int mobile_rows = calc_mobile_rows(NULL);
1502 
1503     for (i = 0; i < maxrows; i++) {
1504         if (row_hidden[i])
1505             continue;
1506         if (! row_frozen[i]) {
1507             if (i < offscr_sc_rows)
1508                 continue;
1509             if (--mobile_rows < 0)
1510                 continue;
1511         }
1512 
1513         /* compare middle of current row against middle of screen */
1514         if (curr_pos + (row_format[i] - 1)/2 >= midscreen_pos)
1515             return lookat(i, curcol);
1516 
1517         curr_pos += row_format[i];
1518     }
1519     return NULL;
1520 }
1521 
1522 
1523 /**
1524  * \brief go_end(): go to last valid cell of grid
1525  * \return lookat; NULL otherwise
1526  */
go_end()1527 struct ent * go_end() {
1528     int r = 0, c = 0;
1529     int raux = r, caux = c;
1530     register struct ent *p;
1531     do {
1532         if (c < maxcols - 1)
1533             c++;
1534         else {
1535             if (r < maxrows - 1) {
1536                 r++;
1537                 c = 0;
1538             } else break;
1539         }
1540         if (VALID_CELL(p, r, c) && ! col_hidden[c] && ! row_hidden[r]) { raux = r; caux = c; }
1541     } while ( r < maxrows || c < maxcols );
1542     if ( ! VALID_CELL(p, r, c) && ! col_hidden[c] && ! row_hidden[r] )
1543         return lookat(raux, caux);
1544     return NULL;
1545 }
1546 
1547 
1548 /**
1549  * \brief TODO Document tick()
1550  * \details if ticks a cell, returns struct ent *
1551  * if ticks a range, return struct ent * to top left cell
1552  * \return lookat; NULL otherwise
1553  */
tick(char ch)1554 struct ent * tick(char ch) {
1555     int r, c;
1556     struct mark * m = get_mark(ch);
1557     //tick cell
1558     r = m->row;
1559 
1560     if (r != -1) {
1561         checkbounds(&r, &curcol);
1562         return lookat(r, m->col);
1563     }
1564 
1565     // tick range
1566     if (curmode != VISUAL_MODE) {
1567         r = m->rng->tlrow;
1568         c = m->rng->tlcol;
1569         m->rng->selected = 1;
1570         checkbounds(&r, &c);
1571         return lookat(r, c);
1572     }
1573     return NULL;
1574 }
1575 
1576 
1577 /**
1578  * \brief TODO  Document scroll_right()
1579  * \param[in] n
1580  * \return none
1581  */
scroll_right(int n)1582 void scroll_right(int n) {
1583     while (n--) {
1584         int last, curcol_orig = curcol;
1585         /* find last mobile column */
1586         calc_mobile_cols(&last);
1587         /* move to next non-hidden non-frozen column */
1588         do {
1589             lookat(currow, ++last);
1590             if (last >= maxcols)
1591                 return;
1592         } while (col_hidden[last] || col_frozen[last]);
1593         /* this will adjust offscr_sc_cols */
1594         curcol = last;
1595         calc_mobile_cols(NULL);
1596         /* restore curcol */
1597         curcol = curcol_orig;
1598         if (curcol < offscr_sc_cols)
1599             curcol = offscr_sc_cols;
1600         unselect_ranges();
1601     }
1602 }
1603 
1604 /**
1605  * @brief TODO Document scroll_left()
1606  * \param[in] n
1607  * \return none
1608  */
scroll_left(int n)1609 void scroll_left(int n) {
1610     while (n--) {
1611         int first, last, curcol_orig = curcol;
1612         /* move to previous non-hidden non-frozen column */
1613         first = offscr_sc_cols;
1614         do {
1615             if (--first < 0)
1616                 return;
1617         } while (col_hidden[first] || col_frozen[first]);
1618         offscr_sc_cols = first;
1619         /* find corresponding last mobile column */
1620         curcol = first;
1621         calc_mobile_cols(&last);
1622         /* restore/adjust curcol */
1623         curcol = curcol_orig;
1624         if (curcol > last)
1625             curcol = last;
1626         unselect_ranges();
1627     }
1628 }
1629 
1630 
1631 /**
1632  * \brief TODO Document left_limit()
1633  *
1634  * \return lookat
1635  */
left_limit()1636 struct ent * left_limit() {
1637     int c = 0;
1638     while ( col_hidden[c] && c < curcol ) c++;
1639     return lookat(currow, c);
1640 }
1641 
1642 
1643 /**
1644  * \brief right_limit()
1645  * \details get the last valid cell to the right
1646  * \param[in] row where to check
1647  * \return lookat
1648  */
right_limit(int row)1649 struct ent * right_limit(int row) {
1650     register struct ent *p;
1651     int c = maxcols - 1;
1652     while ( (! VALID_CELL(p, row, c) && c > 0) || col_hidden[c]) c--;
1653     return lookat(row, c);
1654 }
1655 
1656 
1657 /**
1658  * \brief TODO Document goto_top()
1659  *
1660  * \return lookat
1661  */
goto_top()1662 struct ent * goto_top() {
1663     int r = 0;
1664     while ( row_hidden[r] && r < currow ) r++;
1665     return lookat(r, curcol);
1666 }
1667 
1668 
1669 /**
1670  * \brief TODO Document goto_bottom()
1671  *
1672  * \return lookat
1673  */
1674 // FIXME to handle freeze rows/cols
goto_bottom()1675 struct ent * goto_bottom() {
1676     register struct ent *p;
1677     int r = maxrows - 1;
1678     while ( (! VALID_CELL(p, r, curcol) && r > 0) || row_hidden[r]) r--;
1679     return lookat(r, curcol);
1680 }
1681 
1682 
1683 /**
1684  * \brief TODO Document goto_last_col()
1685  * traverse the table and see which is the max column that has content
1686  * this is because maxcol changes when moving cursor.
1687  * this function is used when exporting files
1688  * \return lookat
1689  */
goto_last_col()1690 struct ent * goto_last_col() {
1691     int r, mr = maxrows;
1692     int c, mc = 0;
1693     register struct ent *p;
1694     int rf = 0;
1695 
1696     for (r=0; r<mr; r++) {
1697         for (c=0; c<maxcols; c++) {
1698             if (c >= mc && VALID_CELL(p, r, c)) { mc = c; rf = r; }
1699         }
1700     }
1701     return lookat(rf, mc);
1702 }
1703 
1704 
1705 /**
1706  * @brief TODO Document go_forward()
1707  *
1708  * \return lookat
1709  */
go_forward()1710 struct ent * go_forward() {
1711     int r = currow, c = curcol;
1712     int r_ori = r, c_ori = c;
1713     register struct ent * p;
1714     do {
1715         if (c < maxcols - 1) {
1716             c++;
1717         } else {
1718             if (r < maxrows - 1) {
1719                 r++;
1720                 c = 0;
1721             } else break;
1722         }
1723         if (VALID_CELL(p, r, c) && ! col_hidden[c] && ! row_hidden[r] )
1724             return lookat(r, c);
1725     } while (r < maxrows || c < maxcols);
1726 
1727     return lookat(r_ori, c_ori);
1728 }
1729 
1730 
1731 /**
1732  * \brief TODO Document go_bol()
1733  *
1734  * \return lookat
1735  */
go_bol()1736 struct ent * go_bol() {
1737     return lookat(currow, offscr_sc_cols);
1738 }
1739 
1740 
1741 /**
1742  * \brief TODO Document go_eol()
1743  * \return none
1744  */
go_eol()1745 struct ent * go_eol() {
1746     int last_col;
1747     calc_mobile_cols(&last_col);
1748     return lookat(currow, last_col);
1749 }
1750 
1751 
1752 /**
1753  * \brief TODO Document horiz_middle()
1754  * \return lookat; NULL otherwise
1755  */
horiz_middle()1756 struct ent * horiz_middle() {
1757     int i;
1758     int midscreen_pos = (COLS - rescol - 1)/2;
1759     int curr_pos = 0;
1760     int mobile_cols = calc_mobile_cols(NULL);
1761 
1762     for (i = 0; i < maxcols; i++) {
1763         if (col_hidden[i])
1764             continue;
1765         if (! col_frozen[i]) {
1766             if (i < offscr_sc_cols)
1767                 continue;
1768             if (--mobile_cols < 0)
1769                 continue;
1770         }
1771 
1772         /* compare middle of current col against middle of screen */
1773         if (curr_pos + (fwidth[i] - 1)/2 >= midscreen_pos)
1774             return lookat(currow, i);
1775 
1776         curr_pos += fwidth[i];
1777     }
1778     return NULL;
1779 }
1780 
1781 
1782 /**
1783  * \brief TODO Document go_backward()
1784  *
1785  * \return lookat
1786  */
go_backward()1787 struct ent * go_backward() {
1788     int r = currow, c = curcol;
1789     int r_ori = r, c_ori = c;
1790     register struct ent * p;
1791     do {
1792         if (c)
1793             c--;
1794         else {
1795             if (r) {
1796                 r--;
1797                 c = maxcols - 1;
1798             } else break;
1799         }
1800         if ( VALID_CELL(p, r, c) && ! col_hidden[c] && ! row_hidden[r] )
1801             return lookat(r, c);
1802     } while ( currow || curcol );
1803 
1804     return lookat(r_ori, c_ori);
1805 }
1806 
1807 
1808 /**
1809  * \brief TODO Document auto_justify()
1810  *
1811  * \param[in] ci
1812  * \param[in] cf
1813  * \param[in] min
1814  *
1815  * \return none
1816  */
auto_justify(int ci,int cf,int min)1817 void auto_justify(int ci, int cf, int min) {
1818     // column width is not set below the min value
1819     int r, c, sum = 0;
1820     char field[1024] = "";
1821     struct ent * p;
1822     wchar_t widestring[BUFFERSIZE] = { L'\0' };
1823     mbstate_t state;
1824     size_t result;
1825     const char * mbsptr;
1826 
1827 #ifdef UNDO
1828     create_undo_action();
1829 #endif
1830 
1831     checkbounds(&maxrow, &cf);
1832 
1833     for (c = ci; c <= cf; c++) {
1834 #ifdef UNDO
1835         add_undo_col_format(c, 'R', fwidth[c], precision[c], realfmt[c]);
1836 #endif
1837         fwidth[c] = min;
1838         for (r = 0; r <= maxrow; r++) {
1839             if ((p = *ATBL(tbl, r, c)) != NULL) {
1840                 sum = 0;
1841                 if (p->pad) sum += p->pad;
1842                 if (p->label) {
1843                     memset( &state, '\0', sizeof state );
1844                     mbsptr = p->label;
1845                     result = mbsrtowcs(widestring, &mbsptr, BUFFERSIZE, &state);
1846                     if ( result != (size_t)-1 )
1847                         sum += wcswidth(widestring, wcslen(widestring));
1848                 }
1849                 if (p->flags & is_valid) {
1850                     sprintf(field, "%.*f", precision[c], p->v);
1851                     sum += strlen(field);
1852                 }
1853                 if (sum > fwidth[c] && sum < COLS-rescol)
1854                     fwidth[c] = sum;
1855             }
1856         }
1857 #ifdef UNDO
1858         add_undo_col_format(c, 'A', fwidth[c], precision[c], realfmt[c]);
1859 #endif
1860     }
1861 #ifdef UNDO
1862     end_undo_action();
1863 #endif
1864 
1865     return;
1866 }
1867 
1868 
1869 /**
1870  * \brief Delete a cell expression and turn into constant
1871  *
1872  * \details Deletes the expression associated with a cell and
1873  * turns it into a constant containing whatever was on the screen.
1874  *
1875  * \param[in] sr
1876  * \param[in] sc
1877  * \param[in] er
1878  * \param[in] ec
1879  *
1880  * \return none
1881  */
valueize_area(int sr,int sc,int er,int ec)1882 void valueize_area(int sr, int sc, int er, int ec) {
1883     int r, c;
1884     struct ent *p;
1885 
1886     if (sr > er) {
1887         r = sr; sr = er; er= r;
1888     }
1889 
1890     if (sc > ec) {
1891         c = sc; sc = ec; ec= c;
1892     }
1893 
1894     if (sr < 0)
1895         sr = 0;
1896     if (sc < 0)
1897         sc = 0;
1898     checkbounds(&er, &ec);
1899 
1900     #ifdef UNDO
1901     create_undo_action();
1902     copy_to_undostruct(sr, sc, er, ec, UNDO_DEL, IGNORE_DEPS, NULL);
1903     #endif
1904     for (r = sr; r <= er; r++) {
1905         for (c = sc; c <= ec; c++) {
1906             p = *ATBL(tbl, r, c);
1907             if (p && p->flags & is_locked) {
1908                 sc_error(" Cell %s%d is locked", coltoa(c), r);
1909                 continue;
1910             }
1911             if (p && p->expr) {
1912                 efree(p->expr);
1913                 p->expr = (struct enode *)0;
1914                 p->flags &= ~is_strexpr;
1915 
1916                 // TODO move this to depgraph ?
1917                 vertexT * v_cur = getVertex(graph, p, 0);
1918                 if (v_cur != NULL) { // just in case
1919 
1920                     // for each edge in edges, we look for the reference to the vertex we are deleting, and we erase it!
1921                     edgeT * e = v_cur->edges;
1922                     while (e != NULL) { // && v_cur->back_edges == NULL) {
1923                         delete_reference(v_cur, e->connectsTo, 1);
1924 
1925                         // delete vertex only if it end up having no edges, no expression, no value, no label....
1926                         if (e->connectsTo->edges == NULL && e->connectsTo->back_edges == NULL && !e->connectsTo->ent->expr && !(e->connectsTo->ent->flags & is_valid) && ! e->connectsTo->ent->label)
1927                             destroy_vertex(e->connectsTo->ent);
1928                         //     WARNING: an orphan vertex now represents an ent that has an enode thats
1929                         //     need to be evaluated, but do not depend in another cell.
1930                         e = e->next;
1931                     }
1932 
1933                     destroy_list_edges(v_cur->edges);
1934                     v_cur->edges = NULL;
1935 
1936                     /* delete vertex in graph
1937                        only if this vertex is not referenced by other */
1938                     if (v_cur->back_edges == NULL ) destroy_vertex(p);
1939                     }
1940             }
1941         }
1942     }
1943     #ifdef UNDO
1944     copy_to_undostruct(sr, sc, er, ec, UNDO_ADD, IGNORE_DEPS, NULL);
1945     end_undo_action();
1946     #endif
1947     sc_info("Removed formulas from range");
1948     return;
1949 }
1950 
1951 
1952 /**
1953  * \brief TODO Document select_inner_range()
1954  * \param[in] vir_tlrow
1955  * \param[in] vir_tlcol
1956  * \param[in] vir_brrow
1957  * \param[in] vir_brcol
1958  * \return none
1959  */
select_inner_range(int * vir_tlrow,int * vir_tlcol,int * vir_brrow,int * vir_brcol)1960 void select_inner_range(int * vir_tlrow, int * vir_tlcol, int * vir_brrow, int * vir_brcol) {
1961     struct ent * p;
1962     int rr, cc, r, c, mf = 1;
1963 
1964     while (mf != 0) {
1965         mf = 0;
1966         for (rr = *vir_tlrow; rr <= *vir_brrow; rr++) {
1967             for (cc = *vir_tlcol; cc <= *vir_brcol; cc++)
1968                 for (r=-1; r<=1; r++)
1969                     for (c=-1; c<=1; c++) {
1970                         if (r == 0 && c == 0) continue;
1971                         else if (rr + r < 0 || cc + c < 0 || rr + r > maxrow || cc + c > maxcol) continue;
1972                         p = *ATBL(tbl, rr + r, cc + c);
1973                         if ( p != NULL && (p->label || p->flags & is_valid) ) {
1974                             if (*vir_brcol < cc + c) {
1975                                 *vir_brcol = cc + c;
1976                                 mf=1;
1977                             }
1978                             if (*vir_brrow < rr + r) {
1979                                 *vir_brrow = rr + r;
1980                                 mf=1;
1981                             }
1982                             if (*vir_tlcol > cc + c) {
1983                                 *vir_tlcol = cc + c;
1984                                 mf=1;
1985                             }
1986                             if (*vir_tlrow > rr + r) {
1987                                 *vir_tlrow = rr + r;
1988                                 mf = 1;
1989                             }
1990                         }
1991                     }
1992             if (mf) break;
1993         } // rr
1994     }
1995     return;
1996 }
1997 
1998 
1999 /**
2000  * \brief Check if cell is locked
2001  *
2002  * \return 1 if cell if locked; 0 otherwise
2003  */
locked_cell(int r,int c)2004 int locked_cell(int r, int c) {
2005     struct ent *p = *ATBL(tbl, r, c);
2006     if (p && (p->flags & is_locked)) {
2007         sc_error("Cell %s%d is locked", coltoa(c), r) ;
2008         return 1;
2009     }
2010     return 0;
2011 }
2012 
2013 
2014 /**
2015  * \brief Check if area contains locked cells
2016  *
2017  * \param[in] r1
2018  * \param[in] c1
2019  * \param[in] r2
2020  * \param[in] c2
2021  *
2022  * \return 1 if area contains a locked cell; 0 otherwise
2023  */
any_locked_cells(int r1,int c1,int r2,int c2)2024 int any_locked_cells(int r1, int c1, int r2, int c2) {
2025     int r, c;
2026     struct ent * p ;
2027 
2028     for (r = r1; r <= r2; r++)
2029         for (c = c1; c <= c2; c++) {
2030             p = *ATBL(tbl, r, c);
2031             if (p && (p->flags & is_locked))
2032                 return 1;
2033         }
2034     return 0;
2035 }
2036 
2037 
2038 /**
2039  * \brief sum special command
2040  * \return none
2041  */
fsum()2042 int fsum() {
2043     int r = currow, c = curcol;
2044     struct ent * p;
2045 
2046     if (r > 0 && (*ATBL(tbl, r-1, c) != NULL) && (*ATBL(tbl, r-1, c))->flags & is_valid) {
2047         for (r = currow-1; r >= 0; r--) {
2048             p = *ATBL(tbl, r, c);
2049             if (p == NULL) break;
2050             if (! (p->flags & is_valid)) break;
2051         }
2052         if (currow != r) {
2053             swprintf(interp_line, BUFFERSIZE, L"let %s%d = @SUM(", coltoa(curcol), currow);
2054             swprintf(interp_line + wcslen(interp_line), BUFFERSIZE, L"%s%d:", coltoa(curcol), r+1);
2055             swprintf(interp_line + wcslen(interp_line), BUFFERSIZE, L"%s%d)", coltoa(curcol), currow-1);
2056         }
2057     } else if (c > 0 && (*ATBL(tbl, r, c-1) != NULL) && (*ATBL(tbl, r, c-1))->flags & is_valid) {
2058         for (c = curcol-1; c >= 0; c--) {
2059             p = *ATBL(tbl, r, c);
2060             if (p == NULL) break;
2061             if (! (p->flags & is_valid)) break;
2062         }
2063         if (curcol != c) {
2064             swprintf(interp_line, BUFFERSIZE, L"let %s%d = @SUM(", coltoa(curcol), currow);
2065             swprintf(interp_line + wcslen(interp_line), BUFFERSIZE, L"%s%d:", coltoa(c+1), r);
2066             swprintf(interp_line + wcslen(interp_line), BUFFERSIZE, L"%s%d)", coltoa(curcol-1), r);
2067         }
2068     }
2069 
2070     if ((currow != r || curcol != c) && wcslen(interp_line))
2071         send_to_interp(interp_line);
2072     EvalRange(currow, curcol, currow, curcol);
2073     return 0;
2074 }
2075 
2076 
2077 /**
2078  * \brief fcopy special command
2079  * \param[in] action
2080  * \return -1 on error; 0 otherwise
2081  */
fcopy(char * action)2082 int fcopy(char * action) {
2083     int r, ri, rf, c, ci, cf;
2084     struct ent * pdest;
2085     struct ent * pact;
2086     int p = is_range_selected();
2087     struct srange * sr = NULL;
2088     if (p != -1) sr = get_range_by_pos(p);
2089 
2090     if (p == -1) { // no range selected
2091         // fail if the cursor is on the first column
2092         if (curcol == 0) {
2093             sc_error("Can't fcopy with no arguments while on column 'A'");
2094             return -1;
2095         }
2096 
2097         ri = currow;
2098         ci = curcol;
2099         cf = curcol;
2100         for (r=ri+1; r<maxrow && (*ATBL(tbl, r, cf-1)) != NULL && (*ATBL(tbl, r, cf-1))->flags & is_valid ; r++) {}
2101         rf = --r;
2102     } else { // range is selected
2103         ri = sr->tlrow;
2104         ci = sr->tlcol;
2105         cf = sr->brcol;
2106         rf = sr->brrow;
2107     }
2108 
2109     // check if all cells that will be copied to somewhere have a formula in them
2110     if (! strcmp(action, "") || ! strcmp(action, "cells")) {
2111         if (!  *ATBL(tbl, ri, ci)       ) goto formula_not_found;
2112         if (! (*ATBL(tbl, ri, ci))->expr) goto formula_not_found;
2113     } else if (! strcmp(action, "c") || ! strcmp(action, "columns")) {
2114         for (c=ci; c<=cf; c++) {
2115             if (!  *ATBL(tbl, ri, c)       ) goto formula_not_found;
2116             if (! (*ATBL(tbl, ri, c))->expr) goto formula_not_found;
2117         }
2118     } else if (! strcmp(action, "r") || ! strcmp(action, "rows")) {
2119         for (r=ri; r<=rf; r++) {
2120             if (!  *ATBL(tbl, r, ci)       ) goto formula_not_found;
2121             if (! (*ATBL(tbl, r, ci))->expr) goto formula_not_found;
2122         }
2123     } else {
2124         sc_error("Invalid parameter");
2125     }
2126 
2127     goto all_formulas_found;
2128 
2129     formula_not_found:
2130     sc_error("At least 1 formula not found. Nothing changed");
2131     return -1;
2132 
2133     all_formulas_found:
2134 
2135     if (any_locked_cells(ri, ci, rf, cf)) {
2136         swprintf(interp_line, BUFFERSIZE, L"");
2137         sc_error("Locked cells encountered. Nothing changed");
2138         return -1;
2139     }
2140 #ifdef UNDO
2141     create_undo_action();
2142     copy_to_undostruct(ri, ci, rf, cf, UNDO_DEL, IGNORE_DEPS, NULL);
2143 #endif
2144 
2145     if (! strcmp(action, "")) {
2146         // copy first column down (old behavior), for backwards compatibility
2147         pact = *ATBL(tbl, ri, ci);
2148         for (r=ri+1; r<=rf; r++) {
2149             pdest = lookat(r, ci);
2150             copyent(pdest, pact, r - ri, 0, 0, 0, maxrows, maxcols, 'c');
2151         }
2152     } else if (! strcmp(action, "c") || ! strcmp(action, "columns")) {
2153         // copy all selected columns down
2154         for (c=ci; c<=cf; c++) {
2155             pact = *ATBL(tbl, ri, c);
2156             for (r=ri+1; r<=rf; r++) {
2157                 pdest = lookat(r, c);
2158                 copyent(pdest, pact, r - ri, 0, 0, 0, maxrows, maxcols, 'c');
2159             }
2160         }
2161     } else if (! strcmp(action, "r") || ! strcmp(action, "rows")) {
2162         // copy all selected rows right
2163         for (r=ri; r<=rf; r++) {
2164             pact = *ATBL(tbl, r, ci);
2165             for (c=ci+1; c<=cf; c++) {
2166                 pdest = lookat(r, c);
2167                 copyent(pdest, pact, 0, c - ci, 0, 0, maxrows, maxcols, 'c');
2168             }
2169         }
2170     } else if (! strcmp(action, "cells")) {
2171         // copy selected cell down and right
2172         pact = *ATBL(tbl, ri, ci);
2173         for (r=ri; r<=rf; r++) {
2174             for(c=(r==ri?ci+1:ci); c<=cf; c++) {
2175                 pdest = lookat(r, c);
2176                 copyent(pdest, pact, r - ri, c - ci, 0, 0, maxrows, maxcols, 'c');
2177             }
2178         }
2179     }
2180     EvalRange(ri, ci, rf, cf);
2181 
2182 #ifdef UNDO
2183     copy_to_undostruct(ri, ci, rf, cf, UNDO_ADD, IGNORE_DEPS, NULL);
2184     end_undo_action();
2185 #endif
2186 
2187     return 0;
2188 }
2189 
2190 
2191 /**
2192  * \brief Add padding to cells
2193  *
2194  * \details Add padding to cells. This set padding of a range.
2195  *
2196  * \return -1 if locked cell is encountered; 1 if padding exceeded
2197  * column width; 0 otherwise
2198  */
pad(int n,int r1,int c1,int r2,int c2)2199 int pad(int n, int r1, int c1, int r2, int c2) {
2200     int r, c;
2201     struct ent * p ;
2202     int pad_exceed_width = 0;
2203 
2204     if (any_locked_cells(r1, c1, r2, c2)) {
2205         sc_info("Locked cells encountered. Nothing changed");
2206         return -1;
2207      }
2208 
2209 #ifdef UNDO
2210     create_undo_action();
2211     copy_to_undostruct(r1, c1, r2, c2, UNDO_DEL, IGNORE_DEPS, NULL);
2212 #endif
2213 
2214     for (r = r1; r <= r2; r++) {
2215         for (c = c1; c <= c2; c++) {
2216             if (n > fwidth[c]) {
2217                 pad_exceed_width = 1;
2218                 continue;
2219             }
2220             if ((p = *ATBL(tbl, r, c)) != NULL) p->pad = n;
2221             modflg++;
2222         }
2223     }
2224 
2225 #ifdef UNDO
2226     copy_to_undostruct(r1, c1, r2, c2, UNDO_ADD, IGNORE_DEPS, NULL);
2227     end_undo_action();
2228 #endif
2229 
2230     if (pad_exceed_width) {
2231         sc_error(" Could not add padding in some cells. Padding exceeded column width");
2232         return 1;
2233     }
2234     return 0;
2235 }
2236 
2237 
2238 /**
2239  * \brief fix_row_hidden
2240  * \details fix hidden rows after undoing ir dr etc..
2241  * \return none
2242  */
fix_row_hidden(int deltar,int ri,int rf)2243 void fix_row_hidden(int deltar, int ri, int rf) {
2244     int r;
2245     int d = deltar;
2246 
2247     // decrease / for dr
2248     if (deltar > 0)
2249         while (d-- > 0)
2250             for (r = ri; r < rf; r++)
2251                 row_hidden[r] = row_hidden[r+1];
2252 
2253     // increase / for ir
2254     if (deltar < 0)
2255         while (d++ < 0)
2256             for (r = rf; r > ri; r--)
2257                 row_hidden[r] = row_hidden[r-1];
2258     return;
2259 }
2260 
2261 
2262 /**
2263  * \brief fix_col_hidden
2264  * \details fix hidden cols after undoing ic dc etc..
2265  * \return none
2266  */
fix_col_hidden(int deltac,int ci,int cf)2267 void fix_col_hidden(int deltac, int ci, int cf) {
2268     int c;
2269     int d = deltac;
2270 
2271     // decrease / for dc
2272     if (deltac > 0)
2273         while (d-- > 0)
2274             for (c = ci; c < cf; c++)
2275                 col_hidden[c] = col_hidden[c+1];
2276 
2277     // increase / for ic
2278     if (deltac < 0)
2279         while (d++ < 0)
2280             for (c = cf; c > ci; c--)
2281                 col_hidden[c] = col_hidden[c-1];
2282     return;
2283 }
2284 
2285 
2286 /**
2287  * \brief fix_row_frozen
2288  * \details fix frozen rows after undoing ir dr etc..
2289  * \return none
2290  */
fix_row_frozen(int deltar,int ri,int rf)2291 void fix_row_frozen(int deltar, int ri, int rf) {
2292     int r;
2293     int d = deltar;
2294 
2295     // decrease / for dr
2296     if (deltar > 0)
2297         while (d-- > 0)
2298             for (r = ri; r < rf; r++)
2299                 row_frozen[r] = row_frozen[r+1];
2300 
2301     // increase / for ir
2302     if (deltar < 0)
2303         while (d++ < 0)
2304             for (r = rf; r > ri; r--)
2305                 row_frozen[r] = row_frozen[r-1];
2306     return;
2307 }
2308 
2309 
2310 /**
2311  * \brief fix_col_frozen
2312  * \details fix frozen cols after undoing ic dc etc..
2313  * \return none
2314  */
fix_col_frozen(int deltac,int ci,int cf)2315 void fix_col_frozen(int deltac, int ci, int cf) {
2316     int c;
2317     int d = deltac;
2318 
2319     // decrease / for dc
2320     if (deltac > 0)
2321         while (d-- > 0)
2322             for (c = ci; c < cf; c++)
2323                 col_frozen[c] = col_frozen[c+1];
2324 
2325     // increase / for ic
2326     if (deltac < 0)
2327         while (d++ < 0)
2328             for (c = cf; c > ci; c--)
2329                 col_frozen[c] = col_frozen[c-1];
2330     return;
2331 }
2332 
2333 
2334 /**
2335  * \brief Compute number of mobile (unfrozen) rows to fit on screen
2336  *
2337  * \details This function finds the number of mobile rows that can fit on
2338  * the screen. This number excludes frozen rows as they never leave the
2339  * screen hence their total height is subtracted from the available screen
2340  * area. This number also excludes hidden rows. Displayed mobile rows are
2341  * considered starting from offscr_sc_rows, however currow must be within
2342  * the displayed rows. If currow is found to be outside the displayed set
2343  * of rows then offscr_sc_rows is adjusted accordingly.
2344  *
2345  * \param[in] last_p If not NULL then the last mobile row to fit is stored there
2346  *
2347  * \return Number of mobile rows displayable on the screen
2348  */
calc_mobile_rows(int * last_p)2349 int calc_mobile_rows(int *last_p) {
2350     int i, row_space, mobile_rows, last;
2351 
2352     /*
2353      * Compute the number of frozen rows and the space they need.
2354      * Eventually this should be added/subtracted when individual rows
2355      * are frozen/unfrozen/enlarged/reduced/deleted and not recomputed
2356      * every time here... or at least have a global flag indicating that
2357      * nothing has changed and that this loop can be skipped.
2358      */
2359     nb_frozen_rows = 0;
2360     nb_frozen_screenrows = 0;
2361     for (i = 0; i < maxrows; i++) {
2362         if (row_hidden[i])
2363             continue;
2364         if (row_frozen[i]) {
2365             nb_frozen_rows++;
2366             nb_frozen_screenrows += row_format[i];
2367         }
2368     }
2369 
2370     /* Adjust display start if currow is above it */
2371     if (currow < offscr_sc_rows)
2372         offscr_sc_rows = currow;
2373 
2374     /* Determine the space available for mobile rows. */
2375     row_space = LINES - RESROW - RESCOLHEADER - nb_frozen_screenrows;
2376 
2377     /*
2378      * Find how many visible mobile rows can fit in there
2379      * and remember which one is the last to fit.
2380      */
2381     mobile_rows = 0;
2382     last = offscr_sc_rows;
2383     for (i = offscr_sc_rows; i < maxrows; i++) {
2384         if (row_hidden[i])
2385             continue;
2386         if (row_frozen[i])
2387             continue;
2388         if (row_format[i] > row_space)
2389             break;
2390         row_space -= row_format[i];
2391         mobile_rows++;
2392         last = i;
2393     }
2394 
2395     /*
2396      * If currow is beyond the last row here then we must  start over,
2397      * moving backward this time, to properly position start of display.
2398      */
2399     if (last < currow) {
2400         row_space = LINES - RESROW - RESCOLHEADER - nb_frozen_screenrows;
2401         mobile_rows = 0;
2402         last = currow;
2403         for (i = currow; i >= 0; i--) {
2404             if (row_hidden[i])
2405                 continue;
2406             if (row_frozen[i])
2407                 continue;
2408             if (row_format[i] > row_space)
2409                 break;
2410             row_space -= row_format[i];
2411             mobile_rows++;
2412             last = i;
2413         }
2414         offscr_sc_rows = last;
2415     }
2416 
2417     if (last_p)
2418         *last_p = last;
2419     return mobile_rows;
2420 }
2421 
2422 /**
2423  * \brief Compute number of mobile (unfrozen) columns to fit on screen
2424  *
2425  * \details This function finds the number of mobile columns that can fit on
2426  * the screen. This number excludes frozen columns as they never leave the
2427  * screen, hence their total height is subtracted from the available screen
2428  * area. This number also excludes hidden columns. Displayed mobile columnss
2429  * are considered starting from offscr_sc_cols, however curcol must be within
2430  * the displayed columns. If curcol is found to be outside the displayed set
2431  * of columns then offscr_sc_cols is adjusted accordingly.
2432  *
2433  * \param[in] last_p If not NULL then the last mobile column to fit is stored there
2434  *
2435  * \return Number of mobile columns displayable on the screen
2436  */
calc_mobile_cols(int * last_p)2437 int calc_mobile_cols(int *last_p) {
2438     int i, col_space, mobile_cols, last;
2439 
2440     /*
2441      * Compute the number of frozen columns and the space they need.
2442      * Eventually this should be added/subtracted when individual
2443      * columns are frozen/unfrozen/enlarged/reduced/deleted and not
2444      * recomputed every time here... or at least have a flag indicating
2445      * that nothing has changed and that this loop may be skipped.
2446      */
2447     nb_frozen_cols = 0;
2448     nb_frozen_screencols = 0;
2449     for (i = 0; i < maxcols; i++) {
2450         if (row_frozen[i])
2451             continue;
2452         if (col_frozen[i]) {
2453             nb_frozen_cols++;
2454             nb_frozen_screencols += fwidth[i];
2455         }
2456     }
2457 
2458     /* Adjust display start if curcol is left of it */
2459     if (curcol < offscr_sc_cols)
2460         offscr_sc_cols = curcol;
2461 
2462     /* Determine the space available for mobile columns. */
2463     col_space = COLS - rescol - nb_frozen_screencols;
2464 
2465     /*
2466      * Find how many visible mobile columns can fit in there
2467      * and remember which one is the last to fit.
2468      */
2469     mobile_cols = 0;
2470     last = offscr_sc_cols;
2471     for (i = offscr_sc_cols; i < maxcols; i++) {
2472         if (col_hidden[i])
2473             continue;
2474         if (col_frozen[i])
2475             continue;
2476         if (fwidth[i] > col_space)
2477             break;
2478         col_space -= fwidth[i];
2479         mobile_cols++;
2480         last = i;
2481     }
2482 
2483     /*
2484      * If curcol is beyond the last column here then we start over,
2485      * moving backward this time, to properly position start of display.
2486      */
2487     if (last < curcol) {
2488         col_space = COLS - rescol - nb_frozen_screencols;
2489         mobile_cols = 0;
2490         last = curcol;
2491         for (i = curcol; i >= 0; i--) {
2492             if (col_hidden[i])
2493                 continue;
2494             if (col_frozen[i])
2495                 continue;
2496             if (fwidth[i] > col_space)
2497                 break;
2498             col_space -= fwidth[i];
2499             mobile_cols++;
2500             last = i;
2501         }
2502         offscr_sc_cols = last;
2503     }
2504 
2505     if (last_p)
2506         *last_p = last;
2507     return mobile_cols;
2508 }
2509 
2510 
2511 /**
2512  * \brief pad_and_align
2513  *
2514  * \details This function aligns text of a cell (align = 0 center,
2515  * align = 1 right, align = -1 left). Also adds padding between cells.
2516  *
2517  * \param[in] srt_value
2518  * \param[in] numeric_value
2519  * \param[in] col_width
2520  * \param[in] align
2521  * \param[in] padding
2522  * \param[in] str_out
2523  * \param[in] rowfmt: rowheight
2524  *
2525  * \return resulting string to be printed to the screen
2526  */
pad_and_align(char * str_value,char * numeric_value,int col_width,int align,int padding,wchar_t * str_out,int rowfmt)2527 void pad_and_align (char * str_value, char * numeric_value, int col_width, int align, int padding, wchar_t * str_out, int rowfmt) {
2528     int str_len  = 0;
2529     int num_len  = strlen(numeric_value);
2530     str_out[0] = L'\0';
2531 
2532     wchar_t wcs_value[BUFFERSIZE] = { L'\0' };
2533     mbstate_t state;
2534     size_t result;
2535     char * str_in;
2536 
2537     // Here we handle \" and replace them with "
2538     str_in = str_replace(str_value, "\\\"", "\"");
2539 
2540     const char * mbsptr;
2541     mbsptr = str_in;
2542 
2543     // create wcs string based on multibyte string..
2544     memset( &state, '\0', sizeof state );
2545     result = mbsrtowcs(wcs_value, &mbsptr, BUFFERSIZE, &state);
2546     if ( result != (size_t)-1 )
2547         str_len = wcswidth(wcs_value, wcslen(wcs_value));
2548 
2549     if (str_len == 2 && str_in[0] == '\\') {
2550         wmemset(str_out + wcslen(str_out), str_in[1], col_width);
2551         free(str_in);
2552         return;
2553     } else if (str_len == 3 && str_in[0] == '\\' && str_in[1] == '\\') {
2554         wmemset(str_out + wcslen(str_out), str_in[2], col_width);
2555         free(str_in);
2556         return;
2557     }
2558 
2559     // If padding exceedes column width, returns n number of '-' needed to fill column width
2560     if (padding >= col_width ) {
2561         wmemset(str_out + wcslen(str_out), L' ', col_width);
2562         free(str_in);
2563         return;
2564     }
2565 
2566     // If content exceedes column width, outputs n number of '*' needed to fill column width
2567     if (str_len + num_len + padding > col_width * rowfmt && ! get_conf_int("truncate") &&
2568         ! get_conf_int("overlap") && ! get_conf_int("autowrap")) {
2569         if (padding) wmemset(str_out + wcslen(str_out), L' ', padding);
2570         wmemset(str_out + wcslen(str_out), L'*', col_width - padding);
2571         free(str_in);
2572         return;
2573     }
2574 
2575     // padding
2576     if (padding) swprintf(str_out, BUFFERSIZE, L"%*ls", padding, L"");
2577 
2578     // left spaces
2579     int left_spaces = 0;
2580     if (align == 0) {                                           // center align
2581         left_spaces = (col_width - padding - str_len) / 2;
2582         if (num_len > left_spaces) left_spaces = col_width - padding - str_len - num_len;
2583     } else if (align == 1 && str_len && ! num_len) {       // right align
2584         left_spaces = col_width - padding - str_len;
2585     }
2586     while (left_spaces-- > 0) add_wchar(str_out, L' ', wcslen(str_out));
2587 
2588     // add text
2589     if (align != 1 || ! num_len)
2590         swprintf(str_out + wcslen(str_out), BUFFERSIZE, L"%s", str_in);
2591 
2592     // spaces after string value
2593     int spaces = col_width - padding - str_len - num_len;
2594     if (align == 1){
2595       if(num_len) spaces += str_len;
2596       else spaces = 0;
2597     }
2598     if (align == 0) spaces -= (col_width - padding - str_len) / 2;
2599     while (spaces-- > 0) add_wchar(str_out, L' ', wcslen(str_out));
2600 
2601     // add number
2602     int fill_with_number = col_width - str_len - padding;
2603     if (num_len && num_len >= fill_with_number) {
2604         swprintf(str_out + wcslen(str_out), BUFFERSIZE, L"%.*s", fill_with_number, & numeric_value[num_len - fill_with_number]);
2605     } else if (num_len) {
2606         swprintf(str_out + wcslen(str_out), BUFFERSIZE, L"%s", numeric_value);
2607     }
2608 
2609     // Similar condition to max width '*' condition above, but just trims instead
2610     if (str_len + num_len + padding > col_width * rowfmt && get_conf_int("truncate")) {
2611         str_out[col_width] = '\0';
2612     }
2613 
2614     // on each \n chars, replace with n number of L' ' to complete column width
2615     int posnl, leftnl = 0;
2616     while ((posnl = wstr_in_wstr(str_out, L"\\n")) != -1) {
2617         del_range_wchars(str_out, posnl, posnl+1);
2618         if (posnl <= col_width) leftnl = col_width - posnl;
2619         else leftnl = col_width - posnl % col_width;
2620         while (leftnl-- > 0) add_wchar(str_out, L' ', posnl);
2621     }
2622 
2623     free(str_in);
2624     return;
2625 }
2626 
2627 
2628 /**
2629  * \brief Check if the buffer content is a valid command
2630  *
2631  * \details Check if the buffer content is a valid command
2632  * result = 0 or NO_CMD : buf has no command
2633  * result = 1 or EDITION_CMD : buf has a command
2634  * result = 2 or MOVEMENT_CMD : buf has a movement command or a command that do not
2635  * change cell content, and should not be considered by the '.' command
2636  *
2637  * \return result
2638  */
is_single_command(struct block * buf,long timeout)2639 int is_single_command (struct block * buf, long timeout) {
2640     if (buf->value == L'\0') return NO_CMD;
2641     int result = NO_CMD;
2642     int bs = get_bufsize(buf);
2643 
2644     if (curmode == COMMAND_MODE && bs == 1 && ( buf->value != ctl('r') ||
2645         buf->value == ctl('v')) ) {
2646         result = MOVEMENT_CMD;
2647 
2648     } else if ( curmode == COMMAND_MODE && bs == 2 && buf->value == ctl('r') &&
2649         (buf->pnext->value - (L'a' - 1) < 1 || buf->pnext->value > 26)) {
2650         result = MOVEMENT_CMD;
2651 
2652     } else if (curmode == INSERT_MODE && bs == 1 && ( buf->value != ctl('r') ||
2653         buf->value == ctl('v')) ) {
2654         result = MOVEMENT_CMD;
2655 
2656     } else if (curmode == INSERT_MODE && bs == 2 && buf->value == ctl('r') &&
2657         (buf->pnext->value - (L'a' - 1) < 1 || buf->pnext->value > 26)) {
2658         result = MOVEMENT_CMD;
2659 
2660     } else if (curmode == EDIT_MODE && bs == 1) {
2661         result = MOVEMENT_CMD;
2662 
2663     } else if (curmode == NORMAL_MODE && bs == 1) {
2664         // commands for changing mode
2665         if (buf->value == L':')             result = MOVEMENT_CMD;
2666         else if (buf->value == L'\\')       result = MOVEMENT_CMD;
2667         else if (buf->value == L'<')        result = MOVEMENT_CMD;
2668         else if (buf->value == L'>')        result = MOVEMENT_CMD;
2669         else if (buf->value == L'=')        result = MOVEMENT_CMD;
2670         else if (buf->value == L'e')        result = MOVEMENT_CMD;
2671         else if (buf->value == L'E')        result = MOVEMENT_CMD;
2672         else if (buf->value == L'v')        result = MOVEMENT_CMD;
2673 
2674         else if (buf->value == L'Q')        result = MOVEMENT_CMD;  /* FOR TEST PURPOSES */
2675         else if (buf->value == L'A')        result = MOVEMENT_CMD;  /* FOR TEST PURPOSES */
2676         else if (buf->value == L'W')        result = MOVEMENT_CMD;  /* FOR TEST PURPOSES */
2677 
2678         // movement commands
2679         else if (buf->value == L'j')        result = MOVEMENT_CMD;
2680         else if (buf->value == L'k')        result = MOVEMENT_CMD;
2681         else if (buf->value == L'h')        result = MOVEMENT_CMD;
2682         else if (buf->value == L'l')        result = MOVEMENT_CMD;
2683         else if (buf->value == L'0')        result = MOVEMENT_CMD;
2684         else if (buf->value == L'$')        result = MOVEMENT_CMD;
2685         else if (buf->value == OKEY_HOME)   result = MOVEMENT_CMD;
2686         else if (buf->value == OKEY_END)    result = MOVEMENT_CMD;
2687         else if (buf->value == L'#')        result = MOVEMENT_CMD;
2688         else if (buf->value == L'^')        result = MOVEMENT_CMD;
2689         else if (buf->value == OKEY_LEFT)   result = MOVEMENT_CMD;
2690         else if (buf->value == OKEY_RIGHT)  result = MOVEMENT_CMD;
2691         else if (buf->value == OKEY_DOWN)   result = MOVEMENT_CMD;
2692         else if (buf->value == OKEY_UP)     result = MOVEMENT_CMD;
2693         else if (buf->value == OKEY_PGUP)   result = MOVEMENT_CMD;
2694         else if (buf->value == OKEY_PGDOWN) result = MOVEMENT_CMD;
2695         else if (buf->value == ctl('f'))    result = MOVEMENT_CMD;
2696         else if (buf->value == ctl('j'))    result = EDITION_CMD;
2697         else if (buf->value == ctl('d'))    result = EDITION_CMD;
2698         else if (buf->value == ctl('b'))    result = MOVEMENT_CMD;
2699         else if (buf->value == ctl('a'))    result = MOVEMENT_CMD;
2700         else if (buf->value == L'G')        result = MOVEMENT_CMD;
2701         else if (buf->value == L'H')        result = MOVEMENT_CMD;
2702         else if (buf->value == L'M')        result = MOVEMENT_CMD;
2703         else if (buf->value == L'L')        result = MOVEMENT_CMD;
2704         else if (buf->value == ctl('y'))    result = MOVEMENT_CMD;
2705         else if (buf->value == ctl('e'))    result = MOVEMENT_CMD;
2706         else if (buf->value == ctl('l'))    result = MOVEMENT_CMD;
2707         else if (buf->value == L'w')        result = MOVEMENT_CMD;
2708         else if (buf->value == L'b')        result = MOVEMENT_CMD;
2709         else if (buf->value == L'/')        result = MOVEMENT_CMD; // search
2710         else if (buf->value == L'?')        result = MOVEMENT_CMD; // search backwards
2711         else if (buf->value == L'n')        result = MOVEMENT_CMD; // repeat last goto cmd
2712         else if (buf->value == L'N')        result = MOVEMENT_CMD; // repeat last goto cmd - backwards
2713 
2714         // edition commands
2715         else if (buf->value == L'x')        result = EDITION_CMD;  // cuts a cell
2716         else if (buf->value == L'u')        result = MOVEMENT_CMD; // undo
2717         else if (buf->value == ctl('r'))    result = MOVEMENT_CMD; // redo
2718         else if (buf->value == L'@')        result = EDITION_CMD;  // EvalAll
2719         else if (buf->value == L'{')        result = EDITION_CMD;
2720         else if (buf->value == L'}')        result = EDITION_CMD;
2721         else if (buf->value == L'|')        result = EDITION_CMD;
2722         else if (buf->value == L'p')        result = EDITION_CMD;  // paste yanked cells below or left
2723         else if (buf->value == L't')        result = EDITION_CMD;  // paste yanked cells above or right
2724         else if (buf->value == L'-')        result = EDITION_CMD;
2725         else if (buf->value == L'+')        result = EDITION_CMD;
2726 
2727         else if (isdigit(buf->value) && get_conf_int("numeric") )
2728                                             result = MOVEMENT_CMD; // repeat last command
2729 
2730         else if (buf->value == L'.')        result = MOVEMENT_CMD; // repeat last command
2731         else if (buf->value == L'y' && is_range_selected() != -1)
2732                                             result = EDITION_CMD;  // yank range
2733 
2734     } else if (curmode == NORMAL_MODE) {
2735 
2736         if (buf->value == L'g' && bs == 2 && (
2737                  buf->pnext->value == L'f' ||
2738                  buf->pnext->value == L'M' ||
2739                  buf->pnext->value == L'g' ||
2740                  buf->pnext->value == L'G' ||
2741                  buf->pnext->value == L'0' ||
2742                  buf->pnext->value == L'l' ||
2743                  buf->pnext->value == L'$'))
2744                  result = MOVEMENT_CMD;
2745 
2746         else if (buf->value == L'g' && bs > 3 && buf->pnext->value == L't' && timeout >= COMPLETECMDTIMEOUT)
2747                  result = MOVEMENT_CMD; // goto cell
2748                  // TODO add validation: buf->pnext->pnext->value must be a letter
2749 
2750         else if (buf->value == L'P' && bs == 2 && (
2751             buf->pnext->value == L'v' ||
2752             buf->pnext->value == L'f' ||
2753             buf->pnext->value == L'c' ) )   result = EDITION_CMD;  // paste yanked cells below or left
2754 
2755         else if (buf->value == L'T' && bs == 2 && (
2756             buf->pnext->value == L'v' ||
2757             buf->pnext->value == L'f' ||
2758             buf->pnext->value == L'c' ) )   result = EDITION_CMD;  // paste yanked cells above or right
2759 
2760         else if (buf->value == L'a' && bs == 2 &&    // autojus
2761                  buf->pnext->value == L'a') result = EDITION_CMD;
2762 
2763         else if (buf->value == L'Z' && bs == 2 && timeout >= COMPLETECMDTIMEOUT &&  // Zap (or hide) col or row
2764                ( buf->pnext->value == L'c' ||
2765                  buf->pnext->value == L'r')) result = EDITION_CMD;
2766 
2767         else if (buf->value == L'S' && bs == 2 && timeout >= COMPLETECMDTIMEOUT &&  // Zap (or hide) col or row
2768                ( buf->pnext->value == L'c' ||
2769                  buf->pnext->value == L'r')) result = EDITION_CMD;
2770 
2771         else if (buf->value == L'y' && bs == 2 &&    // yank cell
2772                ( buf->pnext->value == L'y' ||
2773                  buf->pnext->value == L'r' ||
2774                  buf->pnext->value == L'c') ) result = EDITION_CMD;
2775 
2776         else if (buf->value == L'm' && bs == 2 &&    // mark
2777                ((buf->pnext->value - (L'a' - 1)) < 1 ||
2778                  buf->pnext->value > 26)) result = MOVEMENT_CMD;
2779 
2780         else if (buf->value == L'c' && bs == 2 &&    // mark
2781                ((buf->pnext->value - (L'a' - 1)) < 1 || buf->pnext->value > 26)) result = EDITION_CMD;
2782 
2783         else if (buf->value == L'z' && bs == 2 &&    // scrolling
2784                ( buf->pnext->value == L'h' ||
2785                  buf->pnext->value == L'l' ||
2786                  buf->pnext->value == L'z' ||
2787                  buf->pnext->value == L'm' ||
2788                  buf->pnext->value == L'.' ||
2789                  buf->pnext->value == L't' ||
2790                  buf->pnext->value == L'b' ||
2791                  buf->pnext->value == L'H' ||
2792                  buf->pnext->value == L'L')
2793                ) result = MOVEMENT_CMD;
2794 
2795         else if (buf->value == L'V' && bs == 3 &&    // Vir
2796                  buf->pnext->value == L'i' &&
2797                  buf->pnext->pnext->value == L'r')
2798                  result = MOVEMENT_CMD;
2799 
2800         else if (buf->value == L'd' && bs == 2 &&    // cuts a cell
2801                  buf->pnext->value == L'd') result = EDITION_CMD;
2802 
2803         else if (buf->value == L'\'' && bs == 2 &&   // tick
2804                ((buf->pnext->value - (L'a' - 1)) < 1 ||
2805                  buf->pnext->value > 26)) result = MOVEMENT_CMD;
2806 
2807         else if (buf->value == L's' && bs == 2 &&    // shift cell down or up
2808                ( buf->pnext->value == L'j' ||
2809                  buf->pnext->value == L'k' ||
2810                  buf->pnext->value == L'h' ||
2811                  buf->pnext->value == L'l')) result = EDITION_CMD;
2812 
2813         else if (buf->value == L'i' && bs == 2 &&    // Insert row or column
2814                ( buf->pnext->value == L'r' ||
2815                  buf->pnext->value == L'c' )) result = EDITION_CMD;
2816 
2817         else if (buf->value == L'o' && bs == 2 &&    // Open row or column
2818                ( buf->pnext->value == L'r' ||
2819                  buf->pnext->value == L'c' )) result = EDITION_CMD;
2820 
2821         else if (buf->value == L'd' && bs == 2 &&    // Delete row or column
2822                ( buf->pnext->value == L'r' ||
2823                  buf->pnext->value == L'c' )) result = EDITION_CMD;
2824 
2825         else if (buf->value == L'r' && bs == 2 &&    // range lock / unlock / valueize
2826                ( buf->pnext->value == L'l' ||
2827                  buf->pnext->value == L'u' ||
2828                  buf->pnext->value == L'v' )) result = EDITION_CMD;
2829 
2830         else if (buf->value == L'R' && bs == 3 &&    // Create range with two marks
2831             //  FIXME add better validation
2832                ((buf->pnext->value - (L'a' - 1)) < 1 ||
2833                  buf->pnext->value > 26) &&
2834                ((buf->pnext->pnext->value - (L'a' - 1)) < 1 ||
2835                  buf->pnext->pnext->value > 26)) result = EDITION_CMD;
2836 
2837         else if (buf->value == L'f' && bs == 2 &&    // Format col
2838                ( buf->pnext->value == L'>' ||
2839                  buf->pnext->value == L'<' ||
2840                  buf->pnext->value == L'h' ||
2841                  buf->pnext->value == OKEY_LEFT ||
2842                  buf->pnext->value == L'l' ||
2843                  buf->pnext->value == OKEY_RIGHT ||
2844                  buf->pnext->value == L'j' ||
2845                  buf->pnext->value == OKEY_DOWN ||
2846                  buf->pnext->value == L'k' ||
2847                  buf->pnext->value == OKEY_UP ||
2848                  buf->pnext->value == L'-' ||
2849                  buf->pnext->value == L'+' ||
2850                  buf->pnext->value == L'r' ||         // Freeze row / col / area
2851                  buf->pnext->value == L'c' ||
2852                  buf->pnext->value == L'a'
2853                  )
2854                ) result = EDITION_CMD;
2855 
2856     } else if (curmode == VISUAL_MODE && bs == 1) {
2857              if (buf->value == L'j' ||
2858                  buf->value == OKEY_DOWN ||
2859                  buf->value == L'k' ||
2860                  buf->value == OKEY_UP    ||
2861                  buf->value == L'h' ||
2862                  buf->value == OKEY_LEFT ||
2863                  buf->value == L'l' ||
2864                  buf->value == OKEY_RIGHT ||
2865                  buf->value == L'$' ||
2866                  buf->value == L'0' ||
2867                  buf->value == L'#' ||
2868                  buf->value == L'^' ||
2869                  buf->value == L'y' ||
2870                  buf->value == L'p' ||
2871                  buf->value == L'P' ||
2872                  buf->value == L'x' ||
2873                  buf->value == L'w' ||
2874                  buf->value == L'b' ||
2875                  buf->value == L'H' ||
2876                  buf->value == L'M' ||
2877                  buf->value == L'L' ||
2878                  buf->value == L'G' ||
2879                  buf->value == ctl('f') ||
2880                  buf->value == ctl('j') ||
2881                  buf->value == ctl('d') ||
2882                  buf->value == ctl('b') ||
2883                  buf->value == ctl('a') ||
2884                  buf->value == ctl('o') ||
2885                  buf->value == ctl('k') ||
2886                  buf->value == L':'
2887              )
2888                  result = MOVEMENT_CMD;
2889              else if (buf->value == L'{' ||
2890                  buf->value == L'}' ||
2891                  buf->value == L'f' ||
2892                  buf->value == L'|')
2893                  result = EDITION_CMD;
2894 
2895     } else if (curmode == VISUAL_MODE && bs == 2) {
2896             if ((buf->value == L'\'') ||
2897                 (buf->value == L'd' &&
2898                  buf->pnext->value == L'd')  ||
2899                 (buf->value == L's' && (
2900                  buf->pnext->value == L'h' ||
2901                  buf->pnext->value == L'j' ||
2902                  buf->pnext->value == L'k' ||
2903                  buf->pnext->value == L'l' ))
2904             ) {
2905                  result = MOVEMENT_CMD;
2906             } else if ((buf->value == L'Z' && (
2907                  buf->pnext->value == L'r' ||
2908                  buf->pnext->value == L'c' )) ||
2909                 (buf->value == L'S' && (
2910                  buf->pnext->value == L'r' ||
2911                  buf->pnext->value == L'c' )) ) {
2912                  result = EDITION_CMD;
2913             } else if (buf->value == L'r' && (
2914                  buf->pnext->value == L'l' ||
2915                  buf->pnext->value == L'u' ||
2916                  buf->pnext->value == L'v' )) {
2917                  result = EDITION_CMD;
2918             } else if (buf->value == L'm' && // mark
2919                ((buf->pnext->value - (L'a' - 1)) < 1 ||
2920                  buf->pnext->value > 26)) {
2921                  result = MOVEMENT_CMD;
2922             }
2923     }
2924     return result;
2925 }
2926