mirror of
https://github.com/enso-org/enso.git
synced 2024-11-26 08:52:58 +03:00
Refactor Input
class to be composable (#8244)
Part of #7926 I found myself wanting to use graph store in the `Input` class. As I learned about composables recently, I decided to refactor the class into a composable, to be more vue-like. Putting it in a separate PR because [other task may wanting to do the same](8066) Also contains some preparations for my actual implementation.
This commit is contained in:
parent
579d83a450
commit
3b12b6e17b
@ -2,7 +2,6 @@
|
|||||||
import { componentBrowserBindings } from '@/bindings'
|
import { componentBrowserBindings } from '@/bindings'
|
||||||
import { makeComponentList, type Component } from '@/components/ComponentBrowser/component'
|
import { makeComponentList, type Component } from '@/components/ComponentBrowser/component'
|
||||||
import { Filtering } from '@/components/ComponentBrowser/filtering'
|
import { Filtering } from '@/components/ComponentBrowser/filtering'
|
||||||
import { Input } from '@/components/ComponentBrowser/input'
|
|
||||||
import { default as DocumentationPanel } from '@/components/DocumentationPanel.vue'
|
import { default as DocumentationPanel } from '@/components/DocumentationPanel.vue'
|
||||||
import SvgIcon from '@/components/SvgIcon.vue'
|
import SvgIcon from '@/components/SvgIcon.vue'
|
||||||
import ToggleIcon from '@/components/ToggleIcon.vue'
|
import ToggleIcon from '@/components/ToggleIcon.vue'
|
||||||
@ -18,6 +17,7 @@ import { Vec2 } from '@/util/vec2'
|
|||||||
import type { SuggestionId } from 'shared/languageServerTypes/suggestions'
|
import type { SuggestionId } from 'shared/languageServerTypes/suggestions'
|
||||||
import type { ContentRange } from 'shared/yjsModel.ts'
|
import type { ContentRange } from 'shared/yjsModel.ts'
|
||||||
import { computed, nextTick, onMounted, ref, watch, type Ref } from 'vue'
|
import { computed, nextTick, onMounted, ref, watch, type Ref } from 'vue'
|
||||||
|
import { useComponentBrowserInput } from './ComponentBrowser/input'
|
||||||
|
|
||||||
const ITEM_SIZE = 32
|
const ITEM_SIZE = 32
|
||||||
const TOP_BAR_HEIGHT = 32
|
const TOP_BAR_HEIGHT = 32
|
||||||
@ -46,6 +46,7 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const projectStore = useProjectStore()
|
const projectStore = useProjectStore()
|
||||||
|
const suggestionDbStore = useSuggestionDbStore()
|
||||||
|
|
||||||
// === Position ===
|
// === Position ===
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ const transform = computed(() => {
|
|||||||
|
|
||||||
const cbRoot = ref<HTMLElement>()
|
const cbRoot = ref<HTMLElement>()
|
||||||
const inputField = ref<HTMLInputElement>()
|
const inputField = ref<HTMLInputElement>()
|
||||||
const input = new Input()
|
const input = useComponentBrowserInput()
|
||||||
const filterFlags = ref({ showUnstable: false, showLocal: false })
|
const filterFlags = ref({ showUnstable: false, showLocal: false })
|
||||||
|
|
||||||
const currentFiltering = computed(() => {
|
const currentFiltering = computed(() => {
|
||||||
@ -128,8 +129,6 @@ function handleDefocus(e: FocusEvent) {
|
|||||||
|
|
||||||
// === Components List and Positions ===
|
// === Components List and Positions ===
|
||||||
|
|
||||||
const suggestionDbStore = useSuggestionDbStore()
|
|
||||||
|
|
||||||
const components = computed(() => {
|
const components = computed(() => {
|
||||||
return makeComponentList(suggestionDbStore.entries, currentFiltering.value)
|
return makeComponentList(suggestionDbStore.entries, currentFiltering.value)
|
||||||
})
|
})
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
} from '@/stores/suggestionDatabase/entry'
|
} from '@/stores/suggestionDatabase/entry'
|
||||||
import { readAstSpan } from '@/util/ast'
|
import { readAstSpan } from '@/util/ast'
|
||||||
import { expect, test } from 'vitest'
|
import { expect, test } from 'vitest'
|
||||||
import { Input } from '../input'
|
import { useComponentBrowserInput } from '../input'
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
['', 0, { type: 'insert', position: 0 }, {}],
|
['', 0, { type: 'insert', position: 0 }, {}],
|
||||||
@ -40,6 +40,20 @@ test.each([
|
|||||||
],
|
],
|
||||||
['2 +', 3, { type: 'insert', position: 3, oprApp: ['2', '+', null] }, {}],
|
['2 +', 3, { type: 'insert', position: 3, oprApp: ['2', '+', null] }, {}],
|
||||||
['2 + 3', 5, { type: 'changeLiteral', literal: '3' }, { pattern: '3' }],
|
['2 + 3', 5, { type: 'changeLiteral', literal: '3' }, { pattern: '3' }],
|
||||||
|
// TODO[ao] test cases for #7926
|
||||||
|
// [
|
||||||
|
// 'operator1.',
|
||||||
|
// 10,
|
||||||
|
// { type: 'insert', position: 10, oprApp: ['operator1', '.', null] },
|
||||||
|
// { selfType: 'Standard.Base.Number' },
|
||||||
|
// ],
|
||||||
|
// [
|
||||||
|
// 'operator2.',
|
||||||
|
// 10,
|
||||||
|
// { type: 'insert', position: 10, oprApp: ['operator2', '.', null] },
|
||||||
|
// // No self type, as the operator2 local is from another module
|
||||||
|
// { qualifiedNamePattern: 'operator2' },
|
||||||
|
// ],
|
||||||
])(
|
])(
|
||||||
"Input context and filtering, when content is '%s' and cursor at %i",
|
"Input context and filtering, when content is '%s' and cursor at %i",
|
||||||
(
|
(
|
||||||
@ -52,9 +66,16 @@ test.each([
|
|||||||
identifier?: string
|
identifier?: string
|
||||||
literal?: string
|
literal?: string
|
||||||
},
|
},
|
||||||
expFiltering: { pattern?: string; qualifiedNamePattern?: string },
|
expFiltering: { pattern?: string; qualifiedNamePattern?: string; selfType?: string },
|
||||||
) => {
|
) => {
|
||||||
const input = new Input()
|
// TODO[ao] See above commented cases for #7926
|
||||||
|
// const db = SuggestionDb.mock([
|
||||||
|
// makeLocal('local.Project', 'operator1', 'Standard.Base.Number'),
|
||||||
|
// makeLocal('local.Project.Another_Module', 'operator2', 'Standard.Base.Text'),
|
||||||
|
// makeType('local.Project.operator1'),
|
||||||
|
// makeLocal('local.Project', 'operator3', 'Standard.Base.Text'),
|
||||||
|
// ])
|
||||||
|
const input = useComponentBrowserInput()
|
||||||
input.code.value = code
|
input.code.value = code
|
||||||
input.selection.value = { start: cursorPos, end: cursorPos }
|
input.selection.value = { start: cursorPos, end: cursorPos }
|
||||||
const context = input.context.value
|
const context = input.context.value
|
||||||
@ -78,6 +99,7 @@ test.each([
|
|||||||
}
|
}
|
||||||
expect(filter.pattern).toStrictEqual(expFiltering.pattern)
|
expect(filter.pattern).toStrictEqual(expFiltering.pattern)
|
||||||
expect(filter.qualifiedNamePattern).toStrictEqual(expFiltering.qualifiedNamePattern)
|
expect(filter.qualifiedNamePattern).toStrictEqual(expFiltering.qualifiedNamePattern)
|
||||||
|
expect(filter.selfType).toStrictEqual(expFiltering.selfType)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -195,7 +217,7 @@ test.each([
|
|||||||
({ code, cursorPos, suggestion, expected, expectedCursorPos }) => {
|
({ code, cursorPos, suggestion, expected, expectedCursorPos }) => {
|
||||||
cursorPos = cursorPos ?? code.length
|
cursorPos = cursorPos ?? code.length
|
||||||
expectedCursorPos = expectedCursorPos ?? expected.length
|
expectedCursorPos = expectedCursorPos ?? expected.length
|
||||||
const input = new Input()
|
const input = useComponentBrowserInput()
|
||||||
input.code.value = code
|
input.code.value = code
|
||||||
input.selection.value = { start: cursorPos, end: cursorPos }
|
input.selection.value = { start: cursorPos, end: cursorPos }
|
||||||
input.applySuggestion(suggestion)
|
input.applySuggestion(suggestion)
|
||||||
|
@ -13,10 +13,11 @@ import {
|
|||||||
qnLastSegment,
|
qnLastSegment,
|
||||||
qnParent,
|
qnParent,
|
||||||
qnSplit,
|
qnSplit,
|
||||||
|
tryIdentifier,
|
||||||
tryQualifiedName,
|
tryQualifiedName,
|
||||||
type QualifiedName,
|
type QualifiedName,
|
||||||
} from '@/util/qualifiedName'
|
} from '@/util/qualifiedName'
|
||||||
import { computed, ref, type ComputedRef, type Ref } from 'vue'
|
import { computed, ref, type ComputedRef } from 'vue'
|
||||||
|
|
||||||
/** Input's editing context.
|
/** Input's editing context.
|
||||||
*
|
*
|
||||||
@ -43,79 +44,70 @@ export type EditingContext =
|
|||||||
| { type: 'changeLiteral'; literal: Ast.Tree.TextLiteral | Ast.Tree.Number }
|
| { type: 'changeLiteral'; literal: Ast.Tree.TextLiteral | Ast.Tree.Number }
|
||||||
|
|
||||||
/** Component Browser Input Data */
|
/** Component Browser Input Data */
|
||||||
export class Input {
|
export function useComponentBrowserInput() {
|
||||||
/** The current input's text (code). */
|
const code = ref('')
|
||||||
readonly code: Ref<string>
|
const selection = ref({ start: 0, end: 0 })
|
||||||
/** The current selection (or cursor position if start is equal to end). */
|
|
||||||
readonly selection: Ref<{ start: number; end: number }>
|
|
||||||
/** The editing context deduced from code and selection */
|
|
||||||
readonly context: ComputedRef<EditingContext>
|
|
||||||
/** The filter deduced from code and selection. */
|
|
||||||
readonly filter: ComputedRef<Filter>
|
|
||||||
|
|
||||||
constructor() {
|
const context: ComputedRef<EditingContext> = computed(() => {
|
||||||
this.code = ref('')
|
const input = code.value
|
||||||
this.selection = ref({ start: 0, end: 0 })
|
const cursorPosition = selection.value.start
|
||||||
|
if (cursorPosition === 0) return { type: 'insert', position: 0 }
|
||||||
|
const editedPart = cursorPosition - 1
|
||||||
|
const inputAst = parseEnso(input)
|
||||||
|
const editedAst = astContainingChar(editedPart, inputAst).values()
|
||||||
|
const leaf = editedAst.next()
|
||||||
|
if (leaf.done) return { type: 'insert', position: cursorPosition }
|
||||||
|
switch (leaf.value.type) {
|
||||||
|
case Ast.Tree.Type.Ident:
|
||||||
|
return {
|
||||||
|
type: 'changeIdentifier',
|
||||||
|
identifier: leaf.value,
|
||||||
|
...readOprApp(editedAst.next(), leaf.value),
|
||||||
|
}
|
||||||
|
case Ast.Tree.Type.TextLiteral:
|
||||||
|
case Ast.Tree.Type.Number:
|
||||||
|
return { type: 'changeLiteral', literal: leaf.value }
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
type: 'insert',
|
||||||
|
position: cursorPosition,
|
||||||
|
...readOprApp(leaf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
this.context = computed(() => {
|
// Filter deduced from the access (`.` operator) chain written by user.
|
||||||
const input = this.code.value
|
const accessChainFilter: ComputedRef<Filter> = computed(() => {
|
||||||
const cursorPosition = this.selection.value.start
|
const ctx = context.value
|
||||||
if (cursorPosition === 0) return { type: 'insert', position: 0 }
|
if (ctx.type === 'changeLiteral') return {}
|
||||||
const editedPart = cursorPosition - 1
|
if (ctx.oprApp == null || ctx.oprApp.lhs == null) return {}
|
||||||
const inputAst = parseEnso(input)
|
const opr = ctx.oprApp.lastOpr()
|
||||||
const editedAst = astContainingChar(editedPart, inputAst).values()
|
const input = code.value
|
||||||
const leaf = editedAst.next()
|
if (opr == null || !opr.ok || readTokenSpan(opr.value, input) !== '.') return {}
|
||||||
if (leaf.done) return { type: 'insert', position: cursorPosition }
|
const selfType = pathAsSelfType(ctx.oprApp, input)
|
||||||
switch (leaf.value.type) {
|
if (selfType != null) return { selfType }
|
||||||
case Ast.Tree.Type.Ident:
|
const qn = pathAsQualifiedName(ctx.oprApp, input)
|
||||||
return {
|
if (qn != null) return { qualifiedNamePattern: qn }
|
||||||
type: 'changeIdentifier',
|
return {}
|
||||||
identifier: leaf.value,
|
})
|
||||||
...Input.readOprApp(editedAst.next(), input, leaf.value),
|
|
||||||
}
|
|
||||||
case Ast.Tree.Type.TextLiteral:
|
|
||||||
case Ast.Tree.Type.Number:
|
|
||||||
return { type: 'changeLiteral', literal: leaf.value }
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
type: 'insert',
|
|
||||||
position: cursorPosition,
|
|
||||||
...Input.readOprApp(leaf, input),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const qualifiedNameFilter: ComputedRef<Filter> = computed(() => {
|
const filter = computed(() => {
|
||||||
const code = this.code.value
|
const input = code.value
|
||||||
const ctx = this.context.value
|
const ctx = context.value
|
||||||
if (ctx.type === 'changeLiteral') return {}
|
const filter = { ...accessChainFilter.value }
|
||||||
if (ctx.oprApp == null || ctx.oprApp.lhs == null) return {}
|
if (ctx.type === 'changeIdentifier') {
|
||||||
const opr = ctx.oprApp.lastOpr()
|
const start =
|
||||||
if (opr == null || !opr.ok || readTokenSpan(opr.value, code) !== '.') return {}
|
ctx.identifier.whitespaceStartInCodeParsed + ctx.identifier.whitespaceLengthInCodeParsed
|
||||||
const qn = Input.pathAsQualifiedName(ctx.oprApp, code)
|
const end = selection.value.end
|
||||||
if (qn != null) return { qualifiedNamePattern: qn }
|
filter.pattern = input.substring(start, end)
|
||||||
else return {}
|
} else if (ctx.type === 'changeLiteral') {
|
||||||
})
|
filter.pattern = readAstSpan(ctx.literal, input)
|
||||||
|
}
|
||||||
|
return filter
|
||||||
|
})
|
||||||
|
|
||||||
this.filter = computed(() => {
|
function readOprApp(
|
||||||
const code = this.code.value
|
|
||||||
const ctx = this.context.value
|
|
||||||
const filter = { ...qualifiedNameFilter.value }
|
|
||||||
if (ctx.type === 'changeIdentifier') {
|
|
||||||
const start =
|
|
||||||
ctx.identifier.whitespaceStartInCodeParsed + ctx.identifier.whitespaceLengthInCodeParsed
|
|
||||||
const end = this.selection.value.end
|
|
||||||
filter.pattern = code.substring(start, end)
|
|
||||||
} else if (ctx.type === 'changeLiteral') {
|
|
||||||
filter.pattern = readAstSpan(ctx.literal, code)
|
|
||||||
}
|
|
||||||
return filter
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readOprApp(
|
|
||||||
leafParent: IteratorResult<Ast.Tree>,
|
leafParent: IteratorResult<Ast.Tree>,
|
||||||
code: string,
|
|
||||||
editedAst?: Ast.Tree,
|
editedAst?: Ast.Tree,
|
||||||
): {
|
): {
|
||||||
oprApp?: GeneralOprApp
|
oprApp?: GeneralOprApp
|
||||||
@ -140,9 +132,18 @@ export class Input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static pathAsQualifiedName(accessOpr: GeneralOprApp, code: string): QualifiedName | null {
|
function pathAsSelfType(accessOpr: GeneralOprApp, inputCode: string): QualifiedName | null {
|
||||||
const operandsAsIdents = Input.qnIdentifiers(accessOpr, code)
|
if (accessOpr.lhs == null) return null
|
||||||
const segments = operandsAsIdents.map((ident) => readAstSpan(ident, code))
|
if (accessOpr.lhs.type !== Ast.Tree.Type.Ident) return null
|
||||||
|
if (accessOpr.apps.length > 1) return null
|
||||||
|
const _ident = tryIdentifier(readAstSpan(accessOpr.lhs, inputCode))
|
||||||
|
// TODO[ao]: #7926 add implementation here
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function pathAsQualifiedName(accessOpr: GeneralOprApp, inputCode: string): QualifiedName | null {
|
||||||
|
const operandsAsIdents = qnIdentifiers(accessOpr, inputCode)
|
||||||
|
const segments = operandsAsIdents.map((ident) => readAstSpan(ident, inputCode))
|
||||||
const rawQn = segments.join('.')
|
const rawQn = segments.join('.')
|
||||||
const qn = tryQualifiedName(rawQn)
|
const qn = tryQualifiedName(rawQn)
|
||||||
return qn.ok ? qn.value : null
|
return qn.ok ? qn.value : null
|
||||||
@ -155,18 +156,20 @@ export class Input {
|
|||||||
* @param code The code from which `opr` was generated.
|
* @param code The code from which `opr` was generated.
|
||||||
* @returns If all path segments are identifiers, return them
|
* @returns If all path segments are identifiers, return them
|
||||||
*/
|
*/
|
||||||
private static qnIdentifiers(opr: GeneralOprApp, code: string): Ast.Tree.Ident[] {
|
function qnIdentifiers(opr: GeneralOprApp, inputCode: string): Ast.Tree.Ident[] {
|
||||||
const operandsAsIdents = Array.from(opr.operandsOfLeftAssocOprChain(code, '.'), (operand) =>
|
const operandsAsIdents = Array.from(
|
||||||
operand?.type === 'ast' && operand.ast.type === Ast.Tree.Type.Ident ? operand.ast : null,
|
opr.operandsOfLeftAssocOprChain(inputCode, '.'),
|
||||||
|
(operand) =>
|
||||||
|
operand?.type === 'ast' && operand.ast.type === Ast.Tree.Type.Ident ? operand.ast : null,
|
||||||
).slice(0, -1)
|
).slice(0, -1)
|
||||||
if (operandsAsIdents.some((optIdent) => optIdent == null)) return []
|
if (operandsAsIdents.some((optIdent) => optIdent == null)) return []
|
||||||
else return operandsAsIdents as Ast.Tree.Ident[]
|
else return operandsAsIdents as Ast.Tree.Ident[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Apply given suggested entry to the input. */
|
/** Apply given suggested entry to the input. */
|
||||||
applySuggestion(entry: SuggestionEntry) {
|
function applySuggestion(entry: SuggestionEntry) {
|
||||||
const oldCode = this.code.value
|
const oldCode = code.value
|
||||||
const changes = Array.from(this.inputChangesAfterApplying(entry)).reverse()
|
const changes = Array.from(inputChangesAfterApplying(entry)).reverse()
|
||||||
const newCodeUpToLastChange = changes.reduce(
|
const newCodeUpToLastChange = changes.reduce(
|
||||||
(builder, change) => {
|
(builder, change) => {
|
||||||
const oldCodeFragment = oldCode.substring(builder.oldCodeIndex, change.range[0])
|
const oldCodeFragment = oldCode.substring(builder.oldCodeIndex, change.range[0])
|
||||||
@ -183,11 +186,11 @@ export class Input {
|
|||||||
!isModule && (firstCharAfter == null || /^[a-zA-Z0-9_]$/.test(firstCharAfter))
|
!isModule && (firstCharAfter == null || /^[a-zA-Z0-9_]$/.test(firstCharAfter))
|
||||||
const shouldMoveCursor = !isModule
|
const shouldMoveCursor = !isModule
|
||||||
const newCursorPos = newCodeUpToLastChange.code.length + (shouldMoveCursor ? 1 : 0)
|
const newCursorPos = newCodeUpToLastChange.code.length + (shouldMoveCursor ? 1 : 0)
|
||||||
this.code.value =
|
code.value =
|
||||||
newCodeUpToLastChange.code +
|
newCodeUpToLastChange.code +
|
||||||
(shouldInsertSpace ? ' ' : '') +
|
(shouldInsertSpace ? ' ' : '') +
|
||||||
oldCode.substring(newCodeUpToLastChange.oldCodeIndex)
|
oldCode.substring(newCodeUpToLastChange.oldCodeIndex)
|
||||||
this.selection.value = { start: newCursorPos, end: newCursorPos }
|
selection.value = { start: newCursorPos, end: newCursorPos }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return all input changes resulting from applying given suggestion.
|
/** Return all input changes resulting from applying given suggestion.
|
||||||
@ -195,11 +198,11 @@ export class Input {
|
|||||||
* @returns The changes, starting from the rightmost. The `start` and `end` parameters refer
|
* @returns The changes, starting from the rightmost. The `start` and `end` parameters refer
|
||||||
* to indices of "old" input content.
|
* to indices of "old" input content.
|
||||||
*/
|
*/
|
||||||
private *inputChangesAfterApplying(
|
function* inputChangesAfterApplying(
|
||||||
entry: SuggestionEntry,
|
entry: SuggestionEntry,
|
||||||
): Generator<{ range: [number, number]; str: string }> {
|
): Generator<{ range: [number, number]; str: string }> {
|
||||||
const ctx = this.context.value
|
const ctx = context.value
|
||||||
const str = this.codeToBeInserted(entry)
|
const str = codeToBeInserted(entry)
|
||||||
switch (ctx.type) {
|
switch (ctx.type) {
|
||||||
case 'insert': {
|
case 'insert': {
|
||||||
yield { range: [ctx.position, ctx.position], str }
|
yield { range: [ctx.position, ctx.position], str }
|
||||||
@ -214,18 +217,18 @@ export class Input {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield* this.qnChangesAfterApplying(entry)
|
yield* qnChangesAfterApplying(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
private codeToBeInserted(entry: SuggestionEntry): string {
|
function codeToBeInserted(entry: SuggestionEntry): string {
|
||||||
const ctx = this.context.value
|
const ctx = context.value
|
||||||
const opr = ctx.type !== 'changeLiteral' && ctx.oprApp != null ? ctx.oprApp.lastOpr() : null
|
const opr = ctx.type !== 'changeLiteral' && ctx.oprApp != null ? ctx.oprApp.lastOpr() : null
|
||||||
const oprAppSpacing =
|
const oprAppSpacing =
|
||||||
ctx.type === 'insert' && opr != null && opr.ok && opr.value.whitespaceLengthInCodeBuffer > 0
|
ctx.type === 'insert' && opr != null && opr.ok && opr.value.whitespaceLengthInCodeBuffer > 0
|
||||||
? ' '.repeat(opr.value.whitespaceLengthInCodeBuffer)
|
? ' '.repeat(opr.value.whitespaceLengthInCodeBuffer)
|
||||||
: ''
|
: ''
|
||||||
const extendingAccessOprChain =
|
const extendingAccessOprChain =
|
||||||
opr != null && opr.ok && readTokenSpan(opr.value, this.code.value) === '.'
|
opr != null && opr.ok && readTokenSpan(opr.value, code.value) === '.'
|
||||||
// Modules are special case, as we want to encourage user to continue writing path.
|
// Modules are special case, as we want to encourage user to continue writing path.
|
||||||
if (entry.kind === SuggestionKind.Module) {
|
if (entry.kind === SuggestionKind.Module) {
|
||||||
if (extendingAccessOprChain) return `${oprAppSpacing}${entry.name}${oprAppSpacing}.`
|
if (extendingAccessOprChain) return `${oprAppSpacing}${entry.name}${oprAppSpacing}.`
|
||||||
@ -245,14 +248,14 @@ export class Input {
|
|||||||
/** All changes to the qualified name already written by the user.
|
/** All changes to the qualified name already written by the user.
|
||||||
*
|
*
|
||||||
* See `inputChangesAfterApplying`. */
|
* See `inputChangesAfterApplying`. */
|
||||||
private *qnChangesAfterApplying(
|
function* qnChangesAfterApplying(
|
||||||
entry: SuggestionEntry,
|
entry: SuggestionEntry,
|
||||||
): Generator<{ range: [number, number]; str: string }> {
|
): Generator<{ range: [number, number]; str: string }> {
|
||||||
if (entry.selfType != null) return []
|
if (entry.selfType != null) return []
|
||||||
if (entry.kind === SuggestionKind.Local || entry.kind === SuggestionKind.Function) return []
|
if (entry.kind === SuggestionKind.Local || entry.kind === SuggestionKind.Function) return []
|
||||||
if (this.context.value.type === 'changeLiteral') return []
|
if (context.value.type === 'changeLiteral') return []
|
||||||
if (this.context.value.oprApp == null) return []
|
if (context.value.oprApp == null) return []
|
||||||
const writtenQn = Input.qnIdentifiers(this.context.value.oprApp, this.code.value).reverse()
|
const writtenQn = qnIdentifiers(context.value.oprApp, code.value).reverse()
|
||||||
|
|
||||||
let containingQn =
|
let containingQn =
|
||||||
entry.kind === SuggestionKind.Module
|
entry.kind === SuggestionKind.Module
|
||||||
@ -265,4 +268,17 @@ export class Input {
|
|||||||
containingQn = parent
|
containingQn = parent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
/** The current input's text (code). */
|
||||||
|
code,
|
||||||
|
/** The current selection (or cursor position if start is equal to end). */
|
||||||
|
selection,
|
||||||
|
/** The editing context deduced from code and selection */
|
||||||
|
context,
|
||||||
|
/** The filter deduced from code and selection. */
|
||||||
|
filter,
|
||||||
|
/** Apply given suggested entry to the input. */
|
||||||
|
applySuggestion,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,9 @@ import {
|
|||||||
qnLastSegment,
|
qnLastSegment,
|
||||||
qnParent,
|
qnParent,
|
||||||
qnSplit,
|
qnSplit,
|
||||||
tryQualifiedName,
|
|
||||||
type Identifier,
|
type Identifier,
|
||||||
type QualifiedName,
|
type QualifiedName,
|
||||||
} from '@/util/qualifiedName'
|
} from '@/util/qualifiedName'
|
||||||
import { unwrap } from '@/util/result'
|
|
||||||
import type {
|
import type {
|
||||||
SuggestionEntryArgument,
|
SuggestionEntryArgument,
|
||||||
SuggestionEntryScope,
|
SuggestionEntryScope,
|
||||||
@ -77,7 +75,7 @@ export function entryQn(entry: SuggestionEntry): QualifiedName {
|
|||||||
} else if (entry.memberOf) {
|
} else if (entry.memberOf) {
|
||||||
return qnJoin(entry.memberOf, entry.name)
|
return qnJoin(entry.memberOf, entry.name)
|
||||||
} else {
|
} else {
|
||||||
return qnJoin(entry.definedIn, unwrap(tryQualifiedName(entry.name)))
|
return qnJoin(entry.definedIn, entry.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user