mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 20:56:39 +03:00
CB Fixes and Improvements (#8385)
* The "main view" (when no self type/module nor pattern is specified) contains all methods defined in top modules of every library * Entries defined inside any "Internal" module from stdlib will be hidden by default; they will be visible only when self type is specified or the module path leading to such module internals. * Fixed an issue with missing groups sometimes - we must wait with asking for groups for first executionComplete message. * Modules and local variables have different default icon. ![image](https://github.com/enso-org/enso/assets/3919101/cb33691e-222b-413e-a92e-2cf84e9fe4bb) ![image](https://github.com/enso-org/enso/assets/3919101/beab202d-4feb-4b00-ba0c-c141862da53c)
This commit is contained in:
parent
c889c8e83f
commit
a38680adf4
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 148 KiB |
@ -353,7 +353,7 @@ const handler = componentBrowserBindings.handler({
|
||||
<div class="top-bar">
|
||||
<div class="top-bar-inner">
|
||||
<ToggleIcon v-model="filterFlags.showLocal" icon="local_scope2" />
|
||||
<ToggleIcon icon="command_key3" />
|
||||
<ToggleIcon icon="command3" />
|
||||
<ToggleIcon v-model="filterFlags.showUnstable" icon="unstable2" />
|
||||
<ToggleIcon icon="marketplace" />
|
||||
<ToggleIcon v-model="docsVisible" icon="right_side_panel" class="first-on-right" />
|
||||
|
@ -20,15 +20,21 @@ test.each([
|
||||
{ ...makeStaticMethod('Standard.Base.Data.Vector.Vector.new'), groupIndex: 1 },
|
||||
makeModule('local.New_Project'),
|
||||
makeModule('Standard.Base.Data'),
|
||||
makeModuleMethod('Standard.Base.Data.read_text'),
|
||||
makeStaticMethod('local.Project.Foo.new'),
|
||||
makeStaticMethod('local.Project.Internalization.internalize'),
|
||||
])('$name entry is in the CB main view', (entry) => {
|
||||
const filtering = new Filtering({})
|
||||
expect(filtering.filter(entry)).not.toBeNull()
|
||||
})
|
||||
|
||||
test.each([
|
||||
makeModuleMethod('Standard.Base.Data.convert'), // not in group
|
||||
makeModuleMethod('Standard.Base.Data.Vector.some_method'), // not in top group
|
||||
{ ...makeMethod('Standard.Base.Data.Vector.Vector.get'), groupIndex: 1 }, // not static method
|
||||
makeModule('Standard.Base.Data.Vector'), // Not top module
|
||||
makeStaticMethod('Standard.Base.Internal.Foo.bar'), // Internal method
|
||||
makeModule('Standard.Base.Internal'), // Internal module
|
||||
makeModule('Standard.Internal.Foo'), // Internal project
|
||||
])('$name entry is not in the CB main view', (entry) => {
|
||||
const filtering = new Filtering({})
|
||||
expect(filtering.filter(entry)).toBeNull()
|
||||
@ -63,6 +69,18 @@ test.each([
|
||||
expect(substringFiltering.filter(entry)).toBeNull()
|
||||
})
|
||||
|
||||
test('Internal entry is not in the Standard.Base.Data content', () => {
|
||||
const entry = makeModule('Standard.Base.Data.Internal')
|
||||
const filtering = new Filtering({ qualifiedNamePattern: 'Standard.Base.Data' })
|
||||
expect(filtering.filter(entry)).toBeNull()
|
||||
})
|
||||
|
||||
test('The content of an internal module is displayed', () => {
|
||||
const entry = makeModuleMethod('Standard.Base.Data.Internal.foo')
|
||||
const filtering = new Filtering({ qualifiedNamePattern: 'Standard.Base.Data.Internal' })
|
||||
expect(filtering.filter(entry)).not.toBeNull()
|
||||
})
|
||||
|
||||
test.each([
|
||||
makeModuleMethod('local.Project.Module.foo'),
|
||||
makeModuleMethod('local.Project.Module.Submodule.foo_in_submodule'),
|
||||
|
@ -285,6 +285,7 @@ export class Filtering {
|
||||
showUnstable: boolean = false
|
||||
showLocal: boolean = false
|
||||
currentModule?: QualifiedName
|
||||
browsingInternalModule: boolean = false
|
||||
|
||||
constructor(filter: Filter, currentModule: Opt<QualifiedName> = undefined) {
|
||||
const { pattern, selfArg, qualifiedNamePattern, showUnstable, showLocal } = filter
|
||||
@ -295,6 +296,7 @@ export class Filtering {
|
||||
if (qualifiedNamePattern) {
|
||||
this.qualifiedName = new FilteringQualifiedName(qualifiedNamePattern)
|
||||
this.fullPattern = pattern ? `${qualifiedNamePattern}.${pattern}` : qualifiedNamePattern
|
||||
this.browsingInternalModule = isInternalModulePath(qualifiedNamePattern)
|
||||
} else if (pattern) this.fullPattern = pattern
|
||||
if (this.fullPattern) {
|
||||
let prefix = ''
|
||||
@ -334,9 +336,11 @@ export class Filtering {
|
||||
private mainViewFilter(entry: SuggestionEntry): MatchResult | null {
|
||||
const hasGroup = entry.groupIndex != null
|
||||
const isModule = entry.kind === SuggestionKind.Module
|
||||
const isTopElement = qnIsTopElement(entry.definedIn)
|
||||
if (!hasGroup && (!isModule || !isTopElement)) return null
|
||||
else return { score: 0 }
|
||||
const isMethod = entry.kind === SuggestionKind.Method
|
||||
const isInTopModule = qnIsTopElement(entry.definedIn)
|
||||
const isTopElementInMainView = (isMethod || isModule) && isInTopModule
|
||||
if (hasGroup || isTopElementInMainView) return { score: 0 }
|
||||
else return null
|
||||
}
|
||||
|
||||
private isLocal(entry: SuggestionEntry): boolean {
|
||||
@ -344,18 +348,28 @@ export class Filtering {
|
||||
}
|
||||
|
||||
filter(entry: SuggestionEntry): MatchResult | null {
|
||||
let qualifiedNameMatch: Opt<MatchedParts>
|
||||
if (entry.isPrivate) return null
|
||||
else if (!this.selfTypeMatches(entry)) return null
|
||||
else if (!(qualifiedNameMatch = this.qualifiedNameMatches(entry))) return null
|
||||
else if (!this.showUnstable && entry.isUnstable) return null
|
||||
else if (this.showLocal && !this.isLocal(entry)) return null
|
||||
else if (this.pattern) {
|
||||
if (this.selfArg == null && isInternal(entry) && !this.browsingInternalModule) return null
|
||||
if (!this.selfTypeMatches(entry)) return null
|
||||
const qualifiedNameMatch = this.qualifiedNameMatches(entry)
|
||||
if (!qualifiedNameMatch) return null
|
||||
if (!this.showUnstable && entry.isUnstable) return null
|
||||
if (this.showLocal && !this.isLocal(entry)) return null
|
||||
if (this.pattern) {
|
||||
const patternMatch = this.pattern.tryMatch(entry)
|
||||
if (!patternMatch) return null
|
||||
if (!this.showLocal && this.isLocal(entry)) patternMatch.score *= 2
|
||||
return { ...qualifiedNameMatch, ...patternMatch }
|
||||
} else if (this.isMainView()) return this.mainViewFilter(entry)
|
||||
else return { score: 0 }
|
||||
}
|
||||
if (this.isMainView()) return this.mainViewFilter(entry)
|
||||
return { score: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
function isInternal(entry: SuggestionEntry): boolean {
|
||||
return isInternalModulePath(entry.definedIn)
|
||||
}
|
||||
|
||||
function isInternalModulePath(path: string): boolean {
|
||||
return /Standard[.].*Internal(?:[._]|$)/.test(path)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
createWebsocketClient,
|
||||
rpcWithRetries as lsRpcWithRetries,
|
||||
} from '@/util/net'
|
||||
import { nextEvent } from '@/util/observable'
|
||||
import { isSome, type Opt } from '@/util/opt'
|
||||
import { tryQualifiedName } from '@/util/qualifiedName'
|
||||
import { VisualizationDataRegistry } from '@/util/visualizationDataRegistry'
|
||||
@ -518,6 +519,9 @@ export const useProjectStore = defineStore('project', () => {
|
||||
})
|
||||
}
|
||||
|
||||
const firstExecution = lsRpcConnection.then((lsRpc) =>
|
||||
nextEvent(lsRpc, 'executionContext/executionComplete'),
|
||||
)
|
||||
const executionContext = createExecutionContextForMain()
|
||||
const computedValueRegistry = ComputedValueRegistry.WithExecutionContext(executionContext)
|
||||
const visualizationDataRegistry = new VisualizationDataRegistry(executionContext, dataConnection)
|
||||
@ -563,6 +567,7 @@ export const useProjectStore = defineStore('project', () => {
|
||||
},
|
||||
name: projectName,
|
||||
executionContext,
|
||||
firstExecution,
|
||||
diagnostics,
|
||||
module,
|
||||
modulePath,
|
||||
|
@ -76,6 +76,7 @@ class Synchronizer {
|
||||
lsRpc.acquireCapability('search/receivesSuggestionsDatabaseUpdates', {}),
|
||||
)
|
||||
this.setupUpdateHandler(lsRpc)
|
||||
this.loadGroups(lsRpc, projectStore.firstExecution)
|
||||
return Synchronizer.loadDatabase(entries, lsRpc, groups.value)
|
||||
})
|
||||
|
||||
@ -125,7 +126,11 @@ class Synchronizer {
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private async loadGroups(lsRpc: LanguageServer, firstExecution: Promise<unknown>) {
|
||||
this.queue.pushTask(async ({ currentVersion }) => {
|
||||
await firstExecution
|
||||
const groups = await lsRpc.getComponentGroups()
|
||||
this.groups.value = groups.componentGroups.map(
|
||||
(group): Group => ({
|
||||
|
@ -1,6 +1,5 @@
|
||||
import type { Opt } from '@/util/opt'
|
||||
import { Vec2 } from '@/util/vec2'
|
||||
import type { ObservableV2 } from 'lib0/observable'
|
||||
import {
|
||||
computed,
|
||||
onScopeDispose,
|
||||
@ -12,7 +11,6 @@ import {
|
||||
type Ref,
|
||||
type WatchSource,
|
||||
} from 'vue'
|
||||
import { ReactiveDb } from './database/reactiveDb'
|
||||
|
||||
export function isClick(e: MouseEvent | PointerEvent) {
|
||||
if (e instanceof PointerEvent) return e.pointerId !== -1
|
||||
|
@ -1,13 +1,17 @@
|
||||
import type { SuggestionEntry, Typename } from '@/stores/suggestionDatabase/entry'
|
||||
import {
|
||||
SuggestionKind,
|
||||
type SuggestionEntry,
|
||||
type Typename,
|
||||
} from '@/stores/suggestionDatabase/entry'
|
||||
import type { Icon } from '@/util/iconName'
|
||||
import type { MethodPointer } from 'shared/languageServerTypes'
|
||||
|
||||
const oldIconNameToNewIconNameLookup: Record<string, string> = {
|
||||
const oldIconNameToNewIconNameLookup: Record<string, Icon> = {
|
||||
/* eslint-disable camelcase */
|
||||
dataframe_clean: 'clean_dataframe',
|
||||
dataframe_clean: 'table_clean',
|
||||
dataframe_map_row: 'map_row',
|
||||
dataframe_map_column: 'add_column',
|
||||
dataframes_join: 'join3',
|
||||
dataframe_map_column: 'column_add',
|
||||
dataframes_join: 'join2-1',
|
||||
dataframes_union: 'union',
|
||||
sigma: 'transform4',
|
||||
io: 'in_out',
|
||||
@ -25,8 +29,8 @@ export function mapOldIconName(oldIconName: string): Icon {
|
||||
|
||||
const typeNameToIconLookup: Record<string, Icon> = {
|
||||
'Standard.Base.Data.Text.Text': 'text_input',
|
||||
'Standard.Base.Data.Numbers.Integer': 'number_input',
|
||||
'Standard.Base.Data.Numbers.Float': 'number_input',
|
||||
'Standard.Base.Data.Numbers.Integer': 'input_number',
|
||||
'Standard.Base.Data.Numbers.Float': 'input_number',
|
||||
'Standard.Base.Data.Array.Array': 'array_new',
|
||||
'Standard.Base.Data.Vector.Vector': 'array_new',
|
||||
'Standard.Base.Data.Time.Date.Date': 'calendar',
|
||||
@ -43,7 +47,10 @@ export function displayedIconOf(
|
||||
methodCall?: MethodPointer,
|
||||
actualType?: Typename,
|
||||
): Icon {
|
||||
if (entry?.iconName) return mapOldIconName(entry.iconName)
|
||||
if (!methodCall?.name && actualType) return typeNameToIcon(actualType)
|
||||
if (entry) {
|
||||
if (entry.iconName) return mapOldIconName(entry.iconName)
|
||||
if (entry.kind === SuggestionKind.Local) return 'local_scope2'
|
||||
if (entry.kind === SuggestionKind.Module) return 'collection'
|
||||
} else if (!methodCall?.name && actualType) return typeNameToIcon(actualType)
|
||||
return 'enso_logo'
|
||||
}
|
||||
|
24
app/gui2/src/util/observable.ts
Normal file
24
app/gui2/src/util/observable.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { ObservableV2 } from 'lib0/observable'
|
||||
|
||||
export type Events<O extends ObservableV2<any>> = O extends ObservableV2<infer E> ? E : never
|
||||
|
||||
/** Returns promise which will resolve on the next event. The promise will have the event's
|
||||
* payload. */
|
||||
export function nextEvent<O extends ObservableV2<any>, NAME extends string>(
|
||||
observable: O,
|
||||
event: NAME,
|
||||
): Promise<Parameters<Events<O>[NAME]>> {
|
||||
type Params = Parameters<Events<O>[NAME]>
|
||||
return new Promise<Params>((resolve) => {
|
||||
observable.once(event, (...args: Params) => {
|
||||
resolve(args)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
declare const EVENTS_BRAND: unique symbol
|
||||
declare module 'lib0/observable' {
|
||||
interface ObservableV2<EVENTS extends { [key: string]: (...arg0: any[]) => void }> {
|
||||
[EVENTS_BRAND]: EVENTS
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user