mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 20:16:47 +03:00
Navigating documentation panel with breadcrumbs (#8176)
Closes #8131 https://github.com/enso-org/enso/assets/6566674/69609307-d5f5-4185-af65-aed1f6b85978
This commit is contained in:
parent
a862ea7948
commit
4de51b25ff
@ -1,15 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { Item as Breadcrumb } from '@/components/DocumentationPanel/DocsBreadcrumbs.vue'
|
||||||
|
import Breadcrumbs from '@/components/DocumentationPanel/DocsBreadcrumbs.vue'
|
||||||
import DocsExamples from '@/components/DocumentationPanel/DocsExamples.vue'
|
import DocsExamples from '@/components/DocumentationPanel/DocsExamples.vue'
|
||||||
import DocsHeader from '@/components/DocumentationPanel/DocsHeader.vue'
|
import DocsHeader from '@/components/DocumentationPanel/DocsHeader.vue'
|
||||||
import DocsList from '@/components/DocumentationPanel/DocsList.vue'
|
import DocsList from '@/components/DocumentationPanel/DocsList.vue'
|
||||||
import DocsSynopsis from '@/components/DocumentationPanel/DocsSynopsis.vue'
|
import DocsSynopsis from '@/components/DocumentationPanel/DocsSynopsis.vue'
|
||||||
import DocsTags from '@/components/DocumentationPanel/DocsTags.vue'
|
import DocsTags from '@/components/DocumentationPanel/DocsTags.vue'
|
||||||
|
import { HistoryStack } from '@/components/DocumentationPanel/history'
|
||||||
import type { Docs, FunctionDocs, Sections, TypeDocs } from '@/components/DocumentationPanel/ir'
|
import type { Docs, FunctionDocs, Sections, TypeDocs } from '@/components/DocumentationPanel/ir'
|
||||||
import { lookupDocumentation, placeholder } from '@/components/DocumentationPanel/ir'
|
import { lookupDocumentation, placeholder } from '@/components/DocumentationPanel/ir'
|
||||||
import { useSuggestionDbStore } from '@/stores/suggestionDatabase'
|
import { useSuggestionDbStore } from '@/stores/suggestionDatabase'
|
||||||
import type { SuggestionId } from '@/stores/suggestionDatabase/entry'
|
import type { SuggestionId } from '@/stores/suggestionDatabase/entry'
|
||||||
|
import type { Icon as IconName } from '@/util/iconName'
|
||||||
import { type Opt } from '@/util/opt'
|
import { type Opt } from '@/util/opt'
|
||||||
import { computed } from 'vue'
|
import type { QualifiedName } from '@/util/qualifiedName'
|
||||||
|
import { qnSegments, qnSlice } from '@/util/qualifiedName'
|
||||||
|
import { computed, watch } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<{ selectedEntry: Opt<SuggestionId> }>()
|
const props = defineProps<{ selectedEntry: Opt<SuggestionId> }>()
|
||||||
const emit = defineEmits<{ 'update:selectedEntry': [id: SuggestionId] }>()
|
const emit = defineEmits<{ 'update:selectedEntry': [id: SuggestionId] }>()
|
||||||
@ -40,29 +46,113 @@ const types = computed<TypeDocs[]>(() => {
|
|||||||
const docs = documentation.value
|
const docs = documentation.value
|
||||||
return docs.kind === 'Module' ? docs.types : []
|
return docs.kind === 'Module' ? docs.types : []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isPlaceholder = computed(() => 'Placeholder' in documentation.value)
|
||||||
|
|
||||||
|
const name = computed<Opt<QualifiedName>>(() => {
|
||||||
|
const docs = documentation.value
|
||||||
|
return docs.kind === 'Placeholder' ? null : docs.name
|
||||||
|
})
|
||||||
|
|
||||||
|
// === Breadcrumbs ===
|
||||||
|
|
||||||
|
const color = computed<string>(() => {
|
||||||
|
const id = props.selectedEntry
|
||||||
|
if (id) {
|
||||||
|
const entry = db.entries.get(id)
|
||||||
|
const groupIndex = entry?.groupIndex ?? -1
|
||||||
|
const group = db.groups[groupIndex]
|
||||||
|
if (group) {
|
||||||
|
const name = group.name.replace(/\s/g, '-')
|
||||||
|
return `var(--group-color-${name})`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'var(--group-color-fallback)'
|
||||||
|
})
|
||||||
|
|
||||||
|
const icon = computed<IconName>(() => {
|
||||||
|
const id = props.selectedEntry
|
||||||
|
if (id) {
|
||||||
|
const entry = db.entries.get(id)
|
||||||
|
return entry?.iconName ?? 'marketplace'
|
||||||
|
} else {
|
||||||
|
return 'marketplace'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const historyStack = new HistoryStack()
|
||||||
|
|
||||||
|
// Reset breadcrumbs history when the user selects the entry from the component list.
|
||||||
|
watch(
|
||||||
|
() => props.selectedEntry,
|
||||||
|
(entry) => {
|
||||||
|
if (entry && historyStack.current.value !== entry) {
|
||||||
|
historyStack.reset(entry)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update displayed documentation page when the user uses breadcrumbs.
|
||||||
|
watch(historyStack.current, (current) => {
|
||||||
|
if (current) {
|
||||||
|
emit('update:selectedEntry', current)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const breadcrumbs = computed<Breadcrumb[]>(() => {
|
||||||
|
if (name.value) {
|
||||||
|
const segments = qnSegments(name.value)
|
||||||
|
return segments.slice(1).map((s) => ({ label: s.toLowerCase() }))
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleBreadcrumbClick(index: number) {
|
||||||
|
if (name.value) {
|
||||||
|
const qName = qnSlice(name.value, 0, index + 2)
|
||||||
|
if (qName.ok) {
|
||||||
|
const [id] = db.entries.nameToId.lookup(qName.value)
|
||||||
|
if (id) {
|
||||||
|
historyStack.record(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="DocumentationPanel scrollable" @wheel.stop.passive>
|
<div class="DocumentationPanel scrollable" @wheel.stop.passive>
|
||||||
<h1 v-if="documentation.kind === 'Placeholder'">{{ documentation.text }}</h1>
|
<h1 v-if="documentation.kind === 'Placeholder'">{{ documentation.text }}</h1>
|
||||||
<DocsTags v-if="sections.tags.length > 0" :tags="sections.tags" />
|
<Breadcrumbs
|
||||||
|
v-if="!isPlaceholder"
|
||||||
|
:breadcrumbs="breadcrumbs"
|
||||||
|
:color="color"
|
||||||
|
:icon="icon"
|
||||||
|
:canGoForward="historyStack.canGoForward()"
|
||||||
|
:canGoBackward="historyStack.canGoBackward()"
|
||||||
|
@click="(index) => handleBreadcrumbClick(index)"
|
||||||
|
@forward="historyStack.forward()"
|
||||||
|
@backward="historyStack.backward()"
|
||||||
|
/>
|
||||||
|
<DocsTags v-if="sections.tags.length > 0" class="tags" :tags="sections.tags" />
|
||||||
<div class="sections">
|
<div class="sections">
|
||||||
<span v-if="sections.synopsis.length == 0">{{ 'No documentation available.' }}</span>
|
<span v-if="sections.synopsis.length == 0">{{ 'No documentation available.' }}</span>
|
||||||
<DocsSynopsis :sections="sections.synopsis" />
|
<DocsSynopsis :sections="sections.synopsis" />
|
||||||
<DocsHeader v-if="types.length > 0" kind="types" label="Types" />
|
<DocsHeader v-if="types.length > 0" kind="types" label="Types" />
|
||||||
<DocsList
|
<DocsList
|
||||||
:items="{ kind: 'Types', items: types }"
|
:items="{ kind: 'Types', items: types }"
|
||||||
@linkClicked="emit('update:selectedEntry', $event)"
|
@linkClicked="historyStack.record($event)"
|
||||||
/>
|
/>
|
||||||
<DocsHeader v-if="constructors.length > 0" kind="methods" label="Constructors" />
|
<DocsHeader v-if="constructors.length > 0" kind="methods" label="Constructors" />
|
||||||
<DocsList
|
<DocsList
|
||||||
:items="{ kind: 'Constructors', items: constructors }"
|
:items="{ kind: 'Constructors', items: constructors }"
|
||||||
@linkClicked="emit('update:selectedEntry', $event)"
|
@linkClicked="historyStack.record($event)"
|
||||||
/>
|
/>
|
||||||
<DocsHeader v-if="methods.length > 0" kind="methods" label="Methods" />
|
<DocsHeader v-if="methods.length > 0" kind="methods" label="Methods" />
|
||||||
<DocsList
|
<DocsList
|
||||||
:items="{ kind: 'Methods', items: methods }"
|
:items="{ kind: 'Methods', items: methods }"
|
||||||
@linkClicked="emit('update:selectedEntry', $event)"
|
@linkClicked="historyStack.record($event)"
|
||||||
/>
|
/>
|
||||||
<DocsHeader v-if="sections.examples.length > 0" kind="examples" label="Examples" />
|
<DocsHeader v-if="sections.examples.length > 0" kind="examples" label="Examples" />
|
||||||
<DocsExamples :examples="sections.examples" />
|
<DocsExamples :examples="sections.examples" />
|
||||||
@ -89,11 +179,18 @@ const types = computed<TypeDocs[]>(() => {
|
|||||||
line-height: 160%;
|
line-height: 160%;
|
||||||
color: var(--enso-docs-text-color);
|
color: var(--enso-docs-text-color);
|
||||||
background-color: var(--enso-docs-background-color);
|
background-color: var(--enso-docs-background-color);
|
||||||
padding: 8px 12px 4px 8px;
|
padding: 4px 12px 4px 4px;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
clip-path: inset(0 0 4px 0);
|
clip-path: inset(0 0 4px 0);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
margin: 4px 0 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sections {
|
.sections {
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import SvgIcon from '@/components/SvgIcon.vue'
|
||||||
|
import type { Icon } from '@/util/iconName'
|
||||||
|
|
||||||
|
const props = defineProps<{ text: string; icon?: Icon | undefined }>()
|
||||||
|
const emit = defineEmits<{ click: [] }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="Breadcrumb">
|
||||||
|
<SvgIcon v-if="props.icon" :name="props.icon || ''" />
|
||||||
|
<span @pointerdown="emit('click')" v-text="props.text"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.Breadcrumb {
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
height: 24px;
|
||||||
|
padding: 1px 0px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: 20px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,97 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import SvgIcon from '@/components/SvgIcon.vue'
|
||||||
|
import type { Icon } from '@/util/iconName'
|
||||||
|
|
||||||
|
import Breadcrumb from '@/components/DocumentationPanel/DocsBreadcrumb.vue'
|
||||||
|
|
||||||
|
export interface Item {
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
breadcrumbs: Item[]
|
||||||
|
color: string
|
||||||
|
icon: Icon
|
||||||
|
canGoForward: boolean
|
||||||
|
canGoBackward: boolean
|
||||||
|
}>()
|
||||||
|
const emit = defineEmits<{ click: [index: number]; backward: []; forward: [] }>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shrink first and middle elements in the breacrumbs, keeping the original size of others.
|
||||||
|
*/
|
||||||
|
function shrinkFactor(index: number): number {
|
||||||
|
const middle = Math.floor(props.breadcrumbs.length / 2)
|
||||||
|
return index === middle || index === 0 ? 100 : 0
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="Breadcrumbs" :style="{ 'background-color': color }">
|
||||||
|
<div class="breadcrumbs-controls">
|
||||||
|
<SvgIcon
|
||||||
|
name="arrow_left"
|
||||||
|
draggable="false"
|
||||||
|
:class="['icon', 'button', 'arrow', { inactive: !props.canGoBackward }]"
|
||||||
|
@pointerdown="emit('backward')"
|
||||||
|
/>
|
||||||
|
<SvgIcon
|
||||||
|
name="arrow_right"
|
||||||
|
draggable="false"
|
||||||
|
:class="['icon', 'button', 'arrow', { inactive: !props.canGoForward }]"
|
||||||
|
@pointerdown="emit('forward')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<TransitionGroup name="breadcrumbs">
|
||||||
|
<template v-for="(breadcrumb, index) in props.breadcrumbs" :key="[index, breadcrumb.label]">
|
||||||
|
<SvgIcon v-if="index > 0" name="arrow_right_head_only" class="arrow" />
|
||||||
|
<Breadcrumb
|
||||||
|
:text="breadcrumb.label"
|
||||||
|
:icon="index === props.breadcrumbs.length - 1 ? props.icon : undefined"
|
||||||
|
:style="{ 'flex-shrink': shrinkFactor(index) }"
|
||||||
|
@click="emit('click', index)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</TransitionGroup>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.Breadcrumbs {
|
||||||
|
display: flex;
|
||||||
|
height: 32px;
|
||||||
|
padding: 8px 10px 8px 8px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
border-radius: 16px;
|
||||||
|
transition: background-color 0.5s;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumbs-controls {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inactive {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
color: white;
|
||||||
|
transition: opacity 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumbs-move,
|
||||||
|
.breadcrumbs-enter-active,
|
||||||
|
.breadcrumb-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumbs-leave-active {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumbs-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FunctionDocs, TypeDocs } from '@/components/DocumentationPanel/ir'
|
import type { FunctionDocs, TypeDocs } from '@/components/DocumentationPanel/ir'
|
||||||
import type { Doc } from '@/util/docParser'
|
import type { Doc } from '@/util/docParser'
|
||||||
|
import { qnSplit } from '@/util/qualifiedName'
|
||||||
import type { SuggestionEntryArgument, SuggestionId } from 'shared/languageServerTypes/suggestions'
|
import type { SuggestionEntryArgument, SuggestionId } from 'shared/languageServerTypes/suggestions'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ function firstParagraph(synopsis: Doc.Section[]): string | undefined {
|
|||||||
function argumentsList(args: SuggestionEntryArgument[]): string {
|
function argumentsList(args: SuggestionEntryArgument[]): string {
|
||||||
return args
|
return args
|
||||||
.map((arg) => {
|
.map((arg) => {
|
||||||
const defaultValue = arg.defaultValue ? ` = ${arg.defaultValue}` : ''
|
const defaultValue = arg.defaultValue ? `=${arg.defaultValue}` : ''
|
||||||
return `${arg.name}${defaultValue}`
|
return `${arg.name}${defaultValue}`
|
||||||
})
|
})
|
||||||
.join(', ')
|
.join(', ')
|
||||||
@ -51,7 +52,7 @@ const annotations = computed<Array<string | undefined>>(() => {
|
|||||||
:class="['link', props.items.kind]"
|
:class="['link', props.items.kind]"
|
||||||
@pointerdown.stop.prevent="emit('linkClicked', item.id)"
|
@pointerdown.stop.prevent="emit('linkClicked', item.id)"
|
||||||
>
|
>
|
||||||
<span class="entryName">{{ item.name }}</span>
|
<span class="entryName">{{ qnSplit(item.name)[1] }}</span>
|
||||||
<span class="arguments">{{ ' ' + argumentsList(item.arguments) }}</span>
|
<span class="arguments">{{ ' ' + argumentsList(item.arguments) }}</span>
|
||||||
</a>
|
</a>
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
|
97
app/gui2/src/components/DocumentationPanel/history.ts
Normal file
97
app/gui2/src/components/DocumentationPanel/history.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import type { SuggestionId } from '@/stores/suggestionDatabase/entry'
|
||||||
|
import type { ComputedRef, Ref } from 'vue'
|
||||||
|
import { computed, reactive, ref } from 'vue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple stack for going forward and backward through the history of visited documentation pages
|
||||||
|
*/
|
||||||
|
export class HistoryStack {
|
||||||
|
private stack: SuggestionId[]
|
||||||
|
private index: Ref<number>
|
||||||
|
public current: ComputedRef<SuggestionId | undefined>
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.stack = reactive([])
|
||||||
|
this.index = ref(0)
|
||||||
|
this.current = computed(() => this.stack[this.index.value] ?? undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
public reset(current: SuggestionId) {
|
||||||
|
this.stack.length = 0
|
||||||
|
this.stack.push(current)
|
||||||
|
this.index.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public record(id: SuggestionId) {
|
||||||
|
this.stack.splice(this.index.value + 1)
|
||||||
|
this.stack.push(id)
|
||||||
|
this.index.value = this.stack.length - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public forward() {
|
||||||
|
if (this.canGoForward()) {
|
||||||
|
this.index.value += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public backward() {
|
||||||
|
if (this.canGoBackward()) {
|
||||||
|
this.index.value -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public canGoBackward(): boolean {
|
||||||
|
return this.index.value > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public canGoForward(): boolean {
|
||||||
|
return this.index.value < this.stack.length - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.vitest) {
|
||||||
|
const { test, expect } = import.meta.vitest
|
||||||
|
const ID_1 = 10
|
||||||
|
const ID_2 = 20
|
||||||
|
const ID_3 = 30
|
||||||
|
|
||||||
|
test('HistoryStack basic operations', () => {
|
||||||
|
const stack = new HistoryStack()
|
||||||
|
expect(stack.current.value).toBeUndefined()
|
||||||
|
expect(stack.canGoBackward()).toBeFalsy()
|
||||||
|
expect(stack.canGoForward()).toBeFalsy()
|
||||||
|
|
||||||
|
stack.reset(ID_1)
|
||||||
|
expect(stack.current.value).toStrictEqual(ID_1)
|
||||||
|
stack.forward()
|
||||||
|
expect(stack.current.value).toStrictEqual(ID_1)
|
||||||
|
expect(stack.canGoBackward()).toBeFalsy()
|
||||||
|
expect(stack.canGoForward()).toBeFalsy()
|
||||||
|
|
||||||
|
stack.record(ID_2)
|
||||||
|
expect(stack.current.value).toStrictEqual(ID_2)
|
||||||
|
expect(stack.canGoBackward()).toBeTruthy()
|
||||||
|
expect(stack.canGoForward()).toBeFalsy()
|
||||||
|
|
||||||
|
stack.backward()
|
||||||
|
expect(stack.current.value).toStrictEqual(ID_1)
|
||||||
|
expect(stack.canGoBackward()).toBeFalsy()
|
||||||
|
expect(stack.canGoForward()).toBeTruthy()
|
||||||
|
stack.backward()
|
||||||
|
expect(stack.current.value).toStrictEqual(ID_1)
|
||||||
|
stack.forward()
|
||||||
|
expect(stack.current.value).toStrictEqual(ID_2)
|
||||||
|
expect(stack.canGoForward()).toBeFalsy()
|
||||||
|
stack.forward()
|
||||||
|
expect(stack.current.value).toStrictEqual(ID_2)
|
||||||
|
|
||||||
|
stack.backward()
|
||||||
|
expect(stack.current.value).toStrictEqual(ID_1)
|
||||||
|
stack.record(ID_3)
|
||||||
|
expect(stack.current.value).toStrictEqual(ID_3)
|
||||||
|
stack.forward()
|
||||||
|
expect(stack.current.value).toStrictEqual(ID_3)
|
||||||
|
stack.backward()
|
||||||
|
expect(stack.current.value).toStrictEqual(ID_1)
|
||||||
|
})
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import type { SuggestionDb } from '@/stores/suggestionDatabase'
|
import type { SuggestionDb } from '@/stores/suggestionDatabase'
|
||||||
import type { SuggestionEntry, SuggestionId } from '@/stores/suggestionDatabase/entry'
|
import type { SuggestionEntry, SuggestionId } from '@/stores/suggestionDatabase/entry'
|
||||||
import { SuggestionKind } from '@/stores/suggestionDatabase/entry'
|
import { SuggestionKind, entryQn } from '@/stores/suggestionDatabase/entry'
|
||||||
import type { Doc } from '@/util/docParser'
|
import type { Doc } from '@/util/docParser'
|
||||||
|
import type { QualifiedName } from '@/util/qualifiedName'
|
||||||
import type { SuggestionEntryArgument } from 'shared/languageServerTypes/suggestions'
|
import type { SuggestionEntryArgument } from 'shared/languageServerTypes/suggestions'
|
||||||
|
|
||||||
// === Types ===
|
// === Types ===
|
||||||
@ -20,7 +21,7 @@ export interface Placeholder {
|
|||||||
export interface FunctionDocs {
|
export interface FunctionDocs {
|
||||||
kind: 'Function'
|
kind: 'Function'
|
||||||
id: SuggestionId
|
id: SuggestionId
|
||||||
name: string
|
name: QualifiedName
|
||||||
arguments: SuggestionEntryArgument[]
|
arguments: SuggestionEntryArgument[]
|
||||||
sections: Sections
|
sections: Sections
|
||||||
}
|
}
|
||||||
@ -28,7 +29,7 @@ export interface FunctionDocs {
|
|||||||
export interface TypeDocs {
|
export interface TypeDocs {
|
||||||
kind: 'Type'
|
kind: 'Type'
|
||||||
id: SuggestionId
|
id: SuggestionId
|
||||||
name: string
|
name: QualifiedName
|
||||||
arguments: SuggestionEntryArgument[]
|
arguments: SuggestionEntryArgument[]
|
||||||
sections: Sections
|
sections: Sections
|
||||||
methods: FunctionDocs[]
|
methods: FunctionDocs[]
|
||||||
@ -38,7 +39,7 @@ export interface TypeDocs {
|
|||||||
export interface ModuleDocs {
|
export interface ModuleDocs {
|
||||||
kind: 'Module'
|
kind: 'Module'
|
||||||
id: SuggestionId
|
id: SuggestionId
|
||||||
name: string
|
name: QualifiedName
|
||||||
sections: Sections
|
sections: Sections
|
||||||
types: TypeDocs[]
|
types: TypeDocs[]
|
||||||
methods: FunctionDocs[]
|
methods: FunctionDocs[]
|
||||||
@ -47,7 +48,7 @@ export interface ModuleDocs {
|
|||||||
export interface LocalDocs {
|
export interface LocalDocs {
|
||||||
kind: 'Local'
|
kind: 'Local'
|
||||||
id: SuggestionId
|
id: SuggestionId
|
||||||
name: string
|
name: QualifiedName
|
||||||
sections: Sections
|
sections: Sections
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +145,7 @@ type DocsHandle = (db: SuggestionDb, entry: SuggestionEntry, id: SuggestionId) =
|
|||||||
const handleFunction: DocsHandle = (_db, entry, id) => ({
|
const handleFunction: DocsHandle = (_db, entry, id) => ({
|
||||||
kind: 'Function',
|
kind: 'Function',
|
||||||
id,
|
id,
|
||||||
name: entry.name,
|
name: entryQn(entry),
|
||||||
arguments: entry.arguments,
|
arguments: entry.arguments,
|
||||||
sections: filterSections(entry.documentation),
|
sections: filterSections(entry.documentation),
|
||||||
})
|
})
|
||||||
@ -156,13 +157,13 @@ const handleDocumentation: Record<SuggestionKind, DocsHandle> = {
|
|||||||
[SuggestionKind.Local]: (_db, entry, id) => ({
|
[SuggestionKind.Local]: (_db, entry, id) => ({
|
||||||
kind: 'Local',
|
kind: 'Local',
|
||||||
id,
|
id,
|
||||||
name: entry.name,
|
name: entryQn(entry),
|
||||||
sections: filterSections(entry.documentation),
|
sections: filterSections(entry.documentation),
|
||||||
}),
|
}),
|
||||||
[SuggestionKind.Type]: (db, entry, id) => ({
|
[SuggestionKind.Type]: (db, entry, id) => ({
|
||||||
kind: 'Type',
|
kind: 'Type',
|
||||||
id,
|
id,
|
||||||
name: entry.name,
|
name: entryQn(entry),
|
||||||
arguments: entry.arguments,
|
arguments: entry.arguments,
|
||||||
sections: filterSections(entry.documentation),
|
sections: filterSections(entry.documentation),
|
||||||
methods: asFunctionDocs(getChildren(db, id, SuggestionKind.Method)),
|
methods: asFunctionDocs(getChildren(db, id, SuggestionKind.Method)),
|
||||||
@ -171,7 +172,7 @@ const handleDocumentation: Record<SuggestionKind, DocsHandle> = {
|
|||||||
[SuggestionKind.Module]: (db, entry, id) => ({
|
[SuggestionKind.Module]: (db, entry, id) => ({
|
||||||
kind: 'Module',
|
kind: 'Module',
|
||||||
id,
|
id,
|
||||||
name: entry.name,
|
name: entryQn(entry),
|
||||||
sections: filterSections(entry.documentation),
|
sections: filterSections(entry.documentation),
|
||||||
types: asTypeDocs(getChildren(db, id, SuggestionKind.Type)),
|
types: asTypeDocs(getChildren(db, id, SuggestionKind.Type)),
|
||||||
methods: asFunctionDocs(getChildren(db, id, SuggestionKind.Method)),
|
methods: asFunctionDocs(getChildren(db, id, SuggestionKind.Method)),
|
||||||
|
@ -63,6 +63,18 @@ export function qnJoin(left: QualifiedName, right: QualifiedName): QualifiedName
|
|||||||
return `${left}.${right}` as QualifiedName
|
return `${left}.${right}` as QualifiedName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function qnSegments(name: QualifiedName): Identifier[] {
|
||||||
|
return name.split('.').map((segment) => segment as Identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function qnSlice(
|
||||||
|
name: QualifiedName,
|
||||||
|
start?: number | undefined,
|
||||||
|
end?: number | undefined,
|
||||||
|
): Result<QualifiedName> {
|
||||||
|
return tryQualifiedName(qnSegments(name).slice(start, end).join('.'))
|
||||||
|
}
|
||||||
|
|
||||||
/** Checks if given full qualified name is considered a top element of some project.
|
/** Checks if given full qualified name is considered a top element of some project.
|
||||||
*
|
*
|
||||||
* The fully qualified names consists of namespace, project name, and then a path (possibly empty).
|
* The fully qualified names consists of namespace, project name, and then a path (possibly empty).
|
||||||
|
Loading…
Reference in New Issue
Block a user