-
Notifications
You must be signed in to change notification settings - Fork 326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Literals and operators in Component Browser #12420
base: develop
Are you sure you want to change the base?
Changes from 2 commits
8649995
ad03676
5f90d2e
044c695
dc33a09
f3242fe
dc91f64
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,32 @@ | ||
<script setup lang="ts"> | ||
import { makeComponentList, type Component } from '@/components/ComponentBrowser/component' | ||
import { makeComponentLists, type Component } from '@/components/ComponentBrowser/component' | ||
import ComponentEntry from '@/components/ComponentBrowser/ComponentEntry.vue' | ||
import type { Filtering } from '@/components/ComponentBrowser/filtering' | ||
import { Filter, Filtering } from '@/components/ComponentBrowser/filtering' | ||
import SvgIcon from '@/components/SvgIcon.vue' | ||
import VirtualizedList from '@/components/VirtualizedList.vue' | ||
import { groupColorStyle } from '@/composables/nodeColors' | ||
import { useProjectStore } from '@/stores/project' | ||
import { useSuggestionDbStore } from '@/stores/suggestionDatabase' | ||
import { Ast } from '@/util/ast' | ||
import { tryGetIndex } from '@/util/data/array' | ||
import { computed, ref, toRef, watch } from 'vue' | ||
import * as map from 'lib0/map' | ||
import { computed, ref, watch } from 'vue' | ||
import type { ComponentExposed } from 'vue-component-type-helpers' | ||
|
||
const ITEM_SIZE = 24 | ||
const SCROLL_TO_SELECTION_MARGIN = ITEM_SIZE / 2 | ||
const MOUSE_SELECTION_DEBOUNCE = 200 | ||
|
||
const props = defineProps<{ | ||
filtering: Filtering | ||
filter: Filter | ||
literal?: Ast.TextLiteral | Ast.NumericLiteral | undefined | ||
}>() | ||
const emit = defineEmits<{ | ||
acceptSuggestion: [suggestion: Component] | ||
'update:selectedComponent': [selected: Component | null] | ||
}>() | ||
|
||
const projectStore = useProjectStore() | ||
const root = ref<HTMLElement>() | ||
const groupsPanel = ref<ComponentExposed<typeof VirtualizedList>>() | ||
const componentsPanel = ref<ComponentExposed<typeof VirtualizedList>>() | ||
|
@@ -42,18 +47,34 @@ const displayedSelectedComponentIndex = computed({ | |
}, | ||
}) | ||
|
||
watch(toRef(props, 'filtering'), () => (displayedSelectedComponentIndex.value = 0)) | ||
const filtering = computed(() => { | ||
const currentModule = projectStore.moduleProjectPath | ||
return new Filtering(props.filter, currentModule?.ok ? currentModule.value : undefined) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be a little simpler with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. currentModule may be |
||
}) | ||
|
||
watch(filtering, () => (displayedSelectedComponentIndex.value = 0)) | ||
watch(selectedGroupIndex, () => (selectedComponentIndex.value = 0)) | ||
|
||
const suggestionDbStore = useSuggestionDbStore() | ||
const components = computed(() => makeComponentList(suggestionDbStore.entries, props.filtering)) | ||
const components = computed(() => { | ||
const lists = makeComponentLists(suggestionDbStore.entries, filtering.value) | ||
if (props.literal != null) { | ||
map | ||
.setIfUndefined(lists, 'all', (): Component[] => []) | ||
.unshift({ | ||
label: props.literal.code(), | ||
icon: props.literal instanceof Ast.TextLiteral ? 'text_input' : 'input_number', | ||
}) | ||
} | ||
return lists | ||
}) | ||
const currentGroups = computed(() => { | ||
return Array.from(components.value.entries(), ([id, components]) => ({ | ||
id, | ||
...(id === 'all' ? { name: 'all' } | ||
: id === 'suggestions' ? { name: 'suggestions' } | ||
: (suggestionDbStore.groups[id] ?? { name: 'unknown' })), | ||
...(props.filtering.pattern != null ? { displayedNumber: components.length } : {}), | ||
...(filtering.value?.pattern != null ? { displayedNumber: components.length } : {}), | ||
})) | ||
}) | ||
const displayedGroupId = computed(() => | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,7 @@ import { | |
type SuggestionEntry, | ||
type SuggestionId, | ||
} from '@/stores/suggestionDatabase/entry' | ||
import { isIdentifier, type AstId, type Identifier } from '@/util/ast/abstract' | ||
import { Ast } from '@/util/ast' | ||
import { Err, Ok, type Result } from '@/util/data/result' | ||
import { type ProjectPath } from '@/util/projectPath' | ||
import { qnJoin, qnLastSegment } from '@/util/qualifiedName' | ||
|
@@ -19,7 +19,7 @@ import { Range } from 'ydoc-shared/util/data/range' | |
|
||
/** Information how the component browser is used, needed for proper input initializing. */ | ||
export type Usage = | ||
| { type: 'newNode'; sourcePort?: AstId | undefined } | ||
| { type: 'newNode'; sourcePort?: Ast.AstId | undefined } | ||
| { type: 'editNode'; node: NodeId; cursorPos: number } | ||
|
||
/** | ||
|
@@ -32,6 +32,7 @@ export type ComponentBrowserMode = | |
| { | ||
mode: 'componentBrowsing' | ||
filter: Filter | ||
literal?: Ast.TextLiteral | Ast.NumericLiteral | ||
} | ||
| { | ||
mode: 'codeEditing' | ||
|
@@ -55,7 +56,7 @@ export function useComponentBrowserInput( | |
const imports = shallowRef<RequiredImport[]>([]) | ||
const processingAIPrompt = ref(false) | ||
const toastError = useToast.error() | ||
const sourceNodeIdentifier = ref<Identifier>() | ||
const sourceNodeIdentifier = ref<Ast.Identifier>() | ||
const switchedToCodeMode = ref<{ appliedSuggestion?: SuggestionEntry }>() | ||
|
||
// Text Model to being edited externally (by user). | ||
|
@@ -110,12 +111,20 @@ export function useComponentBrowserInput( | |
: {}), | ||
} | ||
} else { | ||
let literal: Ast.MutableTextLiteral | Ast.NumericLiteral | undefined = | ||
Ast.TextLiteral.tryParse(text.value) | ||
if (literal == null) { | ||
literal = Ast.NumericLiteral.tryParse(text.value) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The unit tests were once completely removed for |
||
} else { | ||
literal.fixBoundaries() | ||
} | ||
return { | ||
mode: 'componentBrowsing', | ||
filter: { | ||
pattern: text.value, | ||
...(sourceNodeType.value != null ? { selfArg: sourceNodeType.value } : {}), | ||
}, | ||
...(literal ? { literal } : {}), | ||
kazcw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
}) | ||
|
@@ -172,7 +181,7 @@ export function useComponentBrowserInput( | |
qnJoin( | ||
owner.path ? qnLastSegment(owner.path) | ||
: owner.project ? qnLastSegment(owner.project) | ||
: ('Main' as Identifier), | ||
: ('Main' as Ast.Identifier), | ||
entry.name, | ||
) | ||
: entry.name) + ' ', | ||
|
@@ -192,7 +201,9 @@ export function useComponentBrowserInput( | |
const alreadyAdded = finalImports.some((existing) => requiredImportEquals(existing, anImport)) | ||
const importedIdent = | ||
anImport.kind == 'Qualified' ? | ||
qnLastSegment(anImport.module.path ?? anImport.module.project ?? ('Main' as Identifier)) | ||
qnLastSegment( | ||
anImport.module.path ?? anImport.module.project ?? ('Main' as Ast.Identifier), | ||
) | ||
: anImport.import | ||
const noLongerNeeded = !text.value.includes(importedIdent) | ||
if (!noLongerNeeded && !alreadyAdded) { | ||
|
@@ -207,7 +218,7 @@ export function useComponentBrowserInput( | |
case 'newNode': | ||
if (usage.sourcePort) { | ||
const ident = graphDb.getOutputPortIdentifier(usage.sourcePort) | ||
sourceNodeIdentifier.value = ident != null && isIdentifier(ident) ? ident : undefined | ||
sourceNodeIdentifier.value = ident != null && Ast.isIdentifier(ident) ? ident : undefined | ||
} else { | ||
sourceNodeIdentifier.value = undefined | ||
} | ||
|
@@ -234,7 +245,7 @@ export function useComponentBrowserInput( | |
const matchedCode = sourceNodeMatch?.[2] | ||
if ( | ||
matchedSource != null && | ||
isIdentifier(matchedSource) && | ||
Ast.isIdentifier(matchedSource) && | ||
matchedCode != null && | ||
graphDb.getIdentDefiningNode(matchedSource) | ||
) | ||
|
@@ -271,7 +282,13 @@ export function useComponentBrowserInput( | |
} | ||
|
||
function applySourceNode(text: string) { | ||
return sourceNodeIdentifier.value ? `${sourceNodeIdentifier.value}.${text}` : text | ||
return ( | ||
sourceNodeIdentifier.value ? | ||
/^[a-zA-Z]/.test(text) ? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically an identifier can start with a character other than ASCII letters; we could handle that by exposing to WASM an entry point to the lexer so that we can see if the first token is a (lexical) identifier. I suppose this is ok for an initial implementation but let's add a TODO. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, implementation in rust should be quite easy; I just assumed this is too simple case for it. But here you pointed me that it's not that simple case. |
||
`${sourceNodeIdentifier.value}.${text}` | ||
: `${sourceNodeIdentifier.value} ${text}` | ||
: text | ||
) | ||
} | ||
|
||
return proxyRefs({ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1893,6 +1893,16 @@ export class MutableTextLiteral extends TextLiteral implements MutableExpression | |
this.fields.set('close', unspaced(Token.new(code))) | ||
} | ||
|
||
fixBoundaries() { | ||
const open = this.open | ||
const close = this.close | ||
if (open != null && close == null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the literal is multiline, This function could use some unit tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I guess There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
this.fields.set('close', unspaced(Token.new(open.code()))) | ||
} else if (open == null && close != null) { | ||
this.fields.set('open', unspaced(Token.new(close.code()))) | ||
} | ||
} | ||
|
||
setElements(elements: TextElement<OwnedRefs>[]) { | ||
this.fields.set( | ||
'elements', | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestionId
is anumber
, and AFAIK the protocol allows it to be0
.