Skip to content

Commit 00bde82

Browse files
authored
Abstract project names (#12106)
Use abstract project references in retained state. Fixes #11815.
1 parent c6d00bb commit 00bde82

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1525
-939
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
- [Redo stack is no longer lost when interacting with text literals][11908].
1212
- [Fixed bug when clicking header in Table Editor Widget didn't start editing
1313
it][12064]
14+
- [Fixed bugs occurring after renaming project from within graph editor][12106].
1415

1516
[11889]: https://github.com/enso-org/enso/pull/11889
1617
[11836]: https://github.com/enso-org/enso/pull/11836
1718
[12051]: https://github.com/enso-org/enso/pull/12051
1819
[11902]: https://github.com/enso-org/enso/pull/11902
1920
[11908]: https://github.com/enso-org/enso/pull/11908
2021
[12064]: https://github.com/enso-org/enso/pull/12064
22+
[12106]: https://github.com/enso-org/enso/pull/12106
2123

2224
#### Enso Standard Library
2325

app/gui/src/project-view/ProjectView.vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import GraphEditor from '@/components/GraphEditor.vue'
44
import { provideBackend } from '@/providers/backend'
55
import { provideEventLogger } from '@/providers/eventLogging'
66
import { provideVisibility } from '@/providers/visibility'
7-
import { LsUrls, provideProjectStore } from '@/stores/project'
7+
import { type LsUrls, provideProjectStore } from '@/stores/project'
8+
import { provideProjectNames } from '@/stores/projectNames'
89
import { provideSettings } from '@/stores/settings'
9-
import { Opt } from '@/util/data/opt'
10+
import { type Opt } from '@/util/data/opt'
1011
import { useEventListener } from '@vueuse/core'
1112
import { markRaw, onActivated, onDeactivated, ref, toRaw, toRef, watch } from 'vue'
1213
@@ -49,7 +50,12 @@ watch(
4950
5051
useEventListener(window, 'beforeunload', () => logger.send('ide_project_closed'))
5152
52-
provideProjectStore(props)
53+
const projectNames = provideProjectNames(
54+
toRef(props, 'projectNamespace'),
55+
props.projectName,
56+
toRef(props, 'projectDisplayedName'),
57+
)
58+
provideProjectStore(props, projectNames)
5359
provideSettings()
5460
5561
const visible = ref(false)

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { injectInteractionHandler, type Interaction } from '@/providers/interact
1515
import { useGraphStore } from '@/stores/graph'
1616
import type { RequiredImport } from '@/stores/graph/imports'
1717
import { useProjectStore } from '@/stores/project'
18+
import { injectProjectNames } from '@/stores/projectNames'
1819
import { useSuggestionDbStore } from '@/stores/suggestionDatabase'
1920
import { type Typename } from '@/stores/suggestionDatabase/entry'
2021
import type { VisualizationDataSource } from '@/stores/visualization'
@@ -50,6 +51,7 @@ const projectStore = useProjectStore()
5051
const suggestionDbStore = useSuggestionDbStore()
5152
const graphStore = useGraphStore()
5253
const interaction = injectInteractionHandler()
54+
const projectNames = injectProjectNames()
5355
5456
const props = defineProps<{
5557
nodePosition: Vec2
@@ -177,7 +179,7 @@ const input = useComponentBrowserInput()
177179
178180
const currentFiltering = computed(() => {
179181
if (input.mode.mode === 'componentBrowsing') {
180-
const currentModule = projectStore.modulePath
182+
const currentModule = projectStore.moduleProjectPath
181183
return new Filtering(input.mode.filter, currentModule?.ok ? currentModule.value : undefined)
182184
} else {
183185
return undefined
@@ -260,11 +262,11 @@ const previewedCode = debouncedGetter<string>(() => input.code, 200)
260262
const previewedSuggestionReturnType = computed(() => {
261263
const id = input.mode.mode === 'codeEditing' ? input.mode.appliedSuggestion : undefined
262264
const appliedEntry = id != null ? suggestionDbStore.entries.get(id) : undefined
263-
if (appliedEntry != null) return appliedEntry.returnType
264-
else if (props.usage.type === 'editNode') {
265-
return graphStore.db.getNodeMainSuggestion(props.usage.node)?.returnType
266-
}
267-
return undefined
265+
const entry =
266+
appliedEntry ? appliedEntry
267+
: props.usage.type === 'editNode' ? graphStore.db.getNodeMainSuggestion(props.usage.node)
268+
: undefined
269+
return entry?.returnType(projectNames)
268270
})
269271
270272
const previewDataSource = computed<VisualizationDataSource | undefined>(() => {
@@ -316,7 +318,7 @@ function applySuggestion(component: Opt<Component> = null) {
316318
function acceptInput() {
317319
const appliedReturnType =
318320
input.mode.mode === 'codeEditing' && input.mode.appliedSuggestion != null ?
319-
suggestionDbStore.entries.get(input.mode.appliedSuggestion)?.returnType
321+
suggestionDbStore.entries.get(input.mode.appliedSuggestion)?.returnType(projectNames)
320322
: undefined
321323
emit('accepted', input.code.trim(), input.importsToAdd(), appliedReturnType)
322324
interaction.ended(cbOpen)

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

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ test.each([
2222
[makeStaticMethod('Standard.Base.Data.Vector.new'), 'Vector.new'],
2323
[makeMethod('Standard.Base.Data.Vector.get'), 'get'],
2424
[makeConstructor('Standard.Table.Join_Kind.Join_Kind.Inner'), 'Join_Kind.Inner'],
25-
[makeModuleMethod('local.Project.main'), 'Project.main'],
25+
[makeModuleMethod('local.Mock_Project.main'), 'Main.main'],
2626
])("$name Component's label is valid", (suggestion, expected) => {
2727
expect(labelOfEntry(suggestion, { score: 0 }).label).toBe(expected)
2828
})
@@ -31,42 +31,42 @@ test('Suggestions are ordered properly', () => {
3131
const sortedEntries: MatchedSuggestion[] = [
3232
{
3333
id: 100,
34-
entry: makeModuleMethod('local.Project.Z.best_score'),
34+
entry: makeModuleMethod('local.Mock_Project.Z.best_score'),
3535
match: { score: 0 },
3636
},
3737
{
3838
id: 90,
39-
entry: { ...makeModuleMethod('local.Project.Z.b'), groupIndex: 0 },
39+
entry: { ...makeModuleMethod('local.Mock_Project.Z.b'), groupIndex: 0 },
4040
match: { score: 50 },
4141
},
4242
{
4343
id: 91,
44-
entry: { ...makeModuleMethod('local.Project.Z.a'), groupIndex: 0 },
44+
entry: { ...makeModuleMethod('local.Mock_Project.Z.a'), groupIndex: 0 },
4545
match: { score: 50 },
4646
},
4747
{
4848
id: 89,
49-
entry: { ...makeModuleMethod('local.Project.A.foo'), groupIndex: 1 },
49+
entry: { ...makeModuleMethod('local.Mock_Project.A.foo'), groupIndex: 1 },
5050
match: { score: 50 },
5151
},
5252
{
5353
id: 88,
54-
entry: { ...makeModuleMethod('local.Project.B.another_module'), groupIndex: 1 },
54+
entry: { ...makeModuleMethod('local.Mock_Project.B.another_module'), groupIndex: 1 },
5555
match: { score: 50 },
5656
},
5757
{
5858
id: 87,
59-
entry: { ...makeModule('local.Project.A'), groupIndex: 1 },
59+
entry: { ...makeModule('local.Mock_Project.A'), groupIndex: 1 },
6060
match: { score: 50 },
6161
},
6262
{
6363
id: 50,
64-
entry: makeModuleMethod('local.Project.Z.module_content'),
64+
entry: makeModuleMethod('local.Mock_Project.Z.module_content'),
6565
match: { score: 50 },
6666
},
6767
{
6868
id: 49,
69-
entry: makeModule('local.Project.Z.Module'),
69+
entry: makeModule('local.Mock_Project.Z.Module'),
7070
match: { score: 50 },
7171
},
7272
]
@@ -82,16 +82,16 @@ test('Suggestions are ordered properly', () => {
8282

8383
test.each`
8484
name | aliases | highlighted
85-
${'foo_bar'} | ${[]} | ${'Project.<foo><_bar>'}
86-
${'foo_xyz_barabc'} | ${[]} | ${'Project.<foo>_xyz<_bar>abc'}
87-
${'fooabc_barabc'} | ${[]} | ${'Project.<foo>abc<_bar>abc'}
88-
${'bar'} | ${['foo_bar', 'foo']} | ${'Project.bar (<foo><_bar>)'}
89-
${'bar'} | ${['foo', 'foo_xyz_barabc']} | ${'Project.bar (<foo>_xyz<_bar>abc)'}
90-
${'bar'} | ${['foo', 'fooabc_barabc']} | ${'Project.bar (<foo>abc<_bar>abc)'}
91-
${'xyz_foo_abc_bar_xyz'} | ${[]} | ${'Project.xyz_<foo>_abc<_bar>_xyz'}
92-
${'xyz_fooabc_abc_barabc_xyz'} | ${[]} | ${'Project.xyz_<foo>abc_abc<_bar>abc_xyz'}
93-
${'bar'} | ${['xyz_foo_abc_bar_xyz']} | ${'Project.bar (xyz_<foo>_abc<_bar>_xyz)'}
94-
${'bar'} | ${['xyz_fooabc_abc_barabc_xyz']} | ${'Project.bar (xyz_<foo>abc_abc<_bar>abc_xyz)'}
85+
${'foo_bar'} | ${[]} | ${'Main.<foo><_bar>'}
86+
${'foo_xyz_barabc'} | ${[]} | ${'Main.<foo>_xyz<_bar>abc'}
87+
${'fooabc_barabc'} | ${[]} | ${'Main.<foo>abc<_bar>abc'}
88+
${'bar'} | ${['foo_bar', 'foo']} | ${'Main.bar (<foo><_bar>)'}
89+
${'bar'} | ${['foo', 'foo_xyz_barabc']} | ${'Main.bar (<foo>_xyz<_bar>abc)'}
90+
${'bar'} | ${['foo', 'fooabc_barabc']} | ${'Main.bar (<foo>abc<_bar>abc)'}
91+
${'xyz_foo_abc_bar_xyz'} | ${[]} | ${'Main.xyz_<foo>_abc<_bar>_xyz'}
92+
${'xyz_fooabc_abc_barabc_xyz'} | ${[]} | ${'Main.xyz_<foo>abc_abc<_bar>abc_xyz'}
93+
${'bar'} | ${['xyz_foo_abc_bar_xyz']} | ${'Main.bar (xyz_<foo>_abc<_bar>_xyz)'}
94+
${'bar'} | ${['xyz_fooabc_abc_barabc_xyz']} | ${'Main.bar (xyz_<foo>abc_abc<_bar>abc_xyz)'}
9595
`('Matched ranges of $highlighted are correct', ({ name, aliases, highlighted }) => {
9696
function replaceMatches(component: Component) {
9797
if (!component.matchedRanges) return component.label
@@ -104,7 +104,7 @@ test.each`
104104
}
105105

106106
const pattern = 'foo_bar'
107-
const entry = makeModuleMethod(`local.Project.${name}`, { aliases: aliases ?? [] })
107+
const entry = makeModuleMethod(`local.Mock_Project.${name}`, { aliases: aliases ?? [] })
108108
const filtering = new Filtering({ pattern })
109109
const match = filtering.filter(entry, [])
110110
expect(match).not.toBeNull()

app/gui/src/project-view/components/ComponentBrowser/__tests__/filtering.test.ts

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Filtering, type MatchResult } from '@/components/ComponentBrowser/filtering'
2-
import { SuggestionEntry } from '@/stores/suggestionDatabase/entry'
2+
import { type SuggestionEntry } from '@/stores/suggestionDatabase/entry'
33
import {
44
makeConstructor,
55
makeFunction,
@@ -9,9 +9,12 @@ import {
99
makeModuleMethod,
1010
makeStaticMethod,
1111
} from '@/stores/suggestionDatabase/mockSuggestion'
12-
import { qnLastSegment, QualifiedName } from '@/util/qualifiedName'
12+
import { assert } from '@/util/assert'
13+
import { parseAbsoluteProjectPath } from '@/util/projectPath'
14+
import { qnLastSegment, tryQualifiedName } from '@/util/qualifiedName'
1315
import { expect, test } from 'vitest'
14-
import { Opt } from 'ydoc-shared/util/data/opt'
16+
import { type Opt } from 'ydoc-shared/util/data/opt'
17+
import { unwrap } from 'ydoc-shared/util/data/result'
1518

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

43+
function stdPath(path: string) {
44+
assert(path.startsWith('Standard.'))
45+
return parseAbsoluteProjectPath(unwrap(tryQualifiedName(path)))
46+
}
47+
4048
test('An Instance method is shown when self arg matches', () => {
4149
const entry1 = makeMethod('Standard.Base.Data.Vector.Vector.get')
4250
const entry2 = makeMethod('Standard.Base.Data.Table.get')
4351
const filteringWithSelfType = new Filtering({
44-
selfArg: { type: 'known', typename: 'Standard.Base.Data.Vector.Vector' },
52+
selfArg: {
53+
type: 'known',
54+
typename: stdPath('Standard.Base.Data.Vector.Vector'),
55+
},
4556
})
4657
expect(filteringWithSelfType.filter(entry1, [])).not.toBeNull()
4758
expect(filteringWithSelfType.filter(entry2, [])).toBeNull()
@@ -59,7 +70,10 @@ test('`Any` type methods taken into account when filtering', () => {
5970
const entry1 = makeMethod('Standard.Base.Data.Vector.Vector.get')
6071
const entry2 = makeMethod('Standard.Base.Any.Any.to_string')
6172
const filtering = new Filtering({
62-
selfArg: { type: 'known', typename: 'Standard.Base.Data.Vector.Vector' },
73+
selfArg: {
74+
type: 'known',
75+
typename: stdPath('Standard.Base.Data.Vector.Vector'),
76+
},
6377
})
6478
expect(filtering.filter(entry1, [])).not.toBeNull()
6579
expect(filtering.filter(entry2, [])).not.toBeNull()
@@ -72,9 +86,9 @@ test('`Any` type methods taken into account when filtering', () => {
7286
test('Additional self types are taken into account when filtering', () => {
7387
const entry1 = makeMethod('Standard.Base.Data.Numbers.Float.abs')
7488
const entry2 = makeMethod('Standard.Base.Data.Numbers.Number.sqrt')
75-
const additionalSelfType = 'Standard.Base.Data.Numbers.Number' as QualifiedName
89+
const additionalSelfType = stdPath('Standard.Base.Data.Numbers.Number')
7690
const filtering = new Filtering({
77-
selfArg: { type: 'known', typename: 'Standard.Base.Data.Numbers.Float' },
91+
selfArg: { type: 'known', typename: stdPath('Standard.Base.Data.Numbers.Float') },
7892
})
7993
expect(filtering.filter(entry1, [additionalSelfType])).not.toBeNull()
8094
expect(filtering.filter(entry2, [additionalSelfType])).not.toBeNull()
@@ -97,7 +111,10 @@ test.each([
97111
makeMethod('Standard.Base.Data.Vector.Vector2.get'),
98112
])('$name is filtered out when Vector self type is specified', (entry) => {
99113
const filtering = new Filtering({
100-
selfArg: { type: 'known', typename: 'Standard.Base.Data.Vector.Vector' },
114+
selfArg: {
115+
type: 'known',
116+
typename: stdPath('Standard.Base.Data.Vector.Vector'),
117+
},
101118
})
102119
expect(filtering.filter(entry, [])).toBeNull()
103120
})
@@ -203,14 +220,14 @@ test.each<MatchingTestCase>([
203220
],
204221
},
205222
{
206-
pattern: 'pr.foo',
223+
pattern: 'ma.foo',
207224
matchedSorted: [
208-
{ module: 'local.Pr', name: 'foo' }, // exact match
209-
{ module: 'local.Project', name: 'foo' }, // name exact match and owner name start match
210-
{ module: 'local.Pr', name: 'foobar' }, // module exact match and name start match
211-
{ module: 'local.Pr', name: 'bar', aliases: ['baz', 'foo'] }, // exact alias match
212-
{ module: 'local.Project', name: 'bar', aliases: ['baz', 'foo'] }, // exact alias match, but nonexact owner match
213-
{ module: 'local.Project', name: 'bar', aliases: ['bazbar', 'foobar'] }, // alias start match
225+
{ module: 'local.Project.Ma', name: 'foo' }, // exact match
226+
{ module: 'local.Project.Main', name: 'foo' }, // name exact match and owner name start match
227+
{ module: 'local.Project.Ma', name: 'foobar' }, // module exact match and name start match
228+
{ module: 'local.Project.Ma', name: 'bar', aliases: ['baz', 'foo'] }, // exact alias match
229+
{ module: 'local.Project.Main', name: 'bar', aliases: ['baz', 'foo'] }, // exact alias match, but nonexact owner match
230+
{ module: 'local.Project.Main', name: 'bar', aliases: ['bazbar', 'foobar'] }, // alias start match
214231
{ name: 'bar_foo' }, // name word exact match
215232
{ name: 'baz_foobar' }, // name word start match
216233
{ name: 'bar', aliases: ['bar_foo'] }, // alias word exact match
@@ -220,22 +237,25 @@ test.each<MatchingTestCase>([
220237
],
221238
notMatched: [
222239
{ module: 'local.Project.Data', name: 'foo' },
223-
{ module: 'local.Ploject', name: 'foo' },
224240
{ module: 'local.Pr', name: 'bar' },
225241
],
226242
},
227243
])('Matching pattern $pattern', ({ pattern, matchedSorted, notMatched }) => {
228244
const filtering = new Filtering({ pattern })
229-
const matchedSortedEntries = Array.from(matchedSorted, ({ name, aliases, module }) =>
245+
const matchedSortedEntries = matchedSorted.map(({ name, aliases, module }) =>
230246
makeModuleMethod(`${module ?? 'local.Project'}.${name}`, { aliases: aliases ?? [] }),
231247
)
232-
const matchResults = Array.from(matchedSortedEntries, (entry) => filtering.filter(entry, []))
248+
const matchResults = matchedSortedEntries.map((entry) => filtering.filter(entry, []))
233249
// Checking matching entries
234250
function checkResult(entry: SuggestionEntry, result: Opt<MatchResult>) {
235-
expect(result, `Matching entry ${entry.definitionPath}`).not.toBeNull()
251+
expect(result, `Matching entry ${JSON.stringify(entry.definitionPath)}`).not.toBeNull()
236252
expect(
237253
matchedText(
238-
'memberOf' in entry && entry.memberOf ? qnLastSegment(entry.memberOf) : '',
254+
'memberOf' in entry ?
255+
entry.memberOf.path ?
256+
qnLastSegment(entry.memberOf.path)
257+
: 'Main'
258+
: '',
239259
entry.name,
240260
result!,
241261
)
@@ -257,10 +277,9 @@ test.each<MatchingTestCase>([
257277

258278
// Checking non-matching entries
259279
for (const { module, name, aliases } of notMatched) {
260-
const entry = {
261-
...makeModuleMethod(`${module ?? 'local.Project'}.${name}`),
280+
const entry = makeModuleMethod(`${module ?? 'local.Project'}.${name}`, {
262281
aliases: aliases ?? [],
263-
}
264-
expect(filtering.filter(entry, []), entry.definitionPath).toBeNull()
282+
})
283+
expect(filtering.filter(entry, []), JSON.stringify(entry.definitionPath)).toBeNull()
265284
}
266285
})

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Filtering, type MatchResult } from '@/components/ComponentBrowser/filtering'
22
import { SuggestionDb } from '@/stores/suggestionDatabase'
33
import {
4+
entryDisplayPath,
45
entryIsStatic,
56
SuggestionKind,
67
type SuggestionEntry,
@@ -10,8 +11,8 @@ import { compareOpt } from '@/util/compare'
1011
import { isSome } from '@/util/data/opt'
1112
import { Range } from '@/util/data/range'
1213
import { displayedIconOf } from '@/util/getIconName'
13-
import type { Icon } from '@/util/iconMetadata/iconName'
14-
import { qnJoin, qnLastSegment, tryQualifiedName, type QualifiedName } from '@/util/qualifiedName'
14+
import { type Icon } from '@/util/iconMetadata/iconName'
15+
import { type ProjectPath } from '@/util/projectPath'
1516

1617
interface ComponentLabelInfo {
1718
label: string
@@ -33,8 +34,8 @@ export interface Component extends ComponentLabel {
3334

3435
/** @returns the displayed label of given suggestion entry with information of highlighted ranges. */
3536
export function labelOfEntry(entry: SuggestionEntry, match: MatchResult): ComponentLabelInfo {
36-
if (entryIsStatic(entry) && entry.memberOf) {
37-
const label = qnJoin(qnLastSegment(entry.memberOf), entry.name)
37+
if (entryIsStatic(entry)) {
38+
const label = entryDisplayPath(entry)
3839
if ((!match.ownerNameRanges && !match.nameRanges) || match.matchedAlias) {
3940
return {
4041
label,
@@ -86,7 +87,9 @@ export function compareSuggestions(a: MatchedSuggestion, b: MatchedSuggestion):
8687
const kindCompare =
8788
+(a.entry.kind === SuggestionKind.Module) - +(b.entry.kind === SuggestionKind.Module)
8889
if (kindCompare !== 0) return kindCompare
89-
const moduleCompare = a.entry.definedIn.localeCompare(b.entry.definedIn)
90+
const moduleCompare =
91+
(a.entry.definedIn.project ?? '').localeCompare(b.entry.definedIn.project ?? '') ||
92+
(a.entry.definedIn.path ?? '').localeCompare(b.entry.definedIn.path ?? '')
9093
if (moduleCompare !== 0) return moduleCompare
9194
return a.id - b.id
9295
}
@@ -110,13 +113,10 @@ export function makeComponent({ id, entry, match }: ComponentInfo): Component {
110113
/** Create {@link Component} list from filtered suggestions. */
111114
export function makeComponentList(db: SuggestionDb, filtering: Filtering): Component[] {
112115
function* matchSuggestions() {
113-
const additionalSelfTypes: QualifiedName[] = []
116+
const additionalSelfTypes: ProjectPath[] = []
114117
if (filtering.selfArg?.type === 'known') {
115-
const maybeName = tryQualifiedName(filtering.selfArg.typename)
116-
if (maybeName.ok) {
117-
const entry = db.getEntryByQualifiedName(maybeName.value)
118-
if (entry) additionalSelfTypes.push(...db.ancestors(entry))
119-
}
118+
const entry = db.getEntryByProjectPath(filtering.selfArg.typename)
119+
if (entry) additionalSelfTypes.push(...db.ancestors(entry))
120120
}
121121

122122
for (const [id, entry] of db.entries()) {

0 commit comments

Comments
 (0)