Merge branch 'develop' into wip/sergeigarin/increase-retries

This commit is contained in:
mergify[bot] 2024-11-21 12:19:23 +00:00 committed by GitHub
commit c29198eb02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 153 additions and 145 deletions

View File

@ -29,6 +29,9 @@
- ["Write" button in component menu allows to evaluate it separately from the - ["Write" button in component menu allows to evaluate it separately from the
rest of the workflow][11523]. rest of the workflow][11523].
- [The documentation editor can now display tables][11564] - [The documentation editor can now display tables][11564]
- [Table Input Widget is now matched for Table.input method instead of
Table.new. Values must be string literals, and their content is parsed to the
suitable type][11612].
[11151]: https://github.com/enso-org/enso/pull/11151 [11151]: https://github.com/enso-org/enso/pull/11151
[11271]: https://github.com/enso-org/enso/pull/11271 [11271]: https://github.com/enso-org/enso/pull/11271
@ -48,6 +51,7 @@
[11547]: https://github.com/enso-org/enso/pull/11547 [11547]: https://github.com/enso-org/enso/pull/11547
[11523]: https://github.com/enso-org/enso/pull/11523 [11523]: https://github.com/enso-org/enso/pull/11523
[11564]: https://github.com/enso-org/enso/pull/11564 [11564]: https://github.com/enso-org/enso/pull/11564
[11612]: https://github.com/enso-org/enso/pull/11612
#### Enso Standard Library #### Enso Standard Library

View File

