Skip to content

Commit 8649995

Browse files
committed
Literal component
1 parent 9a58260 commit 8649995

File tree

5 files changed

+96
-43
lines changed

5 files changed

+96
-43
lines changed

app/gui/src/project-view/components/ComponentBrowser.vue

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { componentBrowserBindings, listBindings } from '@/bindings'
33
import { type Component } from '@/components/ComponentBrowser/component'
44
import ComponentEditor from '@/components/ComponentBrowser/ComponentEditor.vue'
55
import ComponentList from '@/components/ComponentBrowser/ComponentList.vue'
6-
import { Filtering } from '@/components/ComponentBrowser/filtering'
76
import { useComponentBrowserInput, type Usage } from '@/components/ComponentBrowser/input'
87
import GraphVisualization from '@/components/GraphEditor/GraphVisualization.vue'
98
import SvgButton from '@/components/SvgButton.vue'
@@ -15,7 +14,6 @@ import { injectNodeColors } from '@/providers/graphNodeColors'
1514
import { injectInteractionHandler, type Interaction } from '@/providers/interactionHandler'
1615
import { useGraphStore } from '@/stores/graph'
1716
import type { RequiredImport } from '@/stores/graph/imports'
18-
import { useProjectStore } from '@/stores/project'
1917
import { injectProjectNames } from '@/stores/projectNames'
2018
import { useSuggestionDbStore } from '@/stores/suggestionDatabase'
2119
import { type Typename } from '@/stores/suggestionDatabase/entry'
@@ -29,6 +27,8 @@ import { debouncedGetter } from '@/util/reactivity'
2927
import type { ComponentInstance } from 'vue'
3028
import { computed, onMounted, onUnmounted, ref, toValue, watch, watchEffect } from 'vue'
3129
import type { SuggestionId } from 'ydoc-shared/languageServerTypes/suggestions'
30+
import { Range } from 'ydoc-shared/util/data/range'
31+
import { Ok } from 'ydoc-shared/util/data/result'
3232
import type { VisualizationIdentifier } from 'ydoc-shared/yjsModel'
3333
3434
// Difference in position between the component browser and a node for the input of the component browser to
@@ -47,7 +47,6 @@ const EDGE_Y_OFFSET = -8
4747
4848
const cssComponentEditorPadding = `${COMPONENT_EDITOR_PADDING}px`
4949
50-
const projectStore = useProjectStore()
5150
const suggestionDbStore = useSuggestionDbStore()
5251
const graphStore = useGraphStore()
5352
const interaction = injectInteractionHandler()
@@ -177,15 +176,6 @@ const selectedSuggestion = computed(() => {
177176
178177
const input = useComponentBrowserInput()
179178
180-
const currentFiltering = computed(() => {
181-
if (input.mode.mode === 'componentBrowsing') {
182-
const currentModule = projectStore.moduleProjectPath
183-
return new Filtering(input.mode.filter, currentModule?.ok ? currentModule.value : undefined)
184-
} else {
185-
return undefined
186-
}
187-
})
188-
189179
onUnmounted(() => {
190180
graphStore.cbEditedEdge = undefined
191181
})
@@ -285,20 +275,37 @@ watch(
285275
286276
// === Accepting Entry ===
287277
288-
function acceptSuggestion(component: Opt<Component> = null) {
289-
const suggestionId = component?.suggestionId ?? selectedSuggestionId.value
290-
if (suggestionId == null) return acceptInput()
291-
const result = input.applySuggestion(suggestionId)
278+
function applyComponent(component: Opt<Component> = null) {
279+
component ??= selected.value
280+
if (component == null) {
281+
input.switchToCodeEditMode()
282+
return Ok()
283+
}
284+
if (component.suggestionId) {
285+
return input.applySuggestion(component.suggestionId)
286+
} else {
287+
// Component without suggestion database entry, for example "literal" component.
288+
input.content = { text: component.label, selection: Range.emptyAt(component.label.length) }
289+
input.switchToCodeEditMode()
290+
return Ok()
291+
}
292+
}
293+
294+
function acceptComponent(component: Opt<Component> = null) {
295+
const result = applyComponent(component)
292296
if (result.ok) acceptInput()
293297
else result.error.log('Cannot apply suggestion')
294298
}
295299
296-
function applySuggestion(component: Opt<Component> = null) {
297-
const suggestionId = component?.suggestionId ?? selectedSuggestionId.value
298-
if (suggestionId == null) return input.switchToCodeEditMode()
299-
const result = input.applySuggestion(suggestionId)
300-
if (!result.ok) result.error.log('Cannot apply suggestion')
301-
}
300+
// function editComponent(component: Opt<Component> = null) {
301+
// const result = applyComponentToInput(component)
302+
// if (result.ok) acceptInput()
303+
// else result.error.log('Cannot apply suggestion')
304+
// const suggestionId = component?.suggestionId ?? selectedSuggestionId.value
305+
// if (suggestionId == null) return input.switchToCodeEditMode()
306+
// const result = input.applySuggestion(suggestionId)
307+
// if (!result.ok) result.error.log('Cannot apply suggestion')
308+
// }
302309
303310
function acceptInput() {
304311
const appliedReturnType =
@@ -314,11 +321,14 @@ function acceptInput() {
314321
const outsideComponentBrowsing = computed(() => input.mode.mode != 'componentBrowsing')
315322
const actions = registerHandlers({
316323
'componentBrowser.editSuggestion': {
317-
action: applySuggestion,
324+
action: () => {
325+
const result = applyComponent()
326+
if (!result.ok) result.error.log('Cannot apply component')
327+
},
318328
disabled: outsideComponentBrowsing,
319329
},
320330
'componentBrowser.acceptSuggestion': {
321-
action: acceptSuggestion,
331+
action: acceptComponent,
322332
disabled: outsideComponentBrowsing,
323333
},
324334
'componentBrowser.acceptInputAsCode': {
@@ -427,10 +437,11 @@ const listsHandler = listBindings.handler({
427437
/>
428438
</div>
429439
<ComponentList
430-
v-if="input.mode.mode === 'componentBrowsing' && currentFiltering"
440+
v-if="input.mode.mode === 'componentBrowsing'"
431441
ref="componentList"
432-
:filtering="currentFiltering"
433-
@acceptSuggestion="acceptSuggestion($event)"
442+
:filter="input.mode.filter"
443+
:literal="input.mode.literal"
444+
@acceptSuggestion="acceptComponent($event)"
434445
@update:selectedComponent="selected = $event"
435446
/>
436447
</div>

app/gui/src/project-view/components/ComponentBrowser/ComponentList.vue

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
11
<script setup lang="ts">
2-
import { makeComponentList, type Component } from '@/components/ComponentBrowser/component'
2+
import { makeComponentLists, type Component } from '@/components/ComponentBrowser/component'
33
import ComponentEntry from '@/components/ComponentBrowser/ComponentEntry.vue'
4-
import type { Filtering } from '@/components/ComponentBrowser/filtering'
4+
import { Filter, Filtering } from '@/components/ComponentBrowser/filtering'
55
import SvgIcon from '@/components/SvgIcon.vue'
66
import VirtualizedList from '@/components/VirtualizedList.vue'
77
import { groupColorStyle } from '@/composables/nodeColors'
8+
import { useProjectStore } from '@/stores/project'
89
import { useSuggestionDbStore } from '@/stores/suggestionDatabase'
10+
import { Ast } from '@/util/ast'
911
import { tryGetIndex } from '@/util/data/array'
10-
import { computed, ref, toRef, watch } from 'vue'
12+
import * as map from 'lib0/map'
13+
import { computed, ref, watch } from 'vue'
1114
import type { ComponentExposed } from 'vue-component-type-helpers'
1215
1316
const ITEM_SIZE = 24
1417
const SCROLL_TO_SELECTION_MARGIN = ITEM_SIZE / 2
1518
const MOUSE_SELECTION_DEBOUNCE = 200
1619
1720
const props = defineProps<{
18-
filtering: Filtering
21+
filter: Filter
22+
literal?: Ast.TextLiteral | Ast.NumericLiteral | undefined
1923
}>()
2024
const emit = defineEmits<{
2125
acceptSuggestion: [suggestion: Component]
2226
'update:selectedComponent': [selected: Component | null]
2327
}>()
2428
29+
const projectStore = useProjectStore()
2530
const root = ref<HTMLElement>()
2631
const groupsPanel = ref<ComponentExposed<typeof VirtualizedList>>()
2732
const componentsPanel = ref<ComponentExposed<typeof VirtualizedList>>()
@@ -42,18 +47,34 @@ const displayedSelectedComponentIndex = computed({
4247
},
4348
})
4449
45-
watch(toRef(props, 'filtering'), () => (displayedSelectedComponentIndex.value = 0))
50+
const filtering = computed(() => {
51+
const currentModule = projectStore.moduleProjectPath
52+
return new Filtering(props.filter, currentModule?.ok ? currentModule.value : undefined)
53+
})
54+
55+
watch(filtering, () => (displayedSelectedComponentIndex.value = 0))
4656
watch(selectedGroupIndex, () => (selectedComponentIndex.value = 0))
4757
4858
const suggestionDbStore = useSuggestionDbStore()
49-
const components = computed(() => makeComponentList(suggestionDbStore.entries, props.filtering))
59+
const components = computed(() => {
60+
const lists = makeComponentLists(suggestionDbStore.entries, filtering.value)
61+
if (props.literal != null) {
62+
map
63+
.setIfUndefined(lists, 'all', (): Component[] => [])
64+
.unshift({
65+
label: props.literal.code(),
66+
icon: props.literal instanceof Ast.TextLiteral ? 'text_input' : 'input_number',
67+
})
68+
}
69+
return lists
70+
})
5071
const currentGroups = computed(() => {
5172
return Array.from(components.value.entries(), ([id, components]) => ({
5273
id,
5374
...(id === 'all' ? { name: 'all' }
5475
: id === 'suggestions' ? { name: 'suggestions' }
5576
: (suggestionDbStore.groups[id] ?? { name: 'unknown' })),
56-
...(props.filtering.pattern != null ? { displayedNumber: components.length } : {}),
77+
...(filtering.value?.pattern != null ? { displayedNumber: components.length } : {}),
5778
}))
5879
})
5980
const displayedGroupId = computed(() =>

app/gui/src/project-view/components/ComponentBrowser/component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ interface ComponentLabel {
2828

2929
/** A model of component suggestion displayed in the Component Browser. */
3030
export interface Component extends ComponentLabel {
31-
suggestionId: SuggestionId
31+
suggestionId?: SuggestionId
3232
icon: Icon
3333
group?: number | undefined
3434
}
@@ -112,7 +112,7 @@ export function makeComponent({ id, entry, match }: ComponentInfo): Component {
112112
}
113113

114114
/** Create {@link Component} list for each displayed group from filtered suggestions. */
115-
export function makeComponentList(
115+
export function makeComponentLists(
116116
db: SuggestionDb,
117117
filtering: Filtering,
118118
): Map<GroupId, Component[]> {

app/gui/src/project-view/components/ComponentBrowser/input.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
type SuggestionEntry,
1010
type SuggestionId,
1111
} from '@/stores/suggestionDatabase/entry'
12-
import { isIdentifier, type AstId, type Identifier } from '@/util/ast/abstract'
12+
import { Ast } from '@/util/ast'
1313
import { Err, Ok, type Result } from '@/util/data/result'
1414
import { type ProjectPath } from '@/util/projectPath'
1515
import { qnJoin, qnLastSegment } from '@/util/qualifiedName'
@@ -19,7 +19,7 @@ import { Range } from 'ydoc-shared/util/data/range'
1919

2020
/** Information how the component browser is used, needed for proper input initializing. */
2121
export type Usage =
22-
| { type: 'newNode'; sourcePort?: AstId | undefined }
22+
| { type: 'newNode'; sourcePort?: Ast.AstId | undefined }
2323
| { type: 'editNode'; node: NodeId; cursorPos: number }
2424

2525
/**
@@ -32,6 +32,7 @@ export type ComponentBrowserMode =
3232
| {
3333
mode: 'componentBrowsing'
3434
filter: Filter
35+
literal?: Ast.TextLiteral | Ast.NumericLiteral
3536
}
3637
| {
3738
mode: 'codeEditing'
@@ -55,7 +56,7 @@ export function useComponentBrowserInput(
5556
const imports = shallowRef<RequiredImport[]>([])
5657
const processingAIPrompt = ref(false)
5758
const toastError = useToast.error()
58-
const sourceNodeIdentifier = ref<Identifier>()
59+
const sourceNodeIdentifier = ref<Ast.Identifier>()
5960
const switchedToCodeMode = ref<{ appliedSuggestion?: SuggestionEntry }>()
6061

6162
// Text Model to being edited externally (by user).
@@ -110,12 +111,20 @@ export function useComponentBrowserInput(
110111
: {}),
111112
}
112113
} else {
114+
let literal: Ast.MutableTextLiteral | Ast.NumericLiteral | undefined =
115+
Ast.TextLiteral.tryParse(text.value)
116+
if (literal == null) {
117+
literal = Ast.NumericLiteral.tryParse(text.value)
118+
} else {
119+
literal.fixBoundaries()
120+
}
113121
return {
114122
mode: 'componentBrowsing',
115123
filter: {
116124
pattern: text.value,
117125
...(sourceNodeType.value != null ? { selfArg: sourceNodeType.value } : {}),
118126
},
127+
...(literal ? { literal } : {}),
119128
}
120129
}
121130
})
@@ -172,7 +181,7 @@ export function useComponentBrowserInput(
172181
qnJoin(
173182
owner.path ? qnLastSegment(owner.path)
174183
: owner.project ? qnLastSegment(owner.project)
175-
: ('Main' as Identifier),
184+
: ('Main' as Ast.Identifier),
176185
entry.name,
177186
)
178187
: entry.name) + ' ',
@@ -192,7 +201,9 @@ export function useComponentBrowserInput(
192201
const alreadyAdded = finalImports.some((existing) => requiredImportEquals(existing, anImport))
193202
const importedIdent =
194203
anImport.kind == 'Qualified' ?
195-
qnLastSegment(anImport.module.path ?? anImport.module.project ?? ('Main' as Identifier))
204+
qnLastSegment(
205+
anImport.module.path ?? anImport.module.project ?? ('Main' as Ast.Identifier),
206+
)
196207
: anImport.import
197208
const noLongerNeeded = !text.value.includes(importedIdent)
198209
if (!noLongerNeeded && !alreadyAdded) {
@@ -207,7 +218,7 @@ export function useComponentBrowserInput(
207218
case 'newNode':
208219
if (usage.sourcePort) {
209220
const ident = graphDb.getOutputPortIdentifier(usage.sourcePort)
210-
sourceNodeIdentifier.value = ident != null && isIdentifier(ident) ? ident : undefined
221+
sourceNodeIdentifier.value = ident != null && Ast.isIdentifier(ident) ? ident : undefined
211222
} else {
212223
sourceNodeIdentifier.value = undefined
213224
}
@@ -234,7 +245,7 @@ export function useComponentBrowserInput(
234245
const matchedCode = sourceNodeMatch?.[2]
235246
if (
236247
matchedSource != null &&
237-
isIdentifier(matchedSource) &&
248+
Ast.isIdentifier(matchedSource) &&
238249
matchedCode != null &&
239250
graphDb.getIdentDefiningNode(matchedSource)
240251
)

app/ydoc-shared/src/ast/tree.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1893,6 +1893,16 @@ export class MutableTextLiteral extends TextLiteral implements MutableExpression
18931893
this.fields.set('close', unspaced(Token.new(code)))
18941894
}
18951895

1896+
fixBoundaries() {
1897+
const open = this.open
1898+
const close = this.close
1899+
if (open != null && close == null) {
1900+
this.fields.set('close', unspaced(Token.new(open.code())))
1901+
} else if (open == null && close != null) {
1902+
this.fields.set('open', unspaced(Token.new(close.code())))
1903+
}
1904+
}
1905+
18961906
setElements(elements: TextElement<OwnedRefs>[]) {
18971907
this.fields.set(
18981908
'elements',

0 commit comments

Comments
 (0)