@trebco/treb 31.9.1 → 32.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/treb-spreadsheet.mjs +15 -15
  2. package/i18n/languages/treb-i18n-da.mjs +1 -0
  3. package/i18n/languages/treb-i18n-de.mjs +1 -0
  4. package/i18n/languages/treb-i18n-es.mjs +1 -0
  5. package/i18n/languages/treb-i18n-fr.mjs +1 -0
  6. package/i18n/languages/treb-i18n-it.mjs +1 -0
  7. package/i18n/languages/treb-i18n-nl.mjs +1 -0
  8. package/i18n/languages/treb-i18n-no.mjs +1 -0
  9. package/i18n/languages/treb-i18n-pl.mjs +1 -0
  10. package/i18n/languages/treb-i18n-pt.mjs +1 -0
  11. package/i18n/languages/treb-i18n-sv.mjs +1 -0
  12. package/package.json +1 -1
  13. package/treb-base-types/src/style.ts +20 -0
  14. package/treb-base-types/src/union.ts +6 -0
  15. package/treb-base-types/src/value-type.ts +13 -0
  16. package/treb-calculator/src/calculator.ts +20 -1
  17. package/treb-calculator/src/descriptors.ts +49 -4
  18. package/treb-calculator/src/expression-calculator.ts +263 -12
  19. package/treb-calculator/src/functions/base-functions.ts +49 -0
  20. package/treb-calculator/src/functions/fp.ts +339 -0
  21. package/treb-calculator/src/functions/gamma.ts +143 -0
  22. package/treb-calculator/src/functions/lambda-functions.ts +96 -0
  23. package/treb-calculator/src/functions/statistics-functions.ts +88 -0
  24. package/treb-data-model/src/data_model.ts +28 -13
  25. package/treb-data-model/src/named.ts +7 -0
  26. package/treb-data-model/src/sheet.ts +19 -2
  27. package/treb-embed/src/embedded-spreadsheet.ts +22 -5
  28. package/treb-embed/style/theme-defaults.scss +0 -22
  29. package/treb-grid/src/editors/editor.ts +14 -0
  30. package/treb-grid/src/types/grid.ts +74 -28
  31. package/treb-parser/src/parser-types.ts +24 -0
  32. package/treb-parser/src/parser.ts +157 -223
  33. package/dist/treb-export-worker.mjs +0 -2
  34. package/dist/treb.d.ts +0 -2235
  35. package/treb-calculator/tsconfig.json +0 -7
@@ -111,13 +111,17 @@ export class DataModel {
111
111
  }
112
112
 
113
113
  /**
114
- *
114
+ * @param force_locale - always parse assuming a locale like en-us (comma
115
+ * argument separators). the current thinking is that this is required for
116
+ * XLSX import, although that might be incorrect.
115
117
  */