@ -113,22 +113,22 @@ export async function createTableNode(page: Page) {
// Adding `Table.new` component will display the widget // Adding `Table.new` component will display the widget
await locate.addNewNodeButton(page).click() await locate.addNewNodeButton(page).click()
await expect(locate.componentBrowser(page)).toBeVisible() await expect(locate.componentBrowser(page)).toBeVisible()
await page.keyboard.type('Table.new') await page.keyboard.type('Table.input')
// Wait for CB entry to appear; this way we're sure about node name (binding). // Wait for CB entry to appear; this way we're sure about node name (binding).
await expect(locate.componentBrowserSelectedEntry(page)).toHaveCount(1) await expect(locate.componentBrowserSelectedEntry(page)).toHaveCount(1)
await expect(locate.componentBrowserSelectedEntry(page)).toHaveText('Table.new') await expect(locate.componentBrowserSelectedEntry(page)).toHaveText('Table.input')
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
const node = locate.graphNodeByBinding(page, 'table1') const node = locate.graphNodeByBinding(page, 'table1')
await expect(node).toHaveCount(1) await expect(node).toHaveCount(1)
await expect(node).toBeVisible() await expect(node).toBeVisible()
await mockMethodCallInfo( await mockMethodCallInfo(
page, page,
{ binding: 'table1', expr: 'Table.new' }, { binding: 'table1', expr: 'Table.input' },
{ {
methodPointer: { methodPointer: {
module: 'Standard.Table.Table', module: 'Standard.Table.Table',
definedOnType: 'Standard.Table.Table.Table', definedOnType: 'Standard.Table.Table.Table',
name: 'new', name: 'input',
}, },
notAppliedArguments: [0], notAppliedArguments: [0],
}, },

View File

@ -3,10 +3,10 @@ import { WidgetInputIsSpecificMethodCall } from '@/components/GraphEditor/widget
import TableHeader from '@/components/GraphEditor/widgets/WidgetTableEditor/TableHeader.vue' import TableHeader from '@/components/GraphEditor/widgets/WidgetTableEditor/TableHeader.vue'
import { import {
CELLS_LIMIT, CELLS_LIMIT,
tableNewCallMayBeHandled, tableInputCallMayBeHandled,
useTableNewArgument, useTableInputArgument,
type RowData, type RowData,
} from '@/components/GraphEditor/widgets/WidgetTableEditor/tableNewArgument' } from '@/components/GraphEditor/widgets/WidgetTableEditor/tableInputArgument'
import ResizeHandles from '@/components/ResizeHandles.vue' import ResizeHandles from '@/components/ResizeHandles.vue'
import AgGridTableView from '@/components/shared/AgGridTableView.vue' import AgGridTableView from '@/components/shared/AgGridTableView.vue'
import { injectGraphNavigator } from '@/providers/graphNavigator' import { injectGraphNavigator } from '@/providers/graphNavigator'
@ -55,7 +55,7 @@ const config = computed(() => {
} }
}) })
const { rowData, columnDefs, moveColumn, moveRow, pasteFromClipboard } = useTableNewArgument( const { rowData, columnDefs, moveColumn, moveRow, pasteFromClipboard } = useTableInputArgument(
() => props.input, () => props.input,
graph, graph,
suggestionDb.entries, suggestionDb.entries,
@ -235,12 +235,12 @@ export const widgetDefinition = defineWidget(
WidgetInputIsSpecificMethodCall({ WidgetInputIsSpecificMethodCall({
module: 'Standard.Table.Table', module: 'Standard.Table.Table',
definedOnType: 'Standard.Table.Table.Table', definedOnType: 'Standard.Table.Table.Table',
name: 'new', name: 'input',
}), }),
{ {
priority: 999, priority: 999,
score: (props) => { score: (props) => {
if (!tableNewCallMayBeHandled(props.input.value)) return Score.Mismatch if (!tableInputCallMayBeHandled(props.input.value)) return Score.Mismatch
return Score.Perfect return Score.Perfect
}, },
}, },

View File

@ -4,9 +4,9 @@ import {
NEW_COLUMN_ID, NEW_COLUMN_ID,
ROW_INDEX_HEADER, ROW_INDEX_HEADER,
RowData, RowData,
tableNewCallMayBeHandled, tableInputCallMayBeHandled,
useTableNewArgument, useTableInputArgument,
} from '@/components/GraphEditor/widgets/WidgetTableEditor/tableNewArgument' } from '@/components/GraphEditor/widgets/WidgetTableEditor/tableInputArgument'
import { MenuItem } from '@/components/shared/AgGridTableView.vue' import { MenuItem } from '@/components/shared/AgGridTableView.vue'
import { WidgetInput } from '@/providers/widgetRegistry' import { WidgetInput } from '@/providers/widgetRegistry'
import { SuggestionDb } from '@/stores/suggestionDatabase' import { SuggestionDb } from '@/stores/suggestionDatabase'
@ -24,7 +24,7 @@ function suggestionDbWithNothing() {
} }
function generateTableOfOnes(rows: number, cols: number) { function generateTableOfOnes(rows: number, cols: number) {
const code = `Table.new [${[...Array(cols).keys()].map((i) => `['Column #${i}', [${Array(rows).fill('1').join(',')}]]`).join(',')}]` const code = `Table.input [${[...Array(cols).keys()].map((i) => `['Column #${i}', [${Array(rows).fill("'1'").join(',')}]]`).join(',')}]`
const ast = Ast.parseExpression(code) const ast = Ast.parseExpression(code)
assertDefined(ast) assertDefined(ast)
return ast return ast
@ -38,7 +38,7 @@ assert(CELLS_LIMIT_SQRT === Math.floor(CELLS_LIMIT_SQRT))
test.each([ test.each([
{ {
code: 'Table.new [["a", [1, 2, 3]], ["b", [4, 5, "six"]], ["empty", [Nothing, Standard.Base.Nothing, Nothing]]]', code: "Table.input [['a', ['1', '2', '3']], ['b', ['4', '5', 'six']], ['empty', [Nothing, Standard.Base.Nothing, Nothing]]]",
expectedColumnDefs: [ expectedColumnDefs: [
expectedRowIndexColumnDef, expectedRowIndexColumnDef,
{ headerName: 'a' }, { headerName: 'a' },
@ -47,34 +47,34 @@ test.each([
expectedNewColumnDef, expectedNewColumnDef,
], ],
expectedRows: [ expectedRows: [
{ [ROW_INDEX_HEADER]: 0, a: 1, b: 4, empty: null, '': null }, { [ROW_INDEX_HEADER]: 0, a: '1', b: '4', empty: null, '': null },
{ [ROW_INDEX_HEADER]: 1, a: 2, b: 5, empty: null, '': null }, { [ROW_INDEX_HEADER]: 1, a: '2', b: '5', empty: null, '': null },
{ [ROW_INDEX_HEADER]: 2, a: 3, b: 'six', empty: null, '': null }, { [ROW_INDEX_HEADER]: 2, a: '3', b: 'six', empty: null, '': null },
{ [ROW_INDEX_HEADER]: 3, a: null, b: null, empty: null, '': null }, { [ROW_INDEX_HEADER]: 3, a: null, b: null, empty: null, '': null },
], ],
}, },
{ {
code: 'Table.new []', code: 'Table.input []',
expectedColumnDefs: [expectedRowIndexColumnDef, expectedNewColumnDef], expectedColumnDefs: [expectedRowIndexColumnDef, expectedNewColumnDef],
expectedRows: [{ [ROW_INDEX_HEADER]: 0, '': null }], expectedRows: [{ [ROW_INDEX_HEADER]: 0, '': null }],
}, },
{ {
code: 'Table.new', code: 'Table.input',
expectedColumnDefs: [expectedRowIndexColumnDef, expectedNewColumnDef], expectedColumnDefs: [expectedRowIndexColumnDef, expectedNewColumnDef],
expectedRows: [{ [ROW_INDEX_HEADER]: 0, '': null }], expectedRows: [{ [ROW_INDEX_HEADER]: 0, '': null }],
}, },
{ {
code: 'Table.new _', code: 'Table.input _',
expectedColumnDefs: [expectedRowIndexColumnDef, expectedNewColumnDef], expectedColumnDefs: [expectedRowIndexColumnDef, expectedNewColumnDef],
expectedRows: [{ [ROW_INDEX_HEADER]: 0, '': null }], expectedRows: [{ [ROW_INDEX_HEADER]: 0, '': null }],
}, },
{ {
code: 'Table.new [["a", []]]', code: 'Table.input [["a", []]]',
expectedColumnDefs: [expectedRowIndexColumnDef, { headerName: 'a' }, expectedNewColumnDef], expectedColumnDefs: [expectedRowIndexColumnDef, { headerName: 'a' }, expectedNewColumnDef],
expectedRows: [{ [ROW_INDEX_HEADER]: 0, a: null, '': null }], expectedRows: [{ [ROW_INDEX_HEADER]: 0, a: null, '': null }],
}, },
{ {
code: 'Table.new [["a", [1,,2]], ["b", [3, 4,]], ["c", [, 5, 6]], ["d", [,,]]]', code: "Table.input [['a', ['1',,'2']], ['b', ['3', '4',]], ['c', [, '5', '6']], ['d', [,,]]]",
expectedColumnDefs: [ expectedColumnDefs: [
expectedRowIndexColumnDef, expectedRowIndexColumnDef,
{ headerName: 'a' }, { headerName: 'a' },
@ -84,21 +84,21 @@ test.each([
expectedNewColumnDef, expectedNewColumnDef,
], ],
expectedRows: [ expectedRows: [
{ [ROW_INDEX_HEADER]: 0, a: 1, b: 3, c: null, d: null, '': null }, { [ROW_INDEX_HEADER]: 0, a: '1', b: '3', c: null, d: null, '': null },
{ [ROW_INDEX_HEADER]: 1, a: null, b: 4, c: 5, d: null, '': null }, { [ROW_INDEX_HEADER]: 1, a: null, b: '4', c: '5', d: null, '': null },
{ [ROW_INDEX_HEADER]: 2, a: 2, b: null, c: 6, d: null, '': null }, { [ROW_INDEX_HEADER]: 2, a: '2', b: null, c: '6', d: null, '': null },
{ [ROW_INDEX_HEADER]: 3, a: null, b: null, c: null, d: null, '': null }, { [ROW_INDEX_HEADER]: 3, a: null, b: null, c: null, d: null, '': null },
], ],
}, },
])('Read table from $code', ({ code, expectedColumnDefs, expectedRows }) => { ])('Read table from $code', ({ code, expectedColumnDefs, expectedRows }) => {
const ast = Ast.parseExpression(code) const ast = Ast.parseExpression(code)
assertDefined(ast) assertDefined(ast)
expect(tableNewCallMayBeHandled(ast)).toBeTruthy() expect(tableInputCallMayBeHandled(ast)).toBeTruthy()
const input = WidgetInput.FromAst(ast) const input = WidgetInput.FromAst(ast)
const startEdit = vi.fn() const startEdit = vi.fn()
const addMissingImports = vi.fn() const addMissingImports = vi.fn()
const onUpdate = vi.fn() const onUpdate = vi.fn()
const tableNewArgs = useTableNewArgument( const tableNewArgs = useTableInputArgument(
input, input,
{ startEdit, addMissingImports }, { startEdit, addMissingImports },
suggestionDbWithNothing(), suggestionDbWithNothing(),
@ -160,7 +160,7 @@ test.each([
'Allowed actions in table near limit (rows: $rows, cols: $cols)', 'Allowed actions in table near limit (rows: $rows, cols: $cols)',
({ rows, cols, expectNewRowEnabled, expectNewColEnabled }) => { ({ rows, cols, expectNewRowEnabled, expectNewColEnabled }) => {
const input = WidgetInput.FromAst(generateTableOfOnes(rows, cols)) const input = WidgetInput.FromAst(generateTableOfOnes(rows, cols))
const tableNewArgs = useTableNewArgument( const tableNewArgs = useTableInputArgument(
input, input,
{ startEdit: vi.fn(), addMissingImports: vi.fn() }, { startEdit: vi.fn(), addMissingImports: vi.fn() },
suggestionDbWithNothing(), suggestionDbWithNothing(),
@ -174,16 +174,17 @@ test.each([
) )
test.each([ test.each([
'Table.new 14', 'Table.input 14',
'Table.new array1', 'Table.input array1',
"Table.new ['a', [123]]", "Table.input ['a', ['123']]",
"Table.new [['a', [123]], ['b', [124], []]]", "Table.input [['a', [123]]]",
"Table.new [['a', [123]], ['a'.repeat 170, [123]]]", "Table.input [['a', ['123']], ['b', ['124'], []]]",
"Table.new [['a', [1, 2, 3, 3 + 1]]]", "Table.input [['a', ['123']], ['a'.repeat 170, ['123']]]",
"Table.input [['a', ['1', '2', '3', 3 + 1]]]",
])('"%s" is not valid input for Table Editor Widget', (code) => { ])('"%s" is not valid input for Table Editor Widget', (code) => {
const ast = Ast.parseExpression(code) const ast = Ast.parseExpression(code)
assertDefined(ast) assertDefined(ast)
expect(tableNewCallMayBeHandled(ast)).toBeFalsy() expect(tableInputCallMayBeHandled(ast)).toBeFalsy()
}) })
function tableEditFixture(code: string, expectedCode: string) { function tableEditFixture(code: string, expectedCode: string) {
@ -207,7 +208,7 @@ function tableEditFixture(code: string, expectedCode: string) {
}, },
]) ])
}) })
const tableNewArgs = useTableNewArgument( const tableNewArgs = useTableInputArgument(
input, input,
{ startEdit, addMissingImports }, { startEdit, addMissingImports },
suggestionDbWithNothing(), suggestionDbWithNothing(),
@ -223,62 +224,56 @@ function tableEditFixture(code: string, expectedCode: string) {
test.each([ test.each([
{ {
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]", code: "Table.input [['a', ['1', '2', '3']], ['b', ['4', '5', '6']]]",
description: 'Edit number', description: 'Edit value',
edit: { column: 1, row: 1, value: -22 },
expected: "Table.new [['a', [1, -22, 3]], ['b', [4, 5, 6]]]",
},
{
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]",
description: 'Edit string',
edit: { column: 1, row: 1, value: 'two' }, edit: { column: 1, row: 1, value: 'two' },
expected: "Table.new [['a', [1, 'two', 3]], ['b', [4, 5, 6]]]", expected: "Table.input [['a', ['1', 'two', '3']], ['b', ['4', '5', '6']]]",
}, },
{ {
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]", code: "Table.input [['a', ['1', '2', '3']], ['b', ['4', '5', '6']]]",
description: 'Put blank value', description: 'Put blank value',
edit: { column: 2, row: 1, value: '' }, edit: { column: 2, row: 1, value: '' },
expected: "Table.new [['a', [1, 2, 3]], ['b', [4, Nothing, 6]]]", expected: "Table.input [['a', ['1', '2', '3']], ['b', ['4', Nothing, '6']]]",
importExpected: true, importExpected: true,
}, },
{ {
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]", code: "Table.input [['a', ['1', '2', '3']], ['b', ['4', '5', '6']]]",
description: 'Add new row', description: 'Add new row',
edit: { column: 1, row: 3, value: 4.5 }, edit: { column: 1, row: 3, value: '4.5' },
expected: "Table.new [['a', [1, 2, 3, 4.5]], ['b', [4, 5, 6, Nothing]]]", expected: "Table.input [['a', ['1', '2', '3', '4.5']], ['b', ['4', '5', '6', Nothing]]]",
importExpected: true, importExpected: true,
}, },
{ {
code: "Table.new [['a', []], ['b', []]]", code: "Table.input [['a', []], ['b', []]]",
description: 'Add first row', description: 'Add first row',
edit: { column: 2, row: 0, value: 'val' }, edit: { column: 2, row: 0, value: 'val' },
expected: "Table.new [['a', [Nothing]], ['b', ['val']]]", expected: "Table.input [['a', [Nothing]], ['b', ['val']]]",
importExpected: true, importExpected: true,
}, },
{ {
code: "Table.new [['a', [1, ,3]]]", code: "Table.input [['a', ['1', ,'3']]]",
description: 'Set missing value', description: 'Set missing value',
edit: { column: 1, row: 1, value: 2 }, edit: { column: 1, row: 1, value: '2' },
expected: "Table.new [['a', [1, 2 ,3]]]", expected: "Table.input [['a', ['1', '2' ,'3']]]",
}, },
{ {
code: "Table.new [['a', [, 2, 3]]]", code: "Table.input [['a', [, '2', '3']]]",
description: 'Set missing value at first row', description: 'Set missing value at first row',
edit: { column: 1, row: 0, value: 1 }, edit: { column: 1, row: 0, value: '1' },
expected: "Table.new [['a', [1, 2, 3]]]", expected: "Table.input [['a', ['1', '2', '3']]]",
}, },
{ {
code: "Table.new [['a', [1, 2,]]]", code: "Table.input [['a', ['1', '2',]]]",
description: 'Set missing value at last row', description: 'Set missing value at last row',
edit: { column: 1, row: 2, value: 3 }, edit: { column: 1, row: 2, value: '3' },
expected: "Table.new [['a', [1, 2, 3]]]", expected: "Table.input [['a', ['1', '2', '3']]]",
}, },
{ {
code: "Table.new [['a', [1, 2]], ['a', [3, 4]]]", code: "Table.input [['a', ['1', '2']], ['a', ['3', '4']]]",
description: 'Edit with duplicated column name', description: 'Edit with duplicated column name',
edit: { column: 1, row: 1, value: 5 }, edit: { column: 1, row: 1, value: '5' },
expected: "Table.new [['a', [1, 5]], ['a', [3, 4]]]", expected: "Table.input [['a', ['1', '5']], ['a', ['3', '4']]]",
}, },
])('Edit table $code: $description', ({ code, edit, expected, importExpected }) => { ])('Edit table $code: $description', ({ code, edit, expected, importExpected }) => {
const { tableNewArgs, onUpdate, addMissingImports } = tableEditFixture(code, expected) const { tableNewArgs, onUpdate, addMissingImports } = tableEditFixture(code, expected)
@ -295,21 +290,21 @@ test.each([
test.each([ test.each([
{ {
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]", code: "Table.input [['a', ['1', '2', '3']], ['b', ['4', '5', '6']]]",
expected: `Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]], ['${DEFAULT_COLUMN_PREFIX}3', [Nothing, Nothing, Nothing]]]`, expected: `Table.input [['a', ['1', '2', '3']], ['b', ['4', '5', '6']], ['${DEFAULT_COLUMN_PREFIX}3', [Nothing, Nothing, Nothing]]]`,
importExpected: true, importExpected: true,
}, },
{ {
code: 'Table.new []', code: 'Table.input []',
expected: `Table.new [['${DEFAULT_COLUMN_PREFIX}1', []]]`, expected: `Table.input [['${DEFAULT_COLUMN_PREFIX}1', []]]`,
}, },
{ {
code: 'Table.new', code: 'Table.input',
expected: `Table.new [['${DEFAULT_COLUMN_PREFIX}1', []]]`, expected: `Table.input [['${DEFAULT_COLUMN_PREFIX}1', []]]`,
}, },
{ {
code: 'Table.new _', code: 'Table.input _',
expected: `Table.new [['${DEFAULT_COLUMN_PREFIX}1', []]]`, expected: `Table.input [['${DEFAULT_COLUMN_PREFIX}1', []]]`,
}, },
])('Add column to table $code', ({ code, expected, importExpected }) => { ])('Add column to table $code', ({ code, expected, importExpected }) => {
const { tableNewArgs, onUpdate, addMissingImports } = tableEditFixture(code, expected) const { tableNewArgs, onUpdate, addMissingImports } = tableEditFixture(code, expected)
@ -340,24 +335,24 @@ function getCustomMenuItemByName(
test.each([ test.each([
{ {
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]", code: "Table.input [['a', ['1', '2', '3']], ['b', ['4', '5', '6']]]",
removedRowIndex: 0, removedRowIndex: 0,
expected: "Table.new [['a', [2, 3]], ['b', [5, 6]]]", expected: "Table.input [['a', ['2', '3']], ['b', ['5', '6']]]",
}, },
{ {
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]", code: "Table.input [['a', ['1', '2', '3']], ['b', ['4', '5', '6']]]",
removedRowIndex: 1, removedRowIndex: 1,
expected: "Table.new [['a', [1, 3]], ['b', [4, 6]]]", expected: "Table.input [['a', ['1', '3']], ['b', ['4', '6']]]",
}, },
{ {
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]", code: "Table.input [['a', ['1', '2', '3']], ['b', ['4', '5', '6']]]",
removedRowIndex: 2, removedRowIndex: 2,
expected: "Table.new [['a', [1, 2]], ['b', [4, 5]]]", expected: "Table.input [['a', ['1', '2']], ['b', ['4', '5']]]",
}, },
{ {
code: "Table.new [['a', [1]], ['b', [4]]]", code: "Table.input [['a', ['1']], ['b', ['4']]]",
removedRowIndex: 0, removedRowIndex: 0,
expected: "Table.new [['a', []], ['b', []]]", expected: "Table.input [['a', []], ['b', []]]",
}, },
])('Remove $removedRowIndex row in $code', ({ code, removedRowIndex, expected }) => { ])('Remove $removedRowIndex row in $code', ({ code, removedRowIndex, expected }) => {
const { tableNewArgs, onUpdate, addMissingImports, gridApi } = tableEditFixture(code, expected) const { tableNewArgs, onUpdate, addMissingImports, gridApi } = tableEditFixture(code, expected)
@ -376,24 +371,24 @@ test.each([
test.each([ test.each([
{ {
code: "Table.new [['a', [1, 2]], ['b', [3, 4]], ['c', [5, 6]]]", code: "Table.input [['a', ['1', '2']], ['b', ['3', '4']], ['c', ['5', '6']]]",
removedColIndex: 1, removedColIndex: 1,
expected: "Table.new [['b', [3, 4]], ['c', [5, 6]]]", expected: "Table.input [['b', ['3', '4']], ['c', ['5', '6']]]",
}, },
{ {
code: "Table.new [['a', [1, 2]], ['b', [3, 4]], ['c', [5, 6]]]", code: "Table.input [['a', ['1', '2']], ['b', ['3', '4']], ['c', ['5', '6']]]",
removedColIndex: 2, removedColIndex: 2,
expected: "Table.new [['a', [1, 2]], ['c', [5, 6]]]", expected: "Table.input [['a', ['1', '2']], ['c', ['5', '6']]]",
}, },
{ {
code: "Table.new [['a', [1, 2]], ['b', [3, 4]], ['c', [5, 6]]]", code: "Table.input [['a', ['1', '2']], ['b', ['3', '4']], ['c', ['5', '6']]]",
removedColIndex: 3, removedColIndex: 3,
expected: "Table.new [['a', [1, 2]], ['b', [3, 4]]]", expected: "Table.input [['a', ['1', '2']], ['b', ['3', '4']]]",
}, },
{ {
code: "Table.new [['a', [1, 2]]]", code: "Table.input [['a', ['1', '2']]]",
removedColIndex: 1, removedColIndex: 1,
expected: 'Table.new []', expected: 'Table.input []',
}, },
])('Remove $removedColIndex column in $code', ({ code, removedColIndex, expected }) => { ])('Remove $removedColIndex column in $code', ({ code, removedColIndex, expected }) => {
const { tableNewArgs, onUpdate, addMissingImports, gridApi } = tableEditFixture(code, expected) const { tableNewArgs, onUpdate, addMissingImports, gridApi } = tableEditFixture(code, expected)
@ -414,16 +409,16 @@ test.each([
test.each([ test.each([
{ {
code: "Table.new [['a', [1, 2]], ['b', [3, 4]], ['c', [5, 6]]]", code: "Table.input [['a', ['1', '2']], ['b', ['3', '4']], ['c', ['5', '6']]]",
fromIndex: 1, fromIndex: 1,
toIndex: 3, toIndex: 3,
expected: "Table.new [['b', [3, 4]], ['c', [5, 6]], ['a', [1, 2]]]", expected: "Table.input [['b', ['3', '4']], ['c', ['5', '6']], ['a', ['1', '2']]]",
}, },
{ {
code: "Table.new [['a', [1, 2]], ['b', [3, 4]], ['c', [5, 6]]]", code: "Table.input [['a', ['1', '2']], ['b', ['3', '4']], ['c', ['5', '6']]]",
fromIndex: 3, fromIndex: 3,
toIndex: 2, toIndex: 2,
expected: "Table.new [['a', [1, 2]], ['c', [5, 6]], ['b', [3, 4]]]", expected: "Table.input [['a', ['1', '2']], ['c', ['5', '6']], ['b', ['3', '4']]]",
}, },
])( ])(
'Move column $fromIndex to $toIndex in table $code', 'Move column $fromIndex to $toIndex in table $code',
@ -439,22 +434,22 @@ test.each([
test.each([ test.each([
{ {
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]", code: "Table.input [['a', ['1', '2', '3']], ['b', ['4', '5', '6']]]",
fromIndex: 1, fromIndex: 1,
toIndex: 2, toIndex: 2,
expected: "Table.new [['a', [1, 3, 2]], ['b', [4, 6, 5]]]", expected: "Table.input [['a', ['1', '3', '2']], ['b', ['4', '6', '5']]]",
}, },
{ {
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]", code: "Table.input [['a', ['1', '2', '3']], ['b', ['4', '5', '6']]]",
fromIndex: 2, fromIndex: 2,
toIndex: 0, toIndex: 0,
expected: "Table.new [['a', [3, 1, 2]], ['b', [6, 4, 5]]]", expected: "Table.input [['a', ['3', '1', '2']], ['b', ['6', '4', '5']]]",
}, },
{ {
code: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]", code: "Table.input [['a', ['1', '2', '3']], ['b', ['4', '5', '6']]]",
fromIndex: 1, fromIndex: 1,
toIndex: -1, toIndex: -1,
expected: "Table.new [['a', [1, 2, 3]], ['b', [4, 5, 6]]]", expected: "Table.input [['a', ['1', '2', '3']], ['b', ['4', '5', '6']]]",
}, },
])('Move row $fromIndex to $toIndex in table $code', ({ code, fromIndex, toIndex, expected }) => { ])('Move row $fromIndex to $toIndex in table $code', ({ code, fromIndex, toIndex, expected }) => {
const { tableNewArgs, onUpdate, addMissingImports } = tableEditFixture(code, expected) const { tableNewArgs, onUpdate, addMissingImports } = tableEditFixture(code, expected)
@ -467,100 +462,100 @@ test.each([
test.each([ test.each([
{ {
code: 'Table.new', code: 'Table.input',
focused: { rowIndex: 0, colIndex: 1 }, focused: { rowIndex: 0, colIndex: 1 },
data: [ data: [
['1', '3'], ['1', '3'],
['2', '4'], ['2', '4'],
], ],
expected: `Table.new [['${DEFAULT_COLUMN_PREFIX}1', [1, 2]], ['${DEFAULT_COLUMN_PREFIX}2', [3, 4]]]`, expected: `Table.input [['${DEFAULT_COLUMN_PREFIX}1', ['1', '2']], ['${DEFAULT_COLUMN_PREFIX}2', ['3', '4']]]`,
}, },
{ {
code: 'Table.new []', code: 'Table.input []',
focused: { rowIndex: 0, colIndex: 1 }, focused: { rowIndex: 0, colIndex: 1 },
data: [ data: [
['1', '3'], ['1', '3'],
['2', '4'], ['2', '4'],
], ],
expected: `Table.new [['${DEFAULT_COLUMN_PREFIX}1', [1, 2]], ['${DEFAULT_COLUMN_PREFIX}2', [3, 4]]]`, expected: `Table.input [['${DEFAULT_COLUMN_PREFIX}1', ['1', '2']], ['${DEFAULT_COLUMN_PREFIX}2', ['3', '4']]]`,
}, },
{ {
code: 'Table.new []', code: 'Table.input []',
focused: { rowIndex: 0, colIndex: 1 }, focused: { rowIndex: 0, colIndex: 1 },
data: [['a single cell']], data: [['a single cell']],
expected: `Table.new [['${DEFAULT_COLUMN_PREFIX}1', ['a single cell']]]`, expected: `Table.input [['${DEFAULT_COLUMN_PREFIX}1', ['a single cell']]]`,
}, },
{ {
code: "Table.new [['a', [1, 2]], ['b', [3, 4]]]", code: "Table.input [['a', ['1', '2']], ['b', ['3', '4']]]",
focused: { rowIndex: 0, colIndex: 1 }, focused: { rowIndex: 0, colIndex: 1 },
data: [['a single cell']], data: [['a single cell']],
expected: "Table.new [['a', ['a single cell', 2]], ['b', [3, 4]]]", expected: "Table.input [['a', ['a single cell', '2']], ['b', ['3', '4']]]",
}, },
{ {
code: "Table.new [['a', [1, 2]], ['b', [3, 4]]]", code: "Table.input [['a', ['1', '2']], ['b', ['3', '4']]]",
focused: { rowIndex: 1, colIndex: 2 }, focused: { rowIndex: 1, colIndex: 2 },
data: [['a single cell']], data: [['a single cell']],
expected: "Table.new [['a', [1, 2]], ['b', [3, 'a single cell']]]", expected: "Table.input [['a', ['1', '2']], ['b', ['3', 'a single cell']]]",
}, },
{ {
code: "Table.new [['a', [1, 2]], ['b', [3, 4]]]", code: "Table.input [['a', ['1', '2']], ['b', ['3', '4']]]",
focused: { rowIndex: 2, colIndex: 2 }, focused: { rowIndex: 2, colIndex: 2 },
data: [['a single cell']], data: [['a single cell']],
expected: "Table.new [['a', [1, 2, Nothing]], ['b', [3, 4, 'a single cell']]]", expected: "Table.input [['a', ['1', '2', Nothing]], ['b', ['3', '4', 'a single cell']]]",
importExpected: true, importExpected: true,
}, },
{ {
code: "Table.new [['a', [1, 2]], ['b', [3, 4]]]", code: "Table.input [['a', ['1', '2']], ['b', ['3', '4']]]",
focused: { rowIndex: 1, colIndex: 3 }, focused: { rowIndex: 1, colIndex: 3 },
data: [['a single cell']], data: [['a single cell']],
expected: `Table.new [['a', [1, 2]], ['b', [3, 4]], ['${DEFAULT_COLUMN_PREFIX}3', [Nothing, 'a single cell']]]`, expected: `Table.input [['a', ['1', '2']], ['b', ['3', '4']], ['${DEFAULT_COLUMN_PREFIX}3', [Nothing, 'a single cell']]]`,
importExpected: true, importExpected: true,
}, },
{ {
code: "Table.new [['a', [1, 2]], ['b', [3, 4]]]", code: "Table.input [['a', ['1', '2']], ['b', ['3', '4']]]",
focused: { rowIndex: 0, colIndex: 1 }, focused: { rowIndex: 0, colIndex: 1 },
data: [ data: [
['5', '7'], ['5', '7'],
['6', '8'], ['6', '8'],
], ],
expected: "Table.new [['a', [5, 6]], ['b', [7, 8]]]", expected: "Table.input [['a', ['5', '6']], ['b', ['7', '8']]]",
}, },
{ {
code: "Table.new [['a', [1, 2]], ['b', [3, 4]]]", code: "Table.input [['a', ['1', '2']], ['b', ['3', '4']]]",
focused: { rowIndex: 1, colIndex: 1 }, focused: { rowIndex: 1, colIndex: 1 },
data: [ data: [
['5', '7'], ['5', '7'],
['6', '8'], ['6', '8'],
], ],
expected: "Table.new [['a', [1, 5, 6]], ['b', [3, 7, 8]]]", expected: "Table.input [['a', ['1', '5', '6']], ['b', ['3', '7', '8']]]",
}, },
{ {
code: "Table.new [['a', [1, 2]], ['b', [3, 4]]]", code: "Table.input [['a', ['1', '2']], ['b', ['3', '4']]]",
focused: { rowIndex: 0, colIndex: 2 }, focused: { rowIndex: 0, colIndex: 2 },
data: [ data: [
['5', '7'], ['5', '7'],
['6', '8'], ['6', '8'],
], ],
expected: `Table.new [['a', [1, 2]], ['b', [5, 6]], ['${DEFAULT_COLUMN_PREFIX}3', [7, 8]]]`, expected: `Table.input [['a', ['1', '2']], ['b', ['5', '6']], ['${DEFAULT_COLUMN_PREFIX}3', ['7', '8']]]`,
}, },
{ {
code: "Table.new [['a', [1, 2]], ['b', [3, 4]]]", code: "Table.input [['a', ['1', '2']], ['b', ['3', '4']]]",
focused: { rowIndex: 1, colIndex: 2 }, focused: { rowIndex: 1, colIndex: 2 },
data: [ data: [
['5', '7'], ['5', '7'],
['6', '8'], ['6', '8'],
], ],
expected: `Table.new [['a', [1, 2, Nothing]], ['b', [3, 5, 6]], ['${DEFAULT_COLUMN_PREFIX}3', [Nothing, 7, 8]]]`, expected: `Table.input [['a', ['1', '2', Nothing]], ['b', ['3', '5', '6']], ['${DEFAULT_COLUMN_PREFIX}3', [Nothing, '7', '8']]]`,
importExpected: true, importExpected: true,
}, },
{ {
code: "Table.new [['a', [1, 2]], ['b', [3, 4]]]", code: "Table.input [['a', ['1', '2']], ['b', ['3', '4']]]",
focused: { rowIndex: 2, colIndex: 2 }, focused: { rowIndex: 2, colIndex: 2 },
data: [ data: [
['5', '7'], ['5', '7'],
['6', '8'], ['6', '8'],
], ],
expected: `Table.new [['a', [1, 2, Nothing, Nothing]], ['b', [3, 4, 5, 6]], ['${DEFAULT_COLUMN_PREFIX}3', [Nothing, Nothing, 7, 8]]]`, expected: `Table.input [['a', ['1', '2', Nothing, Nothing]], ['b', ['3', '4', '5', '6']], ['${DEFAULT_COLUMN_PREFIX}3', [Nothing, Nothing, '7', '8']]]`,
importExpected: true, importExpected: true,
}, },
])( ])(
@ -593,12 +588,13 @@ test('Pasted data which would exceed cells limit is truncated', () => {
let cellCount = 0 let cellCount = 0
inputAst.visitRecursive((ast: Ast.Ast | Ast.Token) => { inputAst.visitRecursive((ast: Ast.Ast | Ast.Token) => {
if (ast instanceof Ast.Token) return if (ast instanceof Ast.Token) return
if (ast instanceof Ast.NumericLiteral || ast.code() === 'Nothing') cellCount++ if (ast instanceof Ast.TextLiteral && ast.code().startsWith("'Column #")) return
if (ast instanceof Ast.TextLiteral || ast.code() === 'Nothing') cellCount++
}) })
expect(cellCount).toBe(CELLS_LIMIT) expect(cellCount).toBe(CELLS_LIMIT)
}) })
const addMissingImports = vi.fn() const addMissingImports = vi.fn()
const tableNewArgs = useTableNewArgument( const tableNewArgs = useTableInputArgument(
input, input,
{ startEdit, addMissingImports }, { startEdit, addMissingImports },
suggestionDbWithNothing(), suggestionDbWithNothing(),

View File

@ -4,7 +4,6 @@ import { type RequiredImport, requiredImportsByFQN } from '@/stores/graph/import
import type { SuggestionDb } from '@/stores/suggestionDatabase' import type { SuggestionDb } from '@/stores/suggestionDatabase'
import { assert } from '@/util/assert' import { assert } from '@/util/assert'
import { Ast } from '@/util/ast' import { Ast } from '@/util/ast'
import { tryEnsoToNumber, tryNumberToEnso } from '@/util/ast/abstract'
import { findIndexOpt } from '@/util/data/array' import { findIndexOpt } from '@/util/data/array'
import { Err, Ok, type Result, transposeResult, unwrapOrWithLog } from '@/util/data/result' import { Err, Ok, type Result, transposeResult, unwrapOrWithLog } from '@/util/data/result'
import { qnLastSegment, type QualifiedName } from '@/util/qualifiedName' import { qnLastSegment, type QualifiedName } from '@/util/qualifiedName'
@ -41,7 +40,7 @@ export type RowData = {
*/ */
export interface ColumnDef extends ColDef<RowData> { export interface ColumnDef extends ColDef<RowData> {
valueGetter: ({ data }: { data: RowData | undefined }) => any valueGetter: ({ data }: { data: RowData | undefined }) => any
valueSetter?: ({ data, newValue }: { data: RowData; newValue: any }) => boolean valueSetter?: ({ data, newValue }: { data: RowData; newValue: string }) => boolean
mainMenuItems: (string | MenuItem<RowData>)[] mainMenuItems: (string | MenuItem<RowData>)[]
contextMenuItems: (string | MenuItem<RowData>)[] contextMenuItems: (string | MenuItem<RowData>)[]
rowDrag?: ({ data }: { data: RowData | undefined }) => boolean rowDrag?: ({ data }: { data: RowData | undefined }) => boolean
@ -54,11 +53,7 @@ namespace cellValueConversion {
if (ast instanceof Ast.TextLiteral) return Ok(ast.rawTextContent) if (ast instanceof Ast.TextLiteral) return Ok(ast.rawTextContent)
else if (ast instanceof Ast.Ident && ast.code() === NOTHING_NAME) return Ok(null) else if (ast instanceof Ast.Ident && ast.code() === NOTHING_NAME) return Ok(null)
else if (ast instanceof Ast.PropertyAccess && ast.rhs.code() === NOTHING_NAME) return Ok(null) else if (ast instanceof Ast.PropertyAccess && ast.rhs.code() === NOTHING_NAME) return Ok(null)
else { else return Err('Ast is not convertible to AGGrid value')
const asNumber = tryEnsoToNumber(ast)
if (asNumber != null) return Ok(asNumber)
else return Err('Ast is not convertible to AGGrid value')
}
} }
/** /**
@ -72,16 +67,9 @@ namespace cellValueConversion {
): { ast: Ast.Owned<Ast.MutableExpression>; requireNothingImport: boolean } { ): { ast: Ast.Owned<Ast.MutableExpression>; requireNothingImport: boolean } {
if (value == null || value === '') { if (value == null || value === '') {
return { ast: Ast.Ident.new(module, 'Nothing' as Ast.Identifier), requireNothingImport: true } return { ast: Ast.Ident.new(module, 'Nothing' as Ast.Identifier), requireNothingImport: true }
} else if (typeof value === 'number') {
return {
ast: tryNumberToEnso(value, module) ?? Ast.TextLiteral.new(`${value}`, module),
requireNothingImport: false,
}
} else { } else {
return { return {
ast: ast: Ast.TextLiteral.new(`${value}`, module),
Ast.NumericLiteral.tryParseWithSign(`${value}`, module) ??
Ast.TextLiteral.new(`${value}`, module),
requireNothingImport: false, requireNothingImport: false,
} }
} }
@ -92,7 +80,7 @@ function retrieveColumnsAst(call: Ast.Expression): Result<Ast.Vector | undefined
if (!(call instanceof Ast.App)) return Ok(undefined) if (!(call instanceof Ast.App)) return Ok(undefined)
if (call.argument instanceof Ast.Vector) return Ok(call.argument) if (call.argument instanceof Ast.Vector) return Ok(call.argument)
if (call.argument instanceof Ast.Wildcard) return Ok(undefined) if (call.argument instanceof Ast.Wildcard) return Ok(undefined)
return Err('Expected Table.new argument to be a vector of columns or placeholder') return Err('Expected Table.input argument to be a vector of columns or placeholder')
} }
function readColumn( function readColumn(
@ -121,11 +109,11 @@ function retrieveColumnsDefinitions(columnsAst: Ast.Vector) {
} }
/** /**
* Check if given ast is a `Table.new` call which may be handled by the TableEditorWidget. * Check if given ast is a `Table.input` call which may be handled by the TableEditorWidget.
* *
* This widget may handle table definitions filled with literals or `Nothing` values. * This widget may handle table definitions filled with literals or `Nothing` values.
*/ */
export function tableNewCallMayBeHandled(call: Ast.Expression) { export function tableInputCallMayBeHandled(call: Ast.Expression) {
const columnsAst = retrieveColumnsAst(call) const columnsAst = retrieveColumnsAst(call)
if (!columnsAst.ok) return false if (!columnsAst.ok) return false
if (!columnsAst.value) return true // We can handle lack of the argument if (!columnsAst.value) return true // We can handle lack of the argument
@ -140,13 +128,13 @@ export function tableNewCallMayBeHandled(call: Ast.Expression) {
} }
/** /**
* A composable responsible for interpreting `Table.new` expressions, creating AGGrid column * A composable responsible for interpreting `Table.input` expressions, creating AGGrid column
* definitions allowing also editing AST through AGGrid editing. * definitions allowing also editing AST through AGGrid editing.
* @param input the widget's input * @param input the widget's input
* @param graph the graph store * @param graph the graph store
* @param onUpdate callback called when AGGrid was edited by user, resulting in AST change. * @param onUpdate callback called when AGGrid was edited by user, resulting in AST change.
*/ */
export function useTableNewArgument( export function useTableInputArgument(
input: ToValue<WidgetInput & { value: Ast.Expression }>, input: ToValue<WidgetInput & { value: Ast.Expression }>,
graph: { graph: {
startEdit(): Ast.MutableModule startEdit(): Ast.MutableModule

View File

@ -1047,6 +1047,26 @@
"documentation": "", "documentation": "",
"annotations": [] "annotations": []
}, },
{
"type": "method",
"module": "Standard.Table.Table",
"name": "input",
"arguments": [
{
"name": "columns",
"reprType": "Standard.Base.Data.Vector.Vector Standard.Base.Data.Vector.Vector | Standard.Table.Column.Column",
"isSuspended": false,
"hasDefault": false,
"defaultValue": null,
"tagValues": null
}
],
"selfType": "Standard.Table.Table.Table",
"returnType": "Standard.Table.Table.Table",
"isStatic": true,
"documentation": "",
"annotations": []
},
{ {
"type": "method", "type": "method",
"module": "Standard.Table.Table", "module": "Standard.Table.Table",