Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
268 changes: 140 additions & 128 deletions app/gui/src/project-view/assets/icons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions app/gui/src/project-view/components/ActionButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ const { action: actionName, label } = defineProps<{
}>()
const action = computed(() => resolveAction(actionName))

const descriptionWithShortcut = computed(() =>
action.value.shortcut ?
`${toValue(action.value.description)} (${toValue(action.value.shortcut?.humanReadable)})`
: toValue(action.value.description),
)
const descriptionWithShortcut = computed(() => {
const description = toValue(action.value.description)
const shortcut = toValue(action.value.shortcut)
return shortcut ? `${description} (${shortcut.humanReadable})` : description
})
</script>

<template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,12 @@ class EnsoParser extends Parser {
const code = input.read(0, input.length)
if (code !== this.cachedCode || this.cachedTree == null) {
this.cachedCode = code
assertDefined(this.moduleRoot.value)
const root = Ast.copyIntoNewModule(this.moduleRoot.value)
const moduleRootAst = this.moduleRoot.value
if (moduleRootAst == null) {
console.log('cachedCode', this.cachedCode)
return Tree.empty
}
const root = Ast.copyIntoNewModule(moduleRootAst)
const tempModule = root.module
root.module.setRoot(root)
root.syncToCode(code)
Expand Down
15 changes: 3 additions & 12 deletions app/gui/src/project-view/components/ComponentMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import ActionMenu from '@/components/ActionMenu.vue'
import ColorRing from '@/components/ColorRing.vue'
import DropdownMenu from '@/components/DropdownMenu.vue'
import SvgIcon from '@/components/SvgIcon.vue'
import type { DisplayableActionName } from '@/providers/action'
import { ref } from 'vue'

const _props = defineProps<{
colorPickerOpened: boolean
currentNodeColor: string | undefined
matchableColors: ReadonlySet<string>
actions: DisplayableActionName[]
}>()
const emit = defineEmits<{
closeColorPicker: []
Expand Down Expand Up @@ -42,18 +44,7 @@ const isDropdownOpened = ref(false)
<template #menu>
<ActionMenu
data-testid="component-menu-more-entries"
:actions="[
'component.toggleDocPanel',
'component.toggleVisualization',
'component.createNewNode',
'component.editingComment',
'component.recompute',
'component.pickColor',
'component.enterNode',
'component.startEditing',
'components.copy',
'components.deleteSelected',
]"
:actions="actions"
@close="isDropdownOpened = false"
/>
</template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import { computed, useTemplateRef } from 'vue'
import { newArgumentDefinition } from 'ydoc-shared/ast'
import FormContainer from './FormContainer.vue'
import FormRow from './FormRow.vue'
import { renameArgumentInDefaultValue } from './GraphEditor/widgets/WidgetFunctionDef/argumentAst'
import {
renameArgumentInDefaultValue,
replaceVariableUsages,
} from './GraphEditor/widgets/WidgetFunctionDef/argumentAst'
import ArgumentRow from './GraphEditor/widgets/WidgetFunctionDef/ArgumentRow.vue'
import { FunctionName } from './GraphEditor/widgets/WidgetFunctionName.vue'
import { DisplayIcon } from './GraphEditor/widgets/WidgetIcon.vue'
Expand Down Expand Up @@ -117,10 +120,7 @@ function handleRename(index: number, newName: Ast.Owned<Ast.MutableExpression>)
const newNameString = newName.code()
if (newNameString == oldNameString) return
renameArgumentInDefaultValue(definition, edit, newNameString)
Ast.visitRecursive(ast, (child) => {
if (child instanceof Ast.Ident && child.token.code() === oldNameString)
edit.replaceValue(child.id, newName)
})
replaceVariableUsages(edit, ast, newNameString, newName)
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const model = defineModel<string>({ default: '' })
const emit = defineEmits<{
textEdited: [text: string]
userAction: [text: string, selection: SelectionRange]
blur: []
}>()

const editorRoot = useTemplateRef<ComponentInstance<typeof CodeMirrorRoot>>('editorRoot')
Expand Down Expand Up @@ -88,6 +89,7 @@ const editing = WidgetEditHandler.New(props, {

function blurEditor() {
editorView.contentDOM.blur()
emit('blur')
}

function focusAndSelect() {
Expand Down
35 changes: 19 additions & 16 deletions app/gui/src/project-view/components/GraphEditor/GraphNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ import { useComponentColors } from '@/composables/componentColors'
import { useClickableDraggable } from '@/composables/dragging'
import { useResizeObserver } from '@/composables/events'
import { useProgressBackground } from '@/composables/progressBar'
import type { ActionHandler } from '@/providers/action'
import type { ActionHandler, DisplayableActionName } from '@/providers/action'
import { registerHandlers, toggledAction } from '@/providers/action'
import { injectGraphNavigator } from '@/providers/graphNavigator'
import { injectNodeColors } from '@/providers/graphNodeColors'
import { injectGraphSelection } from '@/providers/graphSelection'
import { providePopoverRoot } from '@/providers/popoverRoot'
import { provideResizableWidgetRegistry } from '@/providers/resizableWidgetRegistry'
import { provideWidgetControlledActions } from '@/providers/widgetActions'
import { Ast } from '@/util/ast'
import { prefixes } from '@/util/ast/node'
import { onWindowBlur } from '@/util/autoBlur'
Expand Down Expand Up @@ -389,9 +390,24 @@ const actionHandlers = registerHandlers(
enabled: computed(() => !isBeingRecomputed.value),
action: recomputeOnce,
},
...provideWidgetControlledActions(['component.widget.editMethodName']),
}),
)

const nodeMenuActions: DisplayableActionName[] = [
'component.toggleDocPanel',
'component.toggleVisualization',
'component.createNewNode',
'component.editingComment',
'component.recompute',
'component.pickColor',
'component.enterNode',
'component.widget.editMethodName',
'component.startEditing',
'components.copy',
'components.deleteSelected',
]

onWindowBlur(() => {
graph.nodeHovered.delete(nodeId.value)
updateNodeHover(undefined)
Expand Down Expand Up @@ -423,6 +439,7 @@ const nodeName = computed(() => props.node.pattern?.code())
:colorPickerOpened="colorPickerOpened"
:currentNodeColor="nodeColor"
:matchableColors="matchableColors"
:actions="nodeMenuActions"
@setNodeColor="emit('setNodeColor', $event)"
@closeColorPicker="colorPickerOpened = false"
@update:hovered="menuHovered = $event"
Expand All @@ -440,21 +457,7 @@ const nodeName = computed(() => props.node.pattern?.code())
class="beforeNode"
@click.capture="setSoleSelected"
/>
<ContextMenuTrigger
:actions="[
'component.toggleDocPanel',
'component.toggleVisualization',
'component.createNewNode',
'component.editingComment',
'component.recompute',
'component.pickColor',
'component.enterNode',
'component.startEditing',
'components.copy',
'components.deleteSelected',
]"
@contextmenu="ensureSelected"
>
<ContextMenuTrigger :actions="nodeMenuActions" @contextmenu="ensureSelected">
<div
ref="contentNode"
:class="{ content: true, dragged: isDragged }"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Ast } from '@/util/ast'
import { ArgumentApplication, ArgumentApplicationKey } from '@/util/callTree'
import { computed } from 'vue'
import { mapOrUndefined } from 'ydoc-shared/util/data/opt'
import { FunctionName } from './WidgetFunctionName.vue'

const props = defineProps(widgetProps(widgetDefinition))
const tree = injectWidgetTree()
Expand All @@ -22,11 +23,19 @@ const targetMaybePort = computed(() => {
if (target instanceof Ast.Ast) {
const input = WidgetInput.FromAst(target)
input.forcePort = true
if (!application.value.calledFunction) return input
const ptr = entryMethodPointer(application.value.calledFunction)
if (!ptr) return input
const definition = module.value.getMethodAst(ptr)
if (!definition.ok) return input
if (input.value instanceof Ast.PropertyAccess || input.value instanceof Ast.Ident) {
const methodPointer = entryMethodPointer(application.value.calledFunction)
if (!methodPointer) return input
const definition = module.value.getMethodAst(methodPointer)
if (definition.ok) {
input[FunctionName] = {
editableNameExpression: definition.value.name.externalId,
methodPointer,
requireUserAction: true,
}
}
}

return input
} else {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { isIdentifier } from '@/util/qualifiedName'
import { proxyRefs } from '@/util/reactivity'
import { computed } from 'vue'
import { Err, Ok } from 'ydoc-shared/util/data/result'
import { FunctionName } from './WidgetFunctionName.vue'

const props = defineProps(widgetProps(widgetDefinition))
const { projectNames: projectNames, module, graph } = useCurrentProject()
Expand Down Expand Up @@ -69,7 +70,24 @@ const innerInput = computed(() => {
input = { ...props.input }
}
const callInfo = methodCallInfo.value
if (callInfo) input[CallInfo] = callInfo
if (callInfo) {
input[CallInfo] = callInfo
console.log(input)
if (
!(ArgumentApplicationKey in input) &&
(input.value instanceof Ast.PropertyAccess || input.value instanceof Ast.Ident)
) {
const methodPointer = callInfo.methodCall.methodPointer
const definition = module.value.getMethodAst(methodPointer)
if (definition.ok) {
input[FunctionName] = {
editableNameExpression: definition.value.name.externalId,
methodPointer,
requireUserAction: true,
}
}
}
}
return input
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Ast } from '@/util/ast'
import { Pattern } from '@/util/ast/match'
import { computed } from 'vue'
import type { Identifier, MutableModule } from 'ydoc-shared/ast'
import { assertNever } from 'ydoc-shared/util/assert'

const missingArgPattern = computed(() => Pattern.parseExpression('Missing_Argument.throw __'))
Expand Down Expand Up @@ -61,3 +62,55 @@ export function renameArgumentInDefaultValue(
}
}
}

/** Replace all variable position tokens with given name with a new token. */
export function replaceVariableUsages(
edit: MutableModule,
ast: Ast.Ast,
oldNameString: string,
newName: Ast.Owned<Ast.MutableExpression>,
shouldRenameProps?: (access: Ast.Expression) => boolean,
) {
const newNameString = newName.code()
console.log(newNameString, oldNameString)
if (newNameString == oldNameString) return

function matchIdent(node: Ast.Ast): node is Ast.Ident {
return node instanceof Ast.Ident && node.token.code() === oldNameString
}

Ast.visitRecursive(ast, (child) => {
if (matchIdent(child)) edit.replaceValue(child.id, newName)
else if (child instanceof Ast.PropertyAccess) {
// Check if this property name qualifies for replacement.
if (shouldRenameProps && child.lhs && matchIdent(child.rhs) && shouldRenameProps(child.lhs)) {
edit.replaceValue(child.rhs.id, newName)
}
// Attempt replacing tokens on the very left side of property accesses
return [child.lhs]
} else if (child instanceof Ast.App) {
// Do not replace named argument names
return [child.function, child.argument]
}
})
}

/**
* Generate an unique variable name that will not introduce collisions withing given scope.
*
* TODO: This should use proper scope analysis. For now, let's just look for all declared bindings within passed subtree.
*/
export function generateUniqueName(baseName: Identifier, scope: Ast.Ast | undefined): Identifier {
if (!scope) return baseName
const existingNames = new Set()
Ast.visitRecursive(scope, (child) => {
if (child instanceof Ast.Ident) existingNames.add(child.token.code())
else if (child instanceof Ast.PropertyAccess) return [child.lhs]
else if (child instanceof Ast.App) return [child.function, child.argument]
})

let name = baseName
let index = 0
while (existingNames.has(name)) name = `${baseName}_${++index}` as Identifier
return name
}
Loading
Loading