Skip to content

Commit

Permalink
Abstract project names (#12106)
Browse files Browse the repository at this point in the history
Use abstract project references in retained state.

Fixes #11815.
  • Loading branch information
kazcw authored Jan 23, 2025
1 parent c6d00bb commit 00bde82
Show file tree
Hide file tree
Showing 54 changed files with 1,525 additions and 939 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
- [Redo stack is no longer lost when interacting with text literals][11908].
- [Fixed bug when clicking header in Table Editor Widget didn't start editing
it][12064]
- [Fixed bugs occurring after renaming project from within graph editor][12106].

[11889]: https://github.com/enso-org/enso/pull/11889
[11836]: https://github.com/enso-org/enso/pull/11836
[12051]: https://github.com/enso-org/enso/pull/12051
[11902]: https://github.com/enso-org/enso/pull/11902
[11908]: https://github.com/enso-org/enso/pull/11908
[12064]: https://github.com/enso-org/enso/pull/12064
[12106]: https://github.com/enso-org/enso/pull/12106

#### Enso Standard Library

Expand Down
12 changes: 9 additions & 3 deletions app/gui/src/project-view/ProjectView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import GraphEditor from '@/components/GraphEditor.vue'
import { provideBackend } from '@/providers/backend'
import { provideEventLogger } from '@/providers/eventLogging'
import { provideVisibility } from '@/providers/visibility'
import { LsUrls, provideProjectStore } from '@/stores/project'
import { type LsUrls, provideProjectStore } from '@/stores/project'
import { provideProjectNames } from '@/stores/projectNames'
import { provideSettings } from '@/stores/settings'
import { Opt } from '@/util/data/opt'
import { type Opt } from '@/util/data/opt'
import { useEventListener } from '@vueuse/core'
import { markRaw, onActivated, onDeactivated, ref, toRaw, toRef, watch } from 'vue'
Expand Down Expand Up @@ -49,7 +50,12 @@ watch(
useEventListener(window, 'beforeunload', () => logger.send('ide_project_closed'))
provideProjectStore(props)
const projectNames = provideProjectNames(
toRef(props, 'projectNamespace'),
props.projectName,
toRef(props, 'projectDisplayedName'),
)
provideProjectStore(props, projectNames)
provideSettings()
const visible = ref(false)
Expand Down
16 changes: 9 additions & 7 deletions app/gui/src/project-view/components/ComponentBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { injectInteractionHandler, type Interaction } from '@/providers/interact
import { useGraphStore } from '@/stores/graph'
import type { RequiredImport } from '@/stores/graph/imports'
import { useProjectStore } from '@/stores/project'
import { injectProjectNames } from '@/stores/projectNames'
import { useSuggestionDbStore } from '@/stores/suggestionDatabase'
import { type Typename } from '@/stores/suggestionDatabase/entry'
import type { VisualizationDataSource } from '@/stores/visualization'
Expand Down Expand Up @@ -50,6 +51,7 @@ const projectStore = useProjectStore()
const suggestionDbStore = useSuggestionDbStore()
const graphStore = useGraphStore()
const interaction = injectInteractionHandler()
const projectNames = injectProjectNames()
const props = defineProps<{
nodePosition: Vec2
Expand Down Expand Up @@ -177,7 +179,7 @@ const input = useComponentBrowserInput()
const currentFiltering = computed(() => {
if (input.mode.mode === 'componentBrowsing') {
const currentModule = projectStore.modulePath
const currentModule = projectStore.moduleProjectPath
return new Filtering(input.mode.filter, currentModule?.ok ? currentModule.value : undefined)
} else {
return undefined
Expand Down Expand Up @@ -260,11 +262,11 @@ const previewedCode = debouncedGetter<string>(() => input.code, 200)
const previewedSuggestionReturnType = computed(() => {
const id = input.mode.mode === 'codeEditing' ? input.mode.appliedSuggestion : undefined
const appliedEntry = id != null ? suggestionDbStore.entries.get(id) : undefined
if (appliedEntry != null) return appliedEntry.returnType
else if (props.usage.type === 'editNode') {
return graphStore.db.getNodeMainSuggestion(props.usage.node)?.returnType
}
return undefined
const entry =
appliedEntry ? appliedEntry
: props.usage.type === 'editNode' ? graphStore.db.getNodeMainSuggestion(props.usage.node)
: undefined
return entry?.returnType(projectNames)
})
const previewDataSource = computed<VisualizationDataSource | undefined>(() => {
Expand Down Expand Up @@ -316,7 +318,7 @@ function applySuggestion(component: Opt<Component> = null) {
function acceptInput() {
const appliedReturnType =
input.mode.mode === 'codeEditing' && input.mode.appliedSuggestion != null ?
suggestionDbStore.entries.get(input.mode.appliedSuggestion)?.returnType
suggestionDbStore.entries.get(input.mode.appliedSuggestion)?.returnType(projectNames)
: undefined
emit('accepted', input.code.trim(), input.importsToAdd(), appliedReturnType)
interaction.ended(cbOpen)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ test.each([
[makeStaticMethod('Standard.Base.Data.Vector.new'), 'Vector.new'],
[makeMethod('Standard.Base.Data.Vector.get'), 'get'],
[makeConstructor('Standard.Table.Join_Kind.Join_Kind.Inner'), 'Join_Kind.Inner'],
[makeModuleMethod('local.Project.main'), 'Project.main'],
[makeModuleMethod('local.Mock_Project.main'), 'Main.main'],
])("$name Component's label is valid", (suggestion, expected) => {
expect(labelOfEntry(suggestion, { score: 0 }).label).toBe(expected)
})
Expand All @@ -31,42 +31,42 @@ test('Suggestions are ordered properly', () => {
const sortedEntries: MatchedSuggestion[] = [
{
id: 100,
entry: makeModuleMethod('local.Project.Z.best_score'),
entry: makeModuleMethod('local.Mock_Project.Z.best_score'),
match: { score: 0 },
},
{
id: 90,
entry: { ...makeModuleMethod('local.Project.Z.b'), groupIndex: 0 },
entry: { ...makeModuleMethod('local.Mock_Project.Z.b'), groupIndex: 0 },
match: { score: 50 },
},
{
id: 91,
entry: { ...makeModuleMethod('local.Project.Z.a'), groupIndex: 0 },
entry: { ...makeModuleMethod('local.Mock_Project.Z.a'), groupIndex: 0 },
match: { score: 50 },
},
{
id: 89,
entry: { ...makeModuleMethod('local.Project.A.foo'), groupIndex: 1 },
entry: { ...makeModuleMethod('local.Mock_Project.A.foo'), groupIndex: 1 },
match: { score: 50 },
},
{
id: 88,
entry: { ...makeModuleMethod('local.Project.B.another_module'), groupIndex: 1 },
entry: { ...makeModuleMethod('local.Mock_Project.B.another_module'), groupIndex: 1 },
match: { score: 50 },
},
{
id: 87,
entry: { ...makeModule('local.Project.A'), groupIndex: 1 },
entry: { ...makeModule('local.Mock_Project.A'), groupIndex: 1 },
match: { score: 50 },
},
{
id: 50,
entry: makeModuleMethod('local.Project.Z.module_content'),
entry: makeModuleMethod('local.Mock_Project.Z.module_content'),
match: { score: 50 },
},
{
id: 49,
entry: makeModule('local.Project.Z.Module'),
entry: makeModule('local.Mock_Project.Z.Module'),
match: { score: 50 },
},
]
Expand All @@ -82,16 +82,16 @@ test('Suggestions are ordered properly', () => {

test.each`
name | aliases | highlighted
${'foo_bar'} | ${[]} | ${'Project.<foo><_bar>'}
${'foo_xyz_barabc'} | ${[]} | ${'Project.<foo>_xyz<_bar>abc'}
${'fooabc_barabc'} | ${[]} | ${'Project.<foo>abc<_bar>abc'}
${'bar'} | ${['foo_bar', 'foo']} | ${'Project.bar (<foo><_bar>)'}
${'bar'} | ${['foo', 'foo_xyz_barabc']} | ${'Project.bar (<foo>_xyz<_bar>abc)'}
${'bar'} | ${['foo', 'fooabc_barabc']} | ${'Project.bar (<foo>abc<_bar>abc)'}
${'xyz_foo_abc_bar_xyz'} | ${[]} | ${'Project.xyz_<foo>_abc<_bar>_xyz'}
${'xyz_fooabc_abc_barabc_xyz'} | ${[]} | ${'Project.xyz_<foo>abc_abc<_bar>abc_xyz'}
${'bar'} | ${['xyz_foo_abc_bar_xyz']} | ${'Project.bar (xyz_<foo>_abc<_bar>_xyz)'}
${'bar'} | ${['xyz_fooabc_abc_barabc_xyz']} | ${'Project.bar (xyz_<foo>abc_abc<_bar>abc_xyz)'}
${'foo_bar'} | ${[]} | ${'Main.<foo><_bar>'}
${'foo_xyz_barabc'} | ${[]} | ${'Main.<foo>_xyz<_bar>abc'}
${'fooabc_barabc'} | ${[]} | ${'Main.<foo>abc<_bar>abc'}
${'bar'} | ${['foo_bar', 'foo']} | ${'Main.bar (<foo><_bar>)'}
${'bar'} | ${['foo', 'foo_xyz_barabc']} | ${'Main.bar (<foo>_xyz<_bar>abc)'}
${'bar'} | ${['foo', 'fooabc_barabc']} | ${'Main.bar (<foo>abc<_bar>abc)'}
${'xyz_foo_abc_bar_xyz'} | ${[]} | ${'Main.xyz_<foo>_abc<_bar>_xyz'}
${'xyz_fooabc_abc_barabc_xyz'} | ${[]} | ${'Main.xyz_<foo>abc_abc<_bar>abc_xyz'}
${'bar'} | ${['xyz_foo_abc_bar_xyz']} | ${'Main.bar (xyz_<foo>_abc<_bar>_xyz)'}
${'bar'} | ${['xyz_fooabc_abc_barabc_xyz']} | ${'Main.bar (xyz_<foo>abc_abc<_bar>abc_xyz)'}
`('Matched ranges of $highlighted are correct', ({ name, aliases, highlighted }) => {
function replaceMatches(component: Component) {
if (!component.matchedRanges) return component.label
Expand All @@ -104,7 +104,7 @@ test.each`
}

const pattern = 'foo_bar'
const entry = makeModuleMethod(`local.Project.${name}`, { aliases: aliases ?? [] })
const entry = makeModuleMethod(`local.Mock_Project.${name}`, { aliases: aliases ?? [] })
const filtering = new Filtering({ pattern })
const match = filtering.filter(entry, [])
expect(match).not.toBeNull()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Filtering, type MatchResult } from '@/components/ComponentBrowser/filtering'
import { SuggestionEntry } from '@/stores/suggestionDatabase/entry'
import { type SuggestionEntry } from '@/stores/suggestionDatabase/entry'
import {
makeConstructor,
makeFunction,
Expand All @@ -9,9 +9,12 @@ import {
makeModuleMethod,
makeStaticMethod,
} from '@/stores/suggestionDatabase/mockSuggestion'
import { qnLastSegment, QualifiedName } from '@/util/qualifiedName'
import { assert } from '@/util/assert'
import { parseAbsoluteProjectPath } from '@/util/projectPath'
import { qnLastSegment, tryQualifiedName } from '@/util/qualifiedName'
import { expect, test } from 'vitest'
import { Opt } from 'ydoc-shared/util/data/opt'
import { type Opt } from 'ydoc-shared/util/data/opt'
import { unwrap } from 'ydoc-shared/util/data/result'

test.each([
makeModuleMethod('Standard.Base.Data.read', { group: 'Standard.Base.MockGroup1' }),
Expand All @@ -37,11 +40,19 @@ test.each([
expect(filtering.filter(entry, [])).toBeNull()
})

function stdPath(path: string) {
assert(path.startsWith('Standard.'))
return parseAbsoluteProjectPath(unwrap(tryQualifiedName(path)))
}

test('An Instance method is shown when self arg matches', () => {
const entry1 = makeMethod('Standard.Base.Data.Vector.Vector.get')
const entry2 = makeMethod('Standard.Base.Data.Table.get')
const filteringWithSelfType = new Filtering({
selfArg: { type: 'known', typename: 'Standard.Base.Data.Vector.Vector' },
selfArg: {
type: 'known',
typename: stdPath('Standard.Base.Data.Vector.Vector'),
},
})
expect(filteringWithSelfType.filter(entry1, [])).not.toBeNull()
expect(filteringWithSelfType.filter(entry2, [])).toBeNull()
Expand All @@ -59,7 +70,10 @@ test('`Any` type methods taken into account when filtering', () => {
const entry1 = makeMethod('Standard.Base.Data.Vector.Vector.get')
const entry2 = makeMethod('Standard.Base.Any.Any.to_string')
const filtering = new Filtering({
selfArg: { type: 'known', typename: 'Standard.Base.Data.Vector.Vector' },
selfArg: {
type: 'known',
typename: stdPath('Standard.Base.Data.Vector.Vector'),
},
})
expect(filtering.filter(entry1, [])).not.toBeNull()
expect(filtering.filter(entry2, [])).not.toBeNull()
Expand All @@ -72,9 +86,9 @@ test('`Any` type methods taken into account when filtering', () => {
test('Additional self types are taken into account when filtering', () => {
const entry1 = makeMethod('Standard.Base.Data.Numbers.Float.abs')
const entry2 = makeMethod('Standard.Base.Data.Numbers.Number.sqrt')
const additionalSelfType = 'Standard.Base.Data.Numbers.Number' as QualifiedName
const additionalSelfType = stdPath('Standard.Base.Data.Numbers.Number')
const filtering = new Filtering({
selfArg: { type: 'known', typename: 'Standard.Base.Data.Numbers.Float' },
selfArg: { type: 'known', typename: stdPath('Standard.Base.Data.Numbers.Float') },
})
expect(filtering.filter(entry1, [additionalSelfType])).not.toBeNull()
expect(filtering.filter(entry2, [additionalSelfType])).not.toBeNull()
Expand All @@ -97,7 +111,10 @@ test.each([
makeMethod('Standard.Base.Data.Vector.Vector2.get'),
])('$name is filtered out when Vector self type is specified', (entry) => {
const filtering = new Filtering({
selfArg: { type: 'known', typename: 'Standard.Base.Data.Vector.Vector' },
selfArg: {
type: 'known',
typename: stdPath('Standard.Base.Data.Vector.Vector'),
},
})
expect(filtering.filter(entry, [])).toBeNull()
})
Expand Down Expand Up @@ -203,14 +220,14 @@ test.each<MatchingTestCase>([
],
},
{
pattern: 'pr.foo',
pattern: 'ma.foo',
matchedSorted: [
{ module: 'local.Pr', name: 'foo' }, // exact match
{ module: 'local.Project', name: 'foo' }, // name exact match and owner name start match
{ module: 'local.Pr', name: 'foobar' }, // module exact match and name start match
{ module: 'local.Pr', name: 'bar', aliases: ['baz', 'foo'] }, // exact alias match
{ module: 'local.Project', name: 'bar', aliases: ['baz', 'foo'] }, // exact alias match, but nonexact owner match
{ module: 'local.Project', name: 'bar', aliases: ['bazbar', 'foobar'] }, // alias start match
{ module: 'local.Project.Ma', name: 'foo' }, // exact match
{ module: 'local.Project.Main', name: 'foo' }, // name exact match and owner name start match
{ module: 'local.Project.Ma', name: 'foobar' }, // module exact match and name start match
{ module: 'local.Project.Ma', name: 'bar', aliases: ['baz', 'foo'] }, // exact alias match
{ module: 'local.Project.Main', name: 'bar', aliases: ['baz', 'foo'] }, // exact alias match, but nonexact owner match
{ module: 'local.Project.Main', name: 'bar', aliases: ['bazbar', 'foobar'] }, // alias start match
{ name: 'bar_foo' }, // name word exact match
{ name: 'baz_foobar' }, // name word start match
{ name: 'bar', aliases: ['bar_foo'] }, // alias word exact match
Expand All @@ -220,22 +237,25 @@ test.each<MatchingTestCase>([
],
notMatched: [
{ module: 'local.Project.Data', name: 'foo' },
{ module: 'local.Ploject', name: 'foo' },
{ module: 'local.Pr', name: 'bar' },
],
},
])('Matching pattern $pattern', ({ pattern, matchedSorted, notMatched }) => {
const filtering = new Filtering({ pattern })
const matchedSortedEntries = Array.from(matchedSorted, ({ name, aliases, module }) =>
const matchedSortedEntries = matchedSorted.map(({ name, aliases, module }) =>
makeModuleMethod(`${module ?? 'local.Project'}.${name}`, { aliases: aliases ?? [] }),
)
const matchResults = Array.from(matchedSortedEntries, (entry) => filtering.filter(entry, []))
const matchResults = matchedSortedEntries.map((entry) => filtering.filter(entry, []))
// Checking matching entries
function checkResult(entry: SuggestionEntry, result: Opt<MatchResult>) {
expect(result, `Matching entry ${entry.definitionPath}`).not.toBeNull()
expect(result, `Matching entry ${JSON.stringify(entry.definitionPath)}`).not.toBeNull()
expect(
matchedText(
'memberOf' in entry && entry.memberOf ? qnLastSegment(entry.memberOf) : '',
'memberOf' in entry ?
entry.memberOf.path ?
qnLastSegment(entry.memberOf.path)
: 'Main'
: '',
entry.name,
result!,
)
Expand All @@ -257,10 +277,9 @@ test.each<MatchingTestCase>([

// Checking non-matching entries
for (const { module, name, aliases } of notMatched) {
const entry = {
...makeModuleMethod(`${module ?? 'local.Project'}.${name}`),
const entry = makeModuleMethod(`${module ?? 'local.Project'}.${name}`, {
aliases: aliases ?? [],
}
expect(filtering.filter(entry, []), entry.definitionPath).toBeNull()
})
expect(filtering.filter(entry, []), JSON.stringify(entry.definitionPath)).toBeNull()
}
})
22 changes: 11 additions & 11 deletions app/gui/src/project-view/components/ComponentBrowser/component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Filtering, type MatchResult } from '@/components/ComponentBrowser/filtering'
import { SuggestionDb } from '@/stores/suggestionDatabase'
import {
entryDisplayPath,
entryIsStatic,
SuggestionKind,
type SuggestionEntry,
Expand All @@ -10,8 +11,8 @@ import { compareOpt } from '@/util/compare'
import { isSome } from '@/util/data/opt'
import { Range } from '@/util/data/range'
import { displayedIconOf } from '@/util/getIconName'
import type { Icon } from '@/util/iconMetadata/iconName'
import { qnJoin, qnLastSegment, tryQualifiedName, type QualifiedName } from '@/util/qualifiedName'
import { type Icon } from '@/util/iconMetadata/iconName'
import { type ProjectPath } from '@/util/projectPath'

interface ComponentLabelInfo {
label: string
Expand All @@ -33,8 +34,8 @@ export interface Component extends ComponentLabel {

/** @returns the displayed label of given suggestion entry with information of highlighted ranges. */
export function labelOfEntry(entry: SuggestionEntry, match: MatchResult): ComponentLabelInfo {
if (entryIsStatic(entry) && entry.memberOf) {
const label = qnJoin(qnLastSegment(entry.memberOf), entry.name)
if (entryIsStatic(entry)) {
const label = entryDisplayPath(entry)
if ((!match.ownerNameRanges && !match.nameRanges) || match.matchedAlias) {
return {
label,
Expand Down Expand Up @@ -86,7 +87,9 @@ export function compareSuggestions(a: MatchedSuggestion, b: MatchedSuggestion):
const kindCompare =
+(a.entry.kind === SuggestionKind.Module) - +(b.entry.kind === SuggestionKind.Module)
if (kindCompare !== 0) return kindCompare
const moduleCompare = a.entry.definedIn.localeCompare(b.entry.definedIn)
const moduleCompare =
(a.entry.definedIn.project ?? '').localeCompare(b.entry.definedIn.project ?? '') ||
(a.entry.definedIn.path ?? '').localeCompare(b.entry.definedIn.path ?? '')
if (moduleCompare !== 0) return moduleCompare
return a.id - b.id
}
Expand All @@ -110,13 +113,10 @@ export function makeComponent({ id, entry, match }: ComponentInfo): Component {
/** Create {@link Component} list from filtered suggestions. */
export function makeComponentList(db: SuggestionDb, filtering: Filtering): Component[] {
function* matchSuggestions() {
const additionalSelfTypes: QualifiedName[] = []
const additionalSelfTypes: ProjectPath[] = []
if (filtering.selfArg?.type === 'known') {
const maybeName = tryQualifiedName(filtering.selfArg.typename)
if (maybeName.ok) {
const entry = db.getEntryByQualifiedName(maybeName.value)
if (entry) additionalSelfTypes.push(...db.ancestors(entry))
}
const entry = db.getEntryByProjectPath(filtering.selfArg.typename)
if (entry) additionalSelfTypes.push(...db.ancestors(entry))
}

for (const [id, entry] of db.entries()) {
Expand Down
Loading

0 comments on commit 00bde82

Please sign in to comment.