mirror of
https://github.com/enso-org/enso.git
synced 2024-08-17 00:40:34 +03:00
Fix missing dropdowns in constructor subexpressions (#10382)
Fixes #10341 <img width="487" alt="image" src="https://github.com/enso-org/enso/assets/919491/af946c1c-a27f-4ed8-8346-b5098e7d5f08"> Also added a new version of vue devtools that is embedded into the dev app itself, and has much better performance than the browser plugin.
This commit is contained in:
parent
db4f7ab3b5
commit
2f7adb9deb
@ -139,8 +139,8 @@
|
|||||||
"tsx": "^4.7.1",
|
"tsx": "^4.7.1",
|
||||||
"typescript": "~5.2.2",
|
"typescript": "~5.2.2",
|
||||||
"unbzip2-stream": "^1.4.3",
|
"unbzip2-stream": "^1.4.3",
|
||||||
"vite": "^4.4.9",
|
"vite": "^5.3.1",
|
||||||
"vite-plugin-inspect": "^0.7.38",
|
"vite-plugin-vue-devtools": "7.3.4",
|
||||||
"vitest": "^1.3.1",
|
"vitest": "^1.3.1",
|
||||||
"vue-react-wrapper": "^0.3.1",
|
"vue-react-wrapper": "^0.3.1",
|
||||||
"vue-tsc": "^1.8.27"
|
"vue-tsc": "^1.8.27"
|
||||||
|
@ -40,22 +40,24 @@ const staticMethod = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test.each`
|
test.each`
|
||||||
code | callSuggestion | subjectSpan | selfSpan | subjectType | methodName
|
code | callSuggestion | subjectSpan | attachedSpan | subjectType | methodName
|
||||||
${'val1.method val2'} | ${method} | ${[0, 4]} | ${[0, 4]} | ${'local.Project.Type'} | ${'.method'}
|
${'val1.method val2'} | ${method} | ${[0, 4]} | ${[0, 4]} | ${'local.Project.Type'} | ${'.method'}
|
||||||
${'local.Project.Type.method val1 val2'} | ${method} | ${[0, 18]} | ${[26, 30]} | ${'local.Project.Type.type'} | ${'.method'}
|
${'local.Project.Type.method val1 val2'} | ${method} | ${[0, 18]} | ${[26, 30]} | ${'local.Project.Type.type'} | ${'.method'}
|
||||||
${'Type.method val1'} | ${method} | ${[0, 4]} | ${[12, 16]} | ${'local.Project.Type.type'} | ${'.method'}
|
${'Type.method val1'} | ${method} | ${[0, 4]} | ${[12, 16]} | ${'local.Project.Type.type'} | ${'.method'}
|
||||||
${'local.Project.Type.method'} | ${method} | ${[0, 18]} | ${[0, 18]} | ${'local.Project.Type.type'} | ${'.method'}
|
${'local.Project.Type.method'} | ${method} | ${[0, 18]} | ${null} | ${'local.Project.Type.type'} | ${'.method'}
|
||||||
|
${'foo.method'} | ${method} | ${[0, 3]} | ${null} | ${'local.Project.Type.type'} | ${'.method'}
|
||||||
|
${'foo.method'} | ${method} | ${[0, 3]} | ${[0, 3]} | ${'local.Project.Type'} | ${'.method'}
|
||||||
${'local.Project.Type.static_method val1'} | ${staticMethod} | ${[0, 18]} | ${[0, 18]} | ${'local.Project.Type.type'} | ${'.static_method'}
|
${'local.Project.Type.static_method val1'} | ${staticMethod} | ${[0, 18]} | ${[0, 18]} | ${'local.Project.Type.type'} | ${'.static_method'}
|
||||||
${'Type.Con val1'} | ${con} | ${[0, 4]} | ${[0, 4]} | ${'local.Project.Type.type'} | ${'.Con'}
|
${'Type.Con val1'} | ${con} | ${[0, 4]} | ${[0, 4]} | ${'local.Project.Type.type'} | ${'.Con'}
|
||||||
${'..Con val1'} | ${con} | ${null} | ${null} | ${null} | ${'.Con'}
|
${'..Con val1'} | ${con} | ${null} | ${null} | ${null} | ${'.Con'}
|
||||||
${'local.Project.module_method val1'} | ${moduleMethod} | ${[0, 13]} | ${[0, 13]} | ${'local.Project'} | ${'.module_method'}
|
${'local.Project.module_method val1'} | ${moduleMethod} | ${[0, 13]} | ${[0, 13]} | ${'local.Project'} | ${'.module_method'}
|
||||||
`(
|
`(
|
||||||
'Visualization config for $code',
|
'Visualization config for $code',
|
||||||
({ code, callSuggestion, subjectSpan, selfSpan, subjectType, methodName }) => {
|
({ code, callSuggestion, subjectSpan, attachedSpan, subjectType, methodName }) => {
|
||||||
const spans = {
|
const spans = {
|
||||||
entireFunction: [0, code.length] as [number, number],
|
entireFunction: [0, code.length] as [number, number],
|
||||||
...(subjectSpan != null ? { subject: subjectSpan as [number, number] } : {}),
|
...(subjectSpan != null ? { subject: subjectSpan as [number, number] } : {}),
|
||||||
...(selfSpan != null ? { self: selfSpan as [number, number] } : {}),
|
...(attachedSpan != null ? { attached: attachedSpan as [number, number] } : {}),
|
||||||
}
|
}
|
||||||
const { ast, eid, id } = parseWithSpans(code, spans)
|
const { ast, eid, id } = parseWithSpans(code, spans)
|
||||||
const line = ast.lines[0]?.expression
|
const line = ast.lines[0]?.expression
|
||||||
@ -101,10 +103,11 @@ test.each`
|
|||||||
if (typeof visConfig.value.expression === 'string') {
|
if (typeof visConfig.value.expression === 'string') {
|
||||||
expect(visConfig.value.expressionId).toBe(eid('entireFunction'))
|
expect(visConfig.value.expressionId).toBe(eid('entireFunction'))
|
||||||
expect(visConfig.value.expression).toBe(
|
expect(visConfig.value.expression).toBe(
|
||||||
`_ -> ${WIDGETS_ENSO_MODULE}.${GET_WIDGETS_METHOD} ${callSuggestion.definedIn}`,
|
`_ -> ${WIDGETS_ENSO_MODULE}.${GET_WIDGETS_METHOD} ${callSuggestion.memberOf}`,
|
||||||
)
|
)
|
||||||
|
expect(eid('attached')).toBeUndefined()
|
||||||
} else {
|
} else {
|
||||||
expect(visConfig.value.expressionId).toBe(eid('self'))
|
expect(visConfig.value.expressionId).toBe(eid('attached'))
|
||||||
}
|
}
|
||||||
expect(visConfig.value.positionalArgumentsExpressions![0]).toBe(methodName)
|
expect(visConfig.value.positionalArgumentsExpressions![0]).toBe(methodName)
|
||||||
expect(visConfig.value.positionalArgumentsExpressions![1]).toBe("['arg']")
|
expect(visConfig.value.positionalArgumentsExpressions![1]).toBe("['arg']")
|
||||||
|
@ -38,13 +38,8 @@ export function useWidgetFunctionCallInfo(
|
|||||||
useVisualizationData(config: Ref<Opt<NodeVisualizationConfiguration>>): Ref<Result<any> | null>
|
useVisualizationData(config: Ref<Opt<NodeVisualizationConfiguration>>): Ref<Result<any> | null>
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const methodCallInfo = computed(() => {
|
const methodCallInfo = computed(() => getMethodCallInfoRecursively(toValue(input).value, graphDb))
|
||||||
return getMethodCallInfoRecursively(toValue(input).value, graphDb)
|
const interpreted = computed(() => interpretCall(toValue(input).value))
|
||||||
})
|
|
||||||
|
|
||||||
const interpreted = computed(() => {
|
|
||||||
return interpretCall(toValue(input).value, methodCallInfo.value == null)
|
|
||||||
})
|
|
||||||
|
|
||||||
const subjectInfo = computed(() => {
|
const subjectInfo = computed(() => {
|
||||||
const analyzed = interpreted.value
|
const analyzed = interpreted.value
|
||||||
@ -60,22 +55,31 @@ export function useWidgetFunctionCallInfo(
|
|||||||
return funcType != null && subjectInfo.value?.typename !== `${funcType}.type`
|
return funcType != null && subjectInfo.value?.typename !== `${funcType}.type`
|
||||||
})
|
})
|
||||||
|
|
||||||
const selfArgumentExternalId = computed<Opt<ExternalId>>(() => {
|
const widgetQuerySubjectExpressionId = computed<Opt<ExternalId>>(() => {
|
||||||
const analyzed = interpreted.value
|
const analyzed = interpreted.value
|
||||||
if (analyzed.kind === 'infix') {
|
if (analyzed.kind === 'infix') {
|
||||||
return analyzed.lhs?.externalId
|
return analyzed.lhs?.externalId
|
||||||
} else if (methodCallInfo.value?.suggestion.selfType != null) {
|
|
||||||
const knownArguments = methodCallInfo.value?.suggestion?.arguments
|
|
||||||
const hasSelfArgument = knownArguments?.[0]?.name === 'self'
|
|
||||||
const selfArgument =
|
|
||||||
hasSelfArgument && !selfArgumentPreapplied.value ?
|
|
||||||
analyzed.args.find((a) => a.argName === 'self' || a.argName == null)?.argument
|
|
||||||
: getAccessOprSubject(analyzed.func) ?? analyzed.args[0]?.argument
|
|
||||||
|
|
||||||
return selfArgument?.externalId
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
const knownArguments = methodCallInfo.value?.suggestion?.arguments
|
||||||
|
const hasKnownSelfArgument = knownArguments?.[0]?.name === 'self'
|
||||||
|
|
||||||
|
// First we always want to attach the visualization to the `self` argument,
|
||||||
|
// whenever we can find an unambiguous expression for it.
|
||||||
|
if (hasKnownSelfArgument && !selfArgumentPreapplied.value) {
|
||||||
|
return analyzed.args.find((a) => a.argName === 'self' || a.argName == null)?.argument
|
||||||
|
?.externalId
|
||||||
|
}
|
||||||
|
|
||||||
|
// When no `self` argument can be resolved or it is already applied, attach to the access
|
||||||
|
// chain subject. This will correctly handle constructors and most common cases with not
|
||||||
|
// yet resolved methods.
|
||||||
|
const accessSubject = getAccessOprSubject(analyzed.func)
|
||||||
|
if (accessSubject) {
|
||||||
|
return accessSubject.externalId
|
||||||
|
}
|
||||||
|
// In other cases (e.g. autoscoped expression) there is no good existing
|
||||||
|
// expression to attach the visualization to. Fallback to synthetic type-based expression.
|
||||||
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
const visualizationConfig = computed<Opt<NodeVisualizationConfiguration>>(() => {
|
const visualizationConfig = computed<Opt<NodeVisualizationConfiguration>>(() => {
|
||||||
@ -84,7 +88,6 @@ export function useWidgetFunctionCallInfo(
|
|||||||
methodCallInfo.value,
|
methodCallInfo.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
const selfArgId = selfArgumentExternalId.value
|
|
||||||
const info = methodCallInfo.value
|
const info = methodCallInfo.value
|
||||||
if (!info) return null
|
if (!info) return null
|
||||||
const annotatedArgs = info.suggestion.annotations
|
const annotatedArgs = info.suggestion.annotations
|
||||||
@ -95,9 +98,11 @@ export function useWidgetFunctionCallInfo(
|
|||||||
Ast.Vector.build(annotatedArgs, Ast.TextLiteral.new).code(),
|
Ast.Vector.build(annotatedArgs, Ast.TextLiteral.new).code(),
|
||||||
Ast.TextLiteral.new(JSON.stringify(args)).code(),
|
Ast.TextLiteral.new(JSON.stringify(args)).code(),
|
||||||
]
|
]
|
||||||
if (selfArgId != null) {
|
|
||||||
|
const expressionId = widgetQuerySubjectExpressionId.value
|
||||||
|
if (expressionId != null) {
|
||||||
return {
|
return {
|
||||||
expressionId: selfArgId,
|
expressionId,
|
||||||
visualizationModule: WIDGETS_ENSO_MODULE,
|
visualizationModule: WIDGETS_ENSO_MODULE,
|
||||||
expression: {
|
expression: {
|
||||||
module: WIDGETS_ENSO_MODULE,
|
module: WIDGETS_ENSO_MODULE,
|
||||||
@ -107,12 +112,12 @@ export function useWidgetFunctionCallInfo(
|
|||||||
positionalArgumentsExpressions,
|
positionalArgumentsExpressions,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// In the case when no self argument is present (for example in autoscoped constructor),
|
// In the case when no clear subject expression exists (for example in autoscoped constructor),
|
||||||
// we assume that this is a static function call.
|
// we assume that this is a static function call and create the subject by using resolved type name.
|
||||||
return {
|
return {
|
||||||
expressionId: toValue(input).value.externalId,
|
expressionId: toValue(input).value.externalId,
|
||||||
visualizationModule: WIDGETS_ENSO_MODULE,
|
visualizationModule: WIDGETS_ENSO_MODULE,
|
||||||
expression: `_ -> ${WIDGETS_ENSO_MODULE}.${GET_WIDGETS_METHOD} ${info.suggestion.definedIn}`,
|
expression: `_ -> ${WIDGETS_ENSO_MODULE}.${GET_WIDGETS_METHOD} ${info.suggestion.memberOf ?? info.suggestion.definedIn}`,
|
||||||
positionalArgumentsExpressions,
|
positionalArgumentsExpressions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ export interface DropdownEntry {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="DropdownWidget" :style="styleVars">
|
<div class="DropdownWidget" :style="styleVars">
|
||||||
<ul class="list scrollable" @wheel.stop>
|
<ul class="list scrollable" @wheel.stop.passive>
|
||||||
<li
|
<li
|
||||||
v-for="entry in sortedValues"
|
v-for="entry in sortedValues"
|
||||||
:key="entry.value"
|
:key="entry.value"
|
||||||
|
@ -157,8 +157,8 @@ interface FoundApplication {
|
|||||||
argName: string | undefined
|
argName: string | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export function interpretCall(callRoot: Ast.Ast, allowInterpretAsInfix: boolean): InterpretedCall {
|
export function interpretCall(callRoot: Ast.Ast): InterpretedCall {
|
||||||
if (allowInterpretAsInfix && callRoot instanceof Ast.OprApp) {
|
if (callRoot instanceof Ast.OprApp) {
|
||||||
// Infix chains are handled one level at a time. Each application may have at most 2 arguments.
|
// Infix chains are handled one level at a time. Each application may have at most 2 arguments.
|
||||||
return {
|
return {
|
||||||
kind: 'infix',
|
kind: 'infix',
|
||||||
|
@ -8,6 +8,7 @@ import postcssNesting from 'postcss-nesting'
|
|||||||
import tailwindcss from 'tailwindcss'
|
import tailwindcss from 'tailwindcss'
|
||||||
import tailwindcssNesting from 'tailwindcss/nesting'
|
import tailwindcssNesting from 'tailwindcss/nesting'
|
||||||
import { defineConfig, type Plugin } from 'vite'
|
import { defineConfig, type Plugin } from 'vite'
|
||||||
|
import VueDevTools from 'vite-plugin-vue-devtools'
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
import * as tailwindConfig from 'enso-dashboard/tailwind.config'
|
import * as tailwindConfig from 'enso-dashboard/tailwind.config'
|
||||||
import { createGatewayServer } from './ydoc-server'
|
import { createGatewayServer } from './ydoc-server'
|
||||||
@ -27,6 +28,7 @@ export default defineConfig({
|
|||||||
publicDir: fileURLToPath(new URL('./public', import.meta.url)),
|
publicDir: fileURLToPath(new URL('./public', import.meta.url)),
|
||||||
envDir: fileURLToPath(new URL('.', import.meta.url)),
|
envDir: fileURLToPath(new URL('.', import.meta.url)),
|
||||||
plugins: [
|
plugins: [
|
||||||
|
VueDevTools(),
|
||||||
vue(),
|
vue(),
|
||||||
react({
|
react({
|
||||||
include: fileURLToPath(new URL('../ide-desktop/lib/dashboard/**/*.tsx', import.meta.url)),
|
include: fileURLToPath(new URL('../ide-desktop/lib/dashboard/**/*.tsx', import.meta.url)),
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
"sharp": "^0.31.2",
|
"sharp": "^0.31.2",
|
||||||
"to-ico": "^1.1.5",
|
"to-ico": "^1.1.5",
|
||||||
"tsx": "^4.7.1",
|
"tsx": "^4.7.1",
|
||||||
"vite": "^5.1.4"
|
"vite": "^5.3.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/darwin-x64": "^0.17.15",
|
"@esbuild/darwin-x64": "^0.17.15",
|
||||||
|
@ -95,7 +95,7 @@
|
|||||||
"tailwindcss-react-aria-components": "^1.1.1",
|
"tailwindcss-react-aria-components": "^1.1.1",
|
||||||
"ts-plugin-namespace-auto-import": "^1.0.0",
|
"ts-plugin-namespace-auto-import": "^1.0.0",
|
||||||
"typescript": "~5.2.2",
|
"typescript": "~5.2.2",
|
||||||
"vite": "^4.4.9",
|
"vite": "^5.3.1",
|
||||||
"vitest": "^1.3.1"
|
"vitest": "^1.3.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
2549
package-lock.json
generated
2549
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user