116
- public UnserializeNames(names: SerializedNamed[], active_sheet?: Sheet) {
118
+ public UnserializeNames(names: SerializedNamed[], active_sheet?: Sheet, force_locale = false) {
117
119
 
118
120
  this.parser.Save();
119
- this.parser.SetLocaleSettings(DecimalMarkType.Period, ArgumentSeparatorType.Comma);
120
-
121
+ if (force_locale) {
122
+ this.parser.SetLocaleSettings(DecimalMarkType.Period, ArgumentSeparatorType.Comma);
123
+ }
124
+
121
125
  //const sorted = names.map(named => {
122
126
  for (const named of names) {
123
127
 
@@ -197,6 +201,7 @@ export class DataModel {
197
201
  const named: SerializedNamed = {
198
202
  name: entry.name,
199
203
  expression: '',
204
+ type: entry.type,
200
205
  };
201
206
 
202
207
  if (entry.scope) {
@@ -239,6 +244,11 @@ export class DataModel {
239
244
  return true;
240
245
  });
241
246
 
247
+ // this is using the current locale settings, but unserialize
248
+ // assumes we are unserializing in US-style locale. I think we
249
+ // do that because excel always uses that? not sure, but we need
250
+ // to be consistent.
251
+
242
252
  named.expression = this.parser.Render(entry.expression, { missing: '' });
243
253
 
244
254
  }
@@ -601,9 +611,9 @@ export class DataModel {
601
611
  * be inlined (assuming it's only called in one place), but we are breaking
602
612
  * it out so we can develop/test/manage it.
603
613
  */
604
- public TranslateFunction(value: string): string {
614
+ public TranslateFunction(value: string, options?: { r1c1?: boolean }): string {
605
615
  if (this.language_map) {
606
- return this.TranslateInternal(value, this.language_map, this.language_model?.boolean_true, this.language_model?.boolean_false);
616
+ return this.TranslateInternal(value, this.language_map, this.language_model?.boolean_true, this.language_model?.boolean_false, options);
607
617
  }
608
618
  return value;
609
619
  }
@@ -612,14 +622,14 @@ export class DataModel {
612
622
  * translate from local language -> common (english).
613
623
  * @see TranslateFunction
614
624
  */
615
- public UntranslateFunction(value: string): string {
625
+ public UntranslateFunction(value: string, options?: { r1c1?: boolean }): string {
616
626
  if (this.reverse_language_map) {
617
- return this.TranslateInternal(value, this.reverse_language_map, 'TRUE', 'FALSE');
627
+ return this.TranslateInternal(value, this.reverse_language_map, 'TRUE', 'FALSE', options);
618
628
  }
619
629
  return value;
620
630
  }
621
631
 
622
- public UntranslateData(value: CellValue|CellValue[]|CellValue[][]): CellValue|CellValue[]|CellValue[][] {
632
+ public UntranslateData(value: CellValue|CellValue[]|CellValue[][], options?: { r1c1?: boolean }): CellValue|CellValue[]|CellValue[][] {
623
633
 
624
634
  if (Array.isArray(value)) {
625
635
 
@@ -628,7 +638,7 @@ export class DataModel {
628
638
  if (Is2DArray(value)) {
629
639
  return value.map(row => row.map(entry => {
630
640
  if (entry && typeof entry === 'string' && entry[0] === '=') {
631
- return this.UntranslateFunction(entry);
641
+ return this.UntranslateFunction(entry, options);
632
642
  }
633
643
  return entry;
634
644
  }));
@@ -636,7 +646,7 @@ export class DataModel {
636
646
  else {
637
647
  return value.map(entry => {
638
648
  if (entry && typeof entry === 'string' && entry[0] === '=') {
639
- return this.UntranslateFunction(entry);
649
+ return this.UntranslateFunction(entry, options);
640
650
  }
641
651
  return entry;
642
652
  });
@@ -646,7 +656,7 @@ export class DataModel {
646
656
  else if (value && typeof value === 'string' && value[0] === '=') {
647
657
 
648
658
  // single value
649
- value = this.UntranslateFunction(value);
659
+ value = this.UntranslateFunction(value, options);
650
660
 
651
661
  }
652
662
 
@@ -663,7 +673,10 @@ export class DataModel {
663
673
  * FIXME: it's about time we started using proper maps, we dropped
664
674
  * support for IE11 some time ago.
665
675
  */
666
- private TranslateInternal(value: string, map: Record<string, string>, boolean_true?: string, boolean_false?: string): string {
676
+ private TranslateInternal(value: string, map: Record<string, string>, boolean_true?: string, boolean_false?: string, options?: { r1c1?: boolean }): string {
677
+
678
+ this.parser.Save();
679
+ this.parser.flags.r1c1 = options?.r1c1;
667
680
 
668
681
  const parse_result = this.parser.Parse(value);
669
682
 
@@ -694,10 +707,12 @@ export class DataModel {
694
707
  if (modified) {
695
708
  return '=' + this.parser.Render(parse_result.expression, {
696
709
  missing: '', boolean_true, boolean_false,
710
+ r1c1: options?.r1c1,
697
711
  });
698
712
  }
699
713
  }
700
714
 
715
+ this.parser.Restore();
701
716
  return value;
702
717
 
703
718
  }
@@ -33,6 +33,7 @@ interface NamedRange {
33
33
  area: Area;
34
34
  }
35
35
 
36
+ /** @internal */
36
37
  export type Named = (NamedExpression | NamedRange) & {
37
38
  name: string; // canonical name
38
39
  scope?: number; // scope to sheet by ID
@@ -57,6 +58,12 @@ export interface SerializedNamed {
57
58
  /** scope is a sheet name (not ID) */
58
59
  scope?: string;
59
60
 
61
+ /**
62
+ * adding type. this is optional, it's not used by tooling. it's
63
+ * just for informational purpopses for clients.
64
+ */
65
+ type?: 'range'|'expression';
66
+
60
67
  }
61
68
 
62
69
  /**
@@ -1631,6 +1631,23 @@ export class Sheet {
1631
1631
  cell.rendered_type = ValueType.dimensioned_quantity; // who cares about rendered_type? (...)
1632
1632
 
1633
1633
  }
1634
+ else if (type === ValueType.function) {
1635
+
1636
+ /*
1637
+ // FIXME: lock down this type (function)
1638
+
1639
+ if ((cell.calculated as any)?.alt) {
1640
+ cell.formatted = (cell.calculated as any).alt.toString();
1641
+ cell.rendered_type = ValueType.string;
1642
+ }
1643
+ else
1644
+ */
1645
+
1646
+ {
1647
+ cell.formatted = '𝑓()'; // FIXME
1648
+ cell.rendered_type = ValueType.string;
1649
+ }
1650
+ }
1634
1651
  else {
1635
1652
 
1636
1653
  // why is this being treated as a number? (...)
@@ -2539,7 +2556,7 @@ export class Sheet {
2539
2556
  cell_reference_map[c] = [];
2540
2557
  for (let r = 0; r < column.length; r++) {
2541
2558
  if (column[r]) {
2542
- const style_as_json = JSON.stringify(column[r]);
2559
+ const style_as_json = Style.Serialize(column[r]); // JSON.stringify(column[r]);
2543
2560
  if (style_as_json !== empty_json) {
2544
2561
  let reference_index = cell_style_map[style_as_json];
2545
2562
  if (typeof reference_index !== 'number') {
@@ -2562,7 +2579,7 @@ export class Sheet {
2562
2579
  */
2563
2580
  const StyleToRef = (style: CellStyle) => {
2564
2581
 
2565
- const style_as_json = JSON.stringify(style);
2582
+ const style_as_json = Style.Serialize(style); // JSON.stringify(style);
2566
2583
  if (style_as_json === empty_json) {
2567
2584
  return 0;
2568
2585
  }
@@ -49,9 +49,9 @@ import type {
49
49
  CondifionalFormatExpressionOptions,
50
50
  ConditionalFormatCellMatchOptions,
51
51
  ConditionalFormatCellMatch,
52
-
53
52
  LanguageModel,
54
53
  TranslatedFunctionDescriptor,
54
+ SerializedNamed,
55
55
 
56
56
  } from 'treb-data-model';
57
57
 
@@ -2242,8 +2242,11 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
2242
2242
 
2243
2243
  // console.info({events});
2244
2244
 
2245
+ // FIXME: composite? (...)
2246
+
2245
2247
  for (const event of events) {
2246
2248
  switch (event.type) {
2249
+ case 'composite':
2247
2250
  case 'data':
2248
2251
  recalc = true;
2249
2252
  break;
@@ -4000,8 +4003,13 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
4000
4003
  // API v1 OK
4001
4004
 
4002
4005
  let area: Area | undefined;
4003
- if (event && event.type === 'data' && event.area) {
4004
- area = event.area;
4006
+ switch (event?.type) {
4007
+ case 'data':
4008
+ area = event.area;
4009
+ break;
4010
+ case 'composite':
4011
+ area = event.data_area;
4012
+ break;
4005
4013
  }
4006
4014
 
4007
4015
  this.calculator.Calculate(area);
@@ -4172,7 +4180,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
4172
4180
  *
4173
4181
  * @public
4174
4182
  */
4175
- public Resolve(reference: string): ICellAddress | IArea | undefined {
4183
+ public Resolve(reference: string, options?: { r1c1?: boolean }): ICellAddress | IArea | undefined {
4176
4184
 
4177
4185
  // API v1 OK
4178
4186
 
@@ -4183,7 +4191,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
4183
4191
  // FIXME: we're using the sheet EnsureAddress method, but that should
4184
4192
  // move either in here or into some sort of helper class
4185
4193
 
4186
- const result = this.model.ResolveAddress(reference, this.grid.active_sheet);
4194
+ const result = this.model.ResolveAddress(reference, this.grid.active_sheet, options);
4187
4195
 
4188
4196
  if (IsCellAddress(result)) {
4189
4197
  return result.sheet_id ? result : undefined;
@@ -4418,6 +4426,10 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
4418
4426
 
4419
4427
  }
4420
4428
 
4429
+ public ListNames(): SerializedNamed[] {
4430
+ return this.model.SerializeNames();
4431
+ }
4432
+
4421
4433
  /**
4422
4434
  * Create a named range or named expression. A named range refers to an
4423
4435
  * address or range. A named expression can be any value or formula. To set
@@ -6445,7 +6457,10 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
6445
6457
  model.named.Reset();
6446
6458
 
6447
6459
  if (data.named) {
6460
+
6461
+ // use locale setting for parsing
6448
6462
  this.model.UnserializeNames(data.named, this.grid.active_sheet);
6463
+
6449
6464
  }
6450
6465
  else {
6451
6466
 
@@ -6482,6 +6497,8 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
6482
6497
  if (data.named_expressions) {
6483
6498
  for (const pair of data.named_expressions) {
6484
6499
 
6500
+ // use locale setting for parsing
6501
+
6485
6502
  this.model.UnserializeNames([{
6486
6503
  name: pair.name,
6487
6504
  expression: pair.expression,
@@ -33,24 +33,6 @@
33
33
  $primary-selection-color: #4caaf1;
34
34
  $primary-selection-color-unfocused: #acc0cf;
35
35
 
36
- /*
37
- $alternate-selection-color-1: rgb(251, 177, 60);
38
- $alternate-selection-color-2: rgb(64, 192, 64);
39
- $alternate-selection-color-3: rgb(182, 109, 13);
40
- $alternate-selection-color-4: rgb(33, 118, 174);
41
- $alternate-selection-color-5: rgb(254, 104, 71);
42
-
43
- / * *
44
- * slightly darkening colors for text highlighting
45
- * algo: convert to HSL; if L > .5, regenerate with L = .5; back to RGB (why?)
46
- * /
47
- $text-reference-color-1: rgb(250, 155, 5);
48
- $text-reference-color-2: rgb(58, 173, 58);
49
- $text-reference-color-3: rgb(182, 109, 13);
50
- $text-reference-color-4: rgb(33, 118, 174);
51
- $text-reference-color-5: rgb(254, 47, 1);
52
- */
53
-
54
36
  .treb-main.treb-main {
55
37
 
56
38
  --alternate-selection-color-1: rgb(251, 177, 60);
@@ -324,25 +306,21 @@ $text-reference-color-5: rgb(254, 47, 1);
324
306
 
325
307
  /* span:nth-of-type(2n) { */
326
308
  span.highlight-2 {
327
- // color: $text-reference-color-2;
328
309
  color: var(--text-reference-color-2);
329
310
  }
330
311
 
331
312
  /* span:nth-of-type(3n) { */
332
313
  span.highlight-3 {
333
- // color: $text-reference-color-3;
334
314
  color: var(--text-reference-color-3);
335
315
  }
336
316
 
337
317
  /* span:nth-of-type(4n) { */
338
318
  span.highlight-4 {
339
- // color: $text-reference-color-4;
340
319
  color: var(--text-reference-color-4);
341
320
  }
342
321
 
343
322
  /* span:nth-of-type(5n) { */
344
323
  span.highlight-5 {
345
- // color: $text-reference-color-5;
346
324
  color: var(--text-reference-color-5);
347
325
  }
348
326
 
@@ -1036,11 +1036,25 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
1036
1036
  if (matcher) {
1037
1037
  Promise.resolve().then(() => {
1038
1038
  const exec_result = matcher.Exec({ text, cursor: substring_end.length });
1039
+
1040
+ // fix behavior around "f16round", which is a function we accidentally
1041
+ // inherit from Math in Safari and Firefox. it breaks arrow-selection
1042
+ // because it pops up an AC window. we don't want to break AC behavior
1043
+ // on tooltips, though, so we still want to call AC methods. just block
1044
+ // the completion list.
1045
+
1046
+ if (this.selecting && exec_result.completions?.length) {
1047
+ // console.info("Blocking completion list", JSON.stringify(exec_result.completions));
1048
+ exec_result.completions = undefined;
1049
+ }
1050
+
1039
1051
  const node =
1040
1052
  this.NodeAtIndex(exec_result.completions?.length ?
1041
1053
  (exec_result.position || 0) :
1042
1054
  (exec_result.function_position || 0));
1055
+
1043
1056
  this.Autocomplete(exec_result, node);
1057
+
1044
1058
  });
1045
1059
  }
1046
1060
 
@@ -1284,7 +1284,7 @@ export class Grid extends GridBase {
1284
1284
  this.model.named.Reset();
1285
1285
 
1286
1286
  if (import_data.named) {
1287
- this.model.UnserializeNames(import_data.named, this.active_sheet);
1287
+ this.model.UnserializeNames(import_data.named, this.active_sheet, true);
1288
1288
  }
1289
1289
 
1290
1290
  // FIXME: do we need to rebuild autocomplete here (A: yes)
@@ -2041,6 +2041,7 @@ export class Grid extends GridBase {
2041
2041
  command.expression = parse_result.expression;
2042
2042
  }
2043
2043
  else {
2044
+ console.info({expression, parse_result});
2044
2045
  throw new Error('invalid expression');
2045
2046
  }
2046
2047
  }
@@ -2350,11 +2351,12 @@ export class Grid extends GridBase {
2350
2351
 
2351
2352
 
2352
2353
  // this is public so we need to (un)translate.
2353
- data = this.model.UntranslateData(data);
2354
+ // console.info("PRE", {r1c1}, data);
2355
+ data = this.model.UntranslateData(data, { r1c1 });
2356
+ // console.info("POST", data, "\n");
2354
2357
 
2355
2358
  // single value, easiest
2356
2359
  if (!Array.isArray(data)) {
2357
-
2358
2360
  if (recycle || array) {
2359
2361
  this.ExecCommand({ key: CommandKey.SetRange, area: range, value: data, array, r1c1 });
2360
2362
  }
@@ -4553,20 +4555,8 @@ export class Grid extends GridBase {
4553
4555
  if (selecting_argument) {
4554
4556
  this.UpdateSelectedArgument(selection);
4555
4557
  }
4556
- else if (!selection.empty && !selection.area.entire_sheet) {
4557
- if (selection.area.entire_column) {
4558
- this.UpdateAddressLabel(undefined, selection.area.columns + 'C');
4559
- }
4560
- else if (selection.area.entire_row) {
4561
- this.UpdateAddressLabel(undefined, selection.area.rows + 'R');
4562
- }
4563
- else if (selection.area.count > 1) {
4564
- this.UpdateAddressLabel(undefined, selection.area.rows + 'R x ' +
4565
- selection.area.columns + 'C');
4566
- }
4567
- else {
4568
- this.UpdateAddressLabel(selection);
4569
- }
4558
+ else {
4559
+ this.UpdateAddressLabelArea(selection); // 3R x 2C
4570
4560
  }
4571
4561
  }
4572
4562
  }, () => {
@@ -5113,6 +5103,9 @@ export class Grid extends GridBase {
5113
5103
 
5114
5104
  if (!selection.empty && (delta.columns || delta.rows)) {
5115
5105
  if (this.BlockSelection(selection, !!event.shiftKey, delta.columns, delta.rows)) {
5106
+ if (event.shiftKey && !selecting_argument) {
5107
+ this.UpdateAddressLabelArea(selection);
5108
+ }
5116
5109
  return;
5117
5110
  }
5118
5111
  }
@@ -5279,6 +5272,9 @@ export class Grid extends GridBase {
5279
5272
 
5280
5273
  if (delta.rows || delta.columns) {
5281
5274
  this.AdvanceSelection(delta, selection, within_selection, expand_selection, !editor_open);
5275
+ if (event.shiftKey && !selecting_argument) {
5276
+ this.UpdateAddressLabelArea(selection);
5277
+ }
5282
5278
  }
5283
5279
 
5284
5280
  }
@@ -5659,7 +5655,13 @@ export class Grid extends GridBase {
5659
5655
  }
5660
5656
  }
5661
5657
  }
5662
- if (formula_parse_result.dependencies) {
5658
+ if (formula_parse_result.expression) {
5659
+
5660
+ // we were previously looking at address arguments, and ignoring
5661
+ // ranges. this leads to inconsistent behavior. we'll now look at
5662
+ // ranges when inferring number format.
5663
+
5664
+ // there was also an issue with using the correct sheet, also fixed.
5663
5665
 
5664
5666
  // this was set up to just use the first format we found.
5665
5667
  // updating to change priority -- if the first one is a
@@ -5668,14 +5670,10 @@ export class Grid extends GridBase {
5668
5670
 
5669
5671
  let found_number_format: string|undefined = undefined;
5670
5672
 
5671
- const list = formula_parse_result.dependencies;
5672
- for (const key of Object.keys(list.addresses)) {
5673
- const address = list.addresses[key];
5674
- if (this.active_sheet.HasCellStyle({ ...address })) {
5675
-
5676
- // FIXME: this should not be active_sheet
5677
-
5678
- const test = this.active_sheet.CellData({ ...address });
5673
+ /** returns true if we've found a non-% number format */
5674
+ const Check = (address: ICellAddress, sheet = this.active_sheet) => {
5675
+ if (sheet.HasCellStyle({...address})) {
5676
+ const test = sheet.CellData({ ...address });
5679
5677
  if (test.style && test.style.number_format) {
5680
5678
  if (!found_number_format || /%/.test(found_number_format)) {
5681
5679
 
@@ -5685,13 +5683,43 @@ export class Grid extends GridBase {
5685
5683
 
5686
5684
  found_number_format = NumberFormatCache.Translate(test.style.number_format);
5687
5685
  if (!/%/.test(found_number_format)) {
5688
- break;
5686
+ return true;
5689
5687
  }
5690
5688
 
5691
5689
  }
5692
5690
  }
5693
5691
  }
5694
- }
5692
+ return false;
5693
+ };
5694
+
5695
+ let finished = false;
5696
+ let sheet: Sheet|undefined = this.active_sheet;
5697
+
5698
+ this.parser.Walk(formula_parse_result.expression, unit => {
5699
+
5700
+ if (finished) {
5701
+ return false;
5702
+ }
5703
+
5704
+ switch (unit.type) {
5705
+ case 'address':
5706
+ this.model.ResolveSheetID(unit);
5707
+ sheet = this.model.sheets.Find(unit.sheet_id || 0);
5708
+ finished = finished || Check(unit, sheet);
5709
+ break;
5710
+
5711
+ case 'range':
5712
+ this.model.ResolveSheetID(unit);
5713
+ sheet = this.model.sheets.Find(unit.start.sheet_id || 0);
5714
+ for (const address of new Area(unit.start, unit.end)) {
5715
+ finished = finished || Check(address, sheet);
5716
+ if (finished) { break; }
5717
+ }
5718
+ break;
5719
+ }
5720
+ return !finished;
5721
+
5722
+ });
5695
5723
 
5696
5724
  if (found_number_format) {
5697
5725
 
@@ -7024,6 +7052,24 @@ export class Grid extends GridBase {
7024
7052
  }
7025
7053
  }
7026
7054
 
7055
+ private UpdateAddressLabelArea(selection: GridSelection) {
7056
+ if (!selection.empty && !selection.area.entire_sheet) {
7057
+ if (selection.area.entire_column) {
7058
+ this.UpdateAddressLabel(undefined, selection.area.columns + 'C');
7059
+ }
7060
+ else if (selection.area.entire_row) {
7061
+ this.UpdateAddressLabel(undefined, selection.area.rows + 'R');
7062
+ }
7063
+ else if (selection.area.count > 1) {
7064
+ this.UpdateAddressLabel(undefined, selection.area.rows + 'R x ' +
7065
+ selection.area.columns + 'C');
7066
+ }
7067
+ else {
7068
+ this.UpdateAddressLabel(selection);
7069
+ }
7070
+ }
7071
+ }
7072
+
7027
7073
  private UpdateAddressLabel(selection = this.primary_selection, text?: string) {
7028
7074
 
7029
7075
  if (!this.formula_bar) { return; }
@@ -167,6 +167,17 @@ export interface UnitCall extends BaseUnit {
167
167
 
168
168
  }
169
169
 
170
+ /**
171
+ * new call type: implicit. we might merge these.
172
+ */
173
+ export interface UnitImplicitCall extends BaseUnit {
174
+ type: 'implicit-call';
175
+ position: number;
176
+ args: ExpressionUnit[];
177
+ call: ExpressionUnit;
178
+
179
+ }
180
+
170
181
  /**
171
182
  * this isn't an output type (unless parsing fails), but it's useful
172
183
  * to be able to pass these around with the same semantics.
@@ -177,6 +188,14 @@ export interface UnitOperator extends BaseUnit {
177
188
  operator: string;
178
189
  }
179
190
 
191
+ /**
192
+ * also not an output type
193
+ */
194
+ export interface UnitGroupSeparator extends BaseUnit {
195
+ type: 'group-separator';
196
+ position: number;
197
+ }
198
+
180
199
  /**
181
200
  * expression unit representing a binary operation. operations may be
182
201
  * re-ordered based on precendence.
@@ -259,9 +278,11 @@ export type BaseExpressionUnit =
259
278
  | UnitArray
260
279
  | UnitIdentifier
261
280
  | UnitCall
281
+ | UnitImplicitCall
262
282
  | UnitMissing
263
283
  | UnitGroup
264
284
  | UnitOperator
285
+ | UnitGroupSeparator
265
286
  | UnitBinary
266
287
  | UnitUnary
267
288
  | UnitAddress
@@ -424,6 +445,9 @@ export interface RenderOptions {
424
445
  /** base for offsetting relative R1C1 addresses */
425
446
  r1c1_base?: UnitAddress;
426
447
 
448
+ /** force addresses to be relative */
449
+ r1c1_force_relative?: boolean;
450
+
427
451
  /** if we're just translating, don't have to render addresses */
428
452
  pass_through_addresses?: boolean;
429
453