mirror of
https://github.com/microsoft/vscode.git
synced 2026-06-06 23:56:32 +01:00
cleanup, refactor, and add doc comments
This commit is contained in:
+130
@@ -0,0 +1,130 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Range } from '../../../../../../../../../editor/common/core/range.js';
|
||||
import { IMarkdownString } from '../../../../../../../../../base/common/htmlContent.js';
|
||||
import { BaseToken } from '../../../../../../../../../editor/common/codecs/baseToken.js';
|
||||
import { ModelDecorationOptions } from '../../../../../../../../../editor/common/model/textModel.js';
|
||||
import { IModelDecorationsChangeAccessor, TrackedRangeStickiness } from '../../../../../../../../../editor/common/model.js';
|
||||
import { IColorTheme, ICssStyleCollector } from '../../../../../../../../../platform/theme/common/themeService.js';
|
||||
|
||||
/**
|
||||
* Base class for all editor decorations.
|
||||
*/
|
||||
export abstract class DecorationBase<
|
||||
TPromptToken extends BaseToken,
|
||||
TCssClassName extends string = string,
|
||||
> {
|
||||
/**
|
||||
* Description of the decoration.
|
||||
*/
|
||||
protected abstract get description(): string;
|
||||
|
||||
/**
|
||||
* Default CSS class name of the decoration.
|
||||
*/
|
||||
protected abstract get className(): TCssClassName;
|
||||
|
||||
/**
|
||||
* Inline CSS class name of the decoration.
|
||||
*/
|
||||
protected abstract get inlineClassName(): TCssClassName;
|
||||
|
||||
/**
|
||||
* Indicates whether the decoration spans the whole line(s).
|
||||
*/
|
||||
protected get isWholeLine(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hover message of the decoration.
|
||||
*/
|
||||
protected get hoverMessage(): IMarkdownString | IMarkdownString[] | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of editor decoration it was registered with.
|
||||
*/
|
||||
public readonly id: string;
|
||||
|
||||
constructor(
|
||||
accessor: Pick<IModelDecorationsChangeAccessor, 'addDecoration'>,
|
||||
protected readonly token: TPromptToken,
|
||||
) {
|
||||
this.id = accessor.addDecoration(this.range, this.decorationOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Range of the decoration.
|
||||
*/
|
||||
public get range(): Range {
|
||||
return this.token.range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders (updates) the decoration in the editor.
|
||||
*/
|
||||
public render(
|
||||
accessor: Pick<IModelDecorationsChangeAccessor, 'changeDecoration' | 'changeDecorationOptions'>,
|
||||
): this {
|
||||
accessor.changeDecorationOptions(
|
||||
this.id,
|
||||
this.decorationOptions,
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes associated editor decoration(s).
|
||||
*/
|
||||
public remove(
|
||||
accessor: Pick<IModelDecorationsChangeAccessor, 'removeDecoration'>,
|
||||
): this {
|
||||
accessor.removeDecoration(this.id);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get editor decoration options for this decorator.
|
||||
*/
|
||||
private get decorationOptions(): ModelDecorationOptions {
|
||||
return ModelDecorationOptions.createDynamic({
|
||||
description: this.description,
|
||||
hoverMessage: this.hoverMessage,
|
||||
className: this.className,
|
||||
inlineClassName: this.inlineClassName,
|
||||
isWholeLine: this.isWholeLine,
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
shouldFillLineOnLineBreak: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of a generic decoration class.
|
||||
*/
|
||||
export type TDecorationClass<TPromptToken extends BaseToken = BaseToken> = {
|
||||
new(
|
||||
accessor: Pick<IModelDecorationsChangeAccessor, 'addDecoration'>,
|
||||
token: TPromptToken,
|
||||
): DecorationBase<TPromptToken>;
|
||||
|
||||
/**
|
||||
* Register CSS styles for the decoration.
|
||||
*/
|
||||
registerStyles(
|
||||
theme: IColorTheme,
|
||||
collector: ICssStyleCollector,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Whether the decoration class handles the provided token.
|
||||
*/
|
||||
handles(token: BaseToken): token is TPromptToken;
|
||||
};
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from '../../../../../../../../../nls.js';
|
||||
import { ReactiveDecorationBase } from './reactiveDecorationBase.js';
|
||||
import { Color, RGBA } from '../../../../../../../../../base/common/color.js';
|
||||
import { BaseToken } from '../../../../../../../../../editor/common/codecs/baseToken.js';
|
||||
import { registerColor } from '../../../../../../../../../platform/theme/common/colorUtils.js';
|
||||
import { contrastBorder } from '../../../../../../../../../platform/theme/common/colorRegistry.js';
|
||||
import { IColorTheme, ICssStyleCollector } from '../../../../../../../../../platform/theme/common/themeService.js';
|
||||
import { FrontMatterHeader } from '../../../../../../../../../editor/common/codecs/markdownExtensionsCodec/tokens/frontMatterHeader.js';
|
||||
|
||||
/**
|
||||
* Decoration CSS class names.
|
||||
*/
|
||||
export enum FrontMatterCssClassNames {
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
frontMatterHeader = 'prompt-front-matter-header',
|
||||
frontMatterHeaderInlineInactive = 'prompt-front-matter-header-inline-inactive',
|
||||
frontMatterHeaderInlineActive = 'prompt-front-matter-header-inline-active',
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
export class FrontMatterHeaderDecoration extends ReactiveDecorationBase<FrontMatterHeader, FrontMatterCssClassNames> {
|
||||
protected override get isWholeLine(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override get description(): string {
|
||||
return 'Front Matter header editor decoration.';
|
||||
}
|
||||
|
||||
protected override get className(): FrontMatterCssClassNames.frontMatterHeader {
|
||||
return FrontMatterCssClassNames.frontMatterHeader;
|
||||
}
|
||||
|
||||
protected override get inlineClassName(): FrontMatterCssClassNames.frontMatterHeaderInlineActive | FrontMatterCssClassNames.frontMatterHeaderInlineInactive {
|
||||
return (this.active)
|
||||
? FrontMatterCssClassNames.frontMatterHeaderInlineActive
|
||||
: FrontMatterCssClassNames.frontMatterHeaderInlineInactive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether current decoration class can decorate provided token.
|
||||
*/
|
||||
public static handles(
|
||||
token: BaseToken,
|
||||
): token is FrontMatterHeader {
|
||||
return token instanceof FrontMatterHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register CSS styles of the decoration.
|
||||
*/
|
||||
public static registerStyles(
|
||||
theme: IColorTheme,
|
||||
collector: ICssStyleCollector,
|
||||
) {
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
const frontMatterHeaderBackgroundColor = registerColor(
|
||||
'chat.prompt.frontMatterBackground',
|
||||
{ dark: new Color(new RGBA(0, 0, 0, 0.20)), light: new Color(new RGBA(0, 0, 0, 0.10)), hcDark: contrastBorder, hcLight: contrastBorder, },
|
||||
localize('chat.prompt.frontMatterBackground', "background color of a Front Matter header block."),
|
||||
);
|
||||
|
||||
const styles = [];
|
||||
styles.push(
|
||||
`background-color: ${theme.getColor(frontMatterHeaderBackgroundColor)};`,
|
||||
);
|
||||
|
||||
const frontMatterHeaderCssSelector = `.monaco-editor .${FrontMatterCssClassNames.frontMatterHeader}`;
|
||||
collector.addRule(
|
||||
`${frontMatterHeaderCssSelector} { ${styles.join(' ')} }`,
|
||||
);
|
||||
|
||||
const inlineInactiveStyles = [];
|
||||
inlineInactiveStyles.push('color: var(--vscode-disabledForeground);');
|
||||
|
||||
const inlineActiveStyles = [];
|
||||
inlineActiveStyles.push('color: var(--vscode-foreground);');
|
||||
|
||||
const frontMatterHeaderInlineActiveCssSelector = `.monaco-editor .${FrontMatterCssClassNames.frontMatterHeaderInlineActive}`;
|
||||
collector.addRule(
|
||||
`${frontMatterHeaderInlineActiveCssSelector} { ${inlineActiveStyles.join(' ')} }`,
|
||||
);
|
||||
|
||||
const frontMatterHeaderInlineInactiveCssSelector = `.monaco-editor .${FrontMatterCssClassNames.frontMatterHeaderInlineInactive}`;
|
||||
collector.addRule(
|
||||
`${frontMatterHeaderInlineInactiveCssSelector} { ${inlineInactiveStyles.join(' ')} }`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DecorationBase } from './decorationBase.js';
|
||||
import { Position } from '../../../../../../../../../editor/common/core/position.js';
|
||||
import { BaseToken } from '../../../../../../../../../editor/common/codecs/baseToken.js';
|
||||
import { IModelDecorationsChangeAccessor } from '../../../../../../../../../editor/common/model.js';
|
||||
|
||||
/**
|
||||
* Base class for all reactive editor decorations. A reactive decoration
|
||||
* is a decoration that can change its appearance based on current cursor
|
||||
* position in the editor, hence can "react" to the user's actions.
|
||||
*/
|
||||
export abstract class ReactiveDecorationBase<
|
||||
TPromptToken extends BaseToken,
|
||||
TCssClassName extends string = string,
|
||||
> extends DecorationBase<TPromptToken, TCssClassName> {
|
||||
/**
|
||||
* Whether the decoration has changed since the last {@link render}.
|
||||
*/
|
||||
public get changed(): boolean {
|
||||
return this.didChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current position of cursor in the editor.
|
||||
*/
|
||||
private cursorPosition?: Position | null;
|
||||
|
||||
/**
|
||||
* Private field for the {@link changed} property.
|
||||
*/
|
||||
private didChange = true;
|
||||
|
||||
constructor(
|
||||
accessor: Pick<IModelDecorationsChangeAccessor, 'addDecoration'>,
|
||||
token: TPromptToken,
|
||||
) {
|
||||
super(accessor, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether cursor is currently inside the decoration range.
|
||||
*/
|
||||
protected get active(): boolean {
|
||||
if (!this.cursorPosition) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// when cursor is at the end of a range, the range considered to
|
||||
// not contain the position, but we want to include it
|
||||
const atEnd = (this.range.endLineNumber === this.cursorPosition.lineNumber)
|
||||
&& (this.range.endColumn === this.cursorPosition.column);
|
||||
|
||||
return atEnd || this.range.containsPosition(this.cursorPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cursor position and update {@link changed} property if needed.
|
||||
*/
|
||||
public setCursorPosition(position: Position | null | undefined): this is { readonly changed: true } {
|
||||
if (this.cursorPosition === position) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.cursorPosition && position) {
|
||||
if (this.cursorPosition.equals(position)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const wasActive = this.active;
|
||||
this.cursorPosition = position;
|
||||
this.didChange = (wasActive !== this.active);
|
||||
|
||||
return this.didChange;
|
||||
}
|
||||
|
||||
public override render(
|
||||
accessor: Pick<IModelDecorationsChangeAccessor, 'changeDecoration' | 'changeDecorationOptions'>,
|
||||
): this {
|
||||
if (this.didChange === false) {
|
||||
return this;
|
||||
}
|
||||
|
||||
super.render(accessor);
|
||||
this.didChange = false;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type for a decorator with {@link ReactiveDecorationBase.changed changed} property set to `true`.
|
||||
*/
|
||||
export type TChangedDecorator = ReactiveDecorationBase<BaseToken> & { readonly changed: true };
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IPromptsService } from '../../../service/types.js';
|
||||
import { ProviderInstanceBase } from '../providerInstanceBase.js';
|
||||
import { toDisposable } from '../../../../../../../../base/common/lifecycle.js';
|
||||
import { DecorationBase, TDecorationClass } from './decorations/decorationBase.js';
|
||||
import { FrontMatterHeaderDecoration } from './decorations/frontMatterDecoration.js';
|
||||
import { BaseToken } from '../../../../../../../../editor/common/codecs/baseToken.js';
|
||||
import { IPromptFileEditor, ProviderInstanceManagerBase } from '../providerInstanceManagerBase.js';
|
||||
import { ReactiveDecorationBase, TChangedDecorator } from './decorations/reactiveDecorationBase.js';
|
||||
import { registerThemingParticipant } from '../../../../../../../../platform/theme/common/themeService.js';
|
||||
import { FrontMatterHeader } from '../../../../../../../../editor/common/codecs/markdownExtensionsCodec/tokens/frontMatterHeader.js';
|
||||
|
||||
/**
|
||||
* Prompt tokens that are decorated by this provider.
|
||||
*/
|
||||
type TDecoratedToken = FrontMatterHeader;
|
||||
|
||||
/**
|
||||
* List of all supported decorations.
|
||||
*/
|
||||
const SUPPORTED_DECORATIONS: readonly TDecorationClass<TDecoratedToken>[] = Object.freeze([
|
||||
FrontMatterHeaderDecoration,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Prompt syntax decorations provider for text models.
|
||||
*/
|
||||
export class TextModelPromptDecorator extends ProviderInstanceBase {
|
||||
/**
|
||||
* Currently active decorations.
|
||||
*/
|
||||
private readonly decorations: DecorationBase<BaseToken>[] = [];
|
||||
|
||||
constructor(
|
||||
editor: IPromptFileEditor,
|
||||
@IPromptsService promptsService: IPromptsService,
|
||||
) {
|
||||
super(editor, promptsService);
|
||||
|
||||
this.watchCursorPosition();
|
||||
}
|
||||
|
||||
// TODO: @legomushroom - update existing decorations instead of recreating them every time
|
||||
protected override async onPromptParserUpdate(): Promise<this> {
|
||||
await this.parser.allSettled();
|
||||
|
||||
this.removeAllDecorations();
|
||||
this.addDecorations(this.parser.tokens);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch editor cursor position and update reactive decorations accordingly.
|
||||
*/
|
||||
private watchCursorPosition(): this {
|
||||
const interval = setInterval(() => {
|
||||
const cursorPosition = this.editor.getPosition();
|
||||
|
||||
const changedDecorations: TChangedDecorator[] = [];
|
||||
for (const decoration of this.decorations) {
|
||||
if ((decoration instanceof ReactiveDecorationBase) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (decoration.setCursorPosition(cursorPosition) === true) {
|
||||
changedDecorations.push(decoration);
|
||||
}
|
||||
}
|
||||
|
||||
if (changedDecorations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.changeEditorDecorations(changedDecorations);
|
||||
}, 25);
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
clearInterval(interval);
|
||||
}));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private changeEditorDecorations(
|
||||
decorations: readonly TChangedDecorator[],
|
||||
): this {
|
||||
this.editor.changeDecorations((accessor) => {
|
||||
for (const decoration of decorations) {
|
||||
decoration.render(accessor);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a decorations for all prompt tokens.
|
||||
*/
|
||||
private addDecorations(
|
||||
tokens: readonly BaseToken[],
|
||||
): this {
|
||||
if (tokens.length === 0) {
|
||||
return this;
|
||||
}
|
||||
|
||||
this.editor.changeDecorations((accessor) => {
|
||||
for (const token of tokens) {
|
||||
for (const Decoration of SUPPORTED_DECORATIONS) {
|
||||
if (Decoration.handles(token) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.decorations.push(
|
||||
new Decoration(accessor, token),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all existing decorations.
|
||||
*/
|
||||
private removeAllDecorations(): this {
|
||||
if (this.decorations.length === 0) {
|
||||
return this;
|
||||
}
|
||||
|
||||
this.editor.changeDecorations((accessor) => {
|
||||
for (const decoration of this.decorations) {
|
||||
decoration.remove(accessor);
|
||||
}
|
||||
|
||||
this.decorations.splice(0);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
this.removeAllDecorations();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this object.
|
||||
*/
|
||||
public override toString() {
|
||||
return `text-model-prompt-decorator:${this.model.uri.path}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register CSS styles of the supported decorations.
|
||||
*/
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
for (const Decoration of SUPPORTED_DECORATIONS) {
|
||||
Decoration.registerStyles(theme, collector);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Provider for prompt syntax decorators on text models.
|
||||
*/
|
||||
export class PromptDecorationsProviderInstanceManager extends ProviderInstanceManagerBase<TextModelPromptDecorator> {
|
||||
protected override get InstanceClass() {
|
||||
return TextModelPromptDecorator;
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -6,10 +6,10 @@
|
||||
import { PromptLinkProvider } from './promptLinkProvider.js';
|
||||
import { isWindows } from '../../../../../../../base/common/platform.js';
|
||||
import { PromptPathAutocompletion } from './promptPathAutocompletion.js';
|
||||
import { PromptDecoratorsInstanceManager } from './textModelPromptDecorator.js';
|
||||
import { Registry } from '../../../../../../../platform/registry/common/platform.js';
|
||||
import { LifecyclePhase } from '../../../../../../services/lifecycle/common/lifecycle.js';
|
||||
import { PromptLinkDiagnosticsInstanceManager } from './promptLinkDiagnosticsProvider.js';
|
||||
import { PromptDecorationsProviderInstanceManager } from './decorationsProvider/promptDecorationsProvider.js';
|
||||
import { BrandedService } from '../../../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IWorkbenchContributionsRegistry, Extensions, IWorkbenchContribution } from '../../../../../../common/contributions.js';
|
||||
|
||||
@@ -18,8 +18,8 @@ import { IWorkbenchContributionsRegistry, Extensions, IWorkbenchContribution } f
|
||||
*/
|
||||
export const registerReusablePromptLanguageFeatures = () => {
|
||||
registerContribution(PromptLinkProvider);
|
||||
registerContribution(PromptDecoratorsInstanceManager);
|
||||
registerContribution(PromptLinkDiagnosticsInstanceManager);
|
||||
registerContribution(PromptDecorationsProviderInstanceManager);
|
||||
|
||||
/**
|
||||
* We restrict this provider to `Unix` machines for now because of
|
||||
@@ -42,5 +42,4 @@ const registerContribution = <TServices extends BrandedService[]>(
|
||||
) => {
|
||||
Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench)
|
||||
.registerWorkbenchContribution(contribution, LifecyclePhase.Eventually);
|
||||
|
||||
};
|
||||
|
||||
-505
@@ -1,505 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from '../../../../../../../nls.js';
|
||||
import { IPromptsService } from '../../service/types.js';
|
||||
import { ProviderInstanceBase } from './providerInstanceBase.js';
|
||||
import { chatSlashCommandBackground } from '../../../chatColors.js';
|
||||
import { Color, RGBA } from '../../../../../../../base/common/color.js';
|
||||
import { assertNever } from '../../../../../../../base/common/assert.js';
|
||||
import { toDisposable } from '../../../../../../../base/common/lifecycle.js';
|
||||
import { Position } from '../../../../../../../editor/common/core/position.js';
|
||||
import { Range, IRange } from '../../../../../../../editor/common/core/range.js';
|
||||
import { IMarkdownString } from '../../../../../../../base/common/htmlContent.js';
|
||||
import { BaseToken } from '../../../../../../../editor/common/codecs/baseToken.js';
|
||||
import { ModelDecorationOptions } from '../../../../../../../editor/common/model/textModel.js';
|
||||
import { IPromptFileEditor, ProviderInstanceManagerBase } from './providerInstanceManagerBase.js';
|
||||
import { contrastBorder, registerColor } from '../../../../../../../platform/theme/common/colorRegistry.js';
|
||||
import { IModelDecorationsChangeAccessor, TrackedRangeStickiness } from '../../../../../../../editor/common/model.js';
|
||||
import { FrontMatterHeader } from '../../../../../../../editor/common/codecs/markdownExtensionsCodec/tokens/frontMatterHeader.js';
|
||||
import { IColorTheme, ICssStyleCollector, registerThemingParticipant } from '../../../../../../../platform/theme/common/themeService.js';
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom - list
|
||||
* - add active/inactive logic for front matter header
|
||||
*/
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
abstract class Decoration<TPromptToken extends BaseToken, TCssClassName extends string = string> {
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
protected abstract get description(): string;
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
protected abstract get className(): TCssClassName;
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
protected abstract get inlineClassName(): TCssClassName;
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
protected get isWholeLine(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
protected get hoverMessage(): IMarkdownString | IMarkdownString[] | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
public readonly id: string;
|
||||
|
||||
constructor(
|
||||
accessor: Pick<IModelDecorationsChangeAccessor, 'addDecoration'>,
|
||||
protected readonly token: TPromptToken,
|
||||
) {
|
||||
this.id = accessor.addDecoration(this.range, this.decorationOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Range of the decoration.
|
||||
*/
|
||||
public get range(): Range {
|
||||
return this.token.range;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
public render(
|
||||
accessor: Pick<IModelDecorationsChangeAccessor, 'changeDecoration' | 'changeDecorationOptions'>,
|
||||
): this {
|
||||
accessor.changeDecorationOptions(
|
||||
this.id,
|
||||
this.decorationOptions,
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
protected get decorationOptions(): ModelDecorationOptions {
|
||||
return ModelDecorationOptions.createDynamic({
|
||||
description: this.description,
|
||||
hoverMessage: this.hoverMessage,
|
||||
className: this.className,
|
||||
inlineClassName: this.inlineClassName,
|
||||
isWholeLine: this.isWholeLine,
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
shouldFillLineOnLineBreak: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
abstract class ReactiveDecoration<TPromptToken extends BaseToken, TCssClassName extends string = string> extends Decoration<TPromptToken, TCssClassName> {
|
||||
/**
|
||||
* Whether the decoration has changed since the last {@link render}.
|
||||
*/
|
||||
public get changed(): boolean {
|
||||
return this.didChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
private cursorPosition?: Position | null;
|
||||
|
||||
/**
|
||||
* Private field for the {@link changed} property.
|
||||
*/
|
||||
private didChange = true;
|
||||
|
||||
constructor(
|
||||
accessor: Pick<IModelDecorationsChangeAccessor, 'addDecoration'>,
|
||||
token: TPromptToken,
|
||||
) {
|
||||
super(accessor, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether cursor is currently inside the decoration range.
|
||||
*/
|
||||
protected get active(): boolean {
|
||||
if (!this.cursorPosition) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// when cursor is at the end of a range, the range considered to
|
||||
// not contain the position, but we want to include it
|
||||
const atEnd = (this.range.endLineNumber === this.cursorPosition.lineNumber)
|
||||
&& (this.range.endColumn === this.cursorPosition.column);
|
||||
|
||||
return atEnd || this.range.containsPosition(this.cursorPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
public setCursorPosition(position: Position | null | undefined): boolean {
|
||||
if (this.cursorPosition === position) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.cursorPosition && position) {
|
||||
if (this.cursorPosition.equals(position)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const wasActive = this.active;
|
||||
this.cursorPosition = position;
|
||||
this.didChange = (wasActive !== this.active);
|
||||
|
||||
return this.didChange;
|
||||
}
|
||||
|
||||
public override render(
|
||||
accessor: Pick<IModelDecorationsChangeAccessor, 'changeDecoration' | 'changeDecorationOptions'>,
|
||||
): this {
|
||||
if (this.didChange === false) {
|
||||
return this;
|
||||
}
|
||||
|
||||
super.render(accessor);
|
||||
this.didChange = false;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoration CSS class names.
|
||||
*/
|
||||
export enum FrontMatterCssClassNames {
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
frontMatterHeader = 'prompt-front-matter-header',
|
||||
frontMatterHeaderInlineInactive = 'prompt-front-matter-header-inline-inactive',
|
||||
frontMatterHeaderInlineActive = 'prompt-front-matter-header-inline-active',
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
class FrontMatterHeaderDecoration extends ReactiveDecoration<FrontMatterHeader, FrontMatterCssClassNames> {
|
||||
protected override get isWholeLine(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override get description(): string {
|
||||
return 'Front Matter header decoration.';
|
||||
}
|
||||
|
||||
protected override get className(): FrontMatterCssClassNames.frontMatterHeader {
|
||||
return FrontMatterCssClassNames.frontMatterHeader;
|
||||
}
|
||||
|
||||
protected override get inlineClassName(): FrontMatterCssClassNames.frontMatterHeaderInlineActive | FrontMatterCssClassNames.frontMatterHeaderInlineInactive {
|
||||
return (this.active)
|
||||
? FrontMatterCssClassNames.frontMatterHeaderInlineActive
|
||||
: FrontMatterCssClassNames.frontMatterHeaderInlineInactive;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoration object.
|
||||
*/
|
||||
export interface ITextModelDecoration {
|
||||
/**
|
||||
* Range of the decoration.
|
||||
*/
|
||||
range: IRange;
|
||||
|
||||
/**
|
||||
* Associated decoration options.
|
||||
*/
|
||||
options: ModelDecorationOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoration CSS class names.
|
||||
*/
|
||||
export enum DecorationClassNames {
|
||||
/**
|
||||
* CSS class name for `default` prompt syntax decoration.
|
||||
*/
|
||||
default = 'prompt-decoration',
|
||||
|
||||
/**
|
||||
* CSS class name for `reference` prompt syntax decoration.
|
||||
*/
|
||||
reference = 'prompt-reference',
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoration CSS class name modifiers.
|
||||
*/
|
||||
export enum DecorationClassNameModifiers {
|
||||
/**
|
||||
* CSS class name for `warning` modifier.
|
||||
*/
|
||||
warning = 'squiggly-warning',
|
||||
|
||||
/**
|
||||
* CSS class name for `error` modifier.
|
||||
*/
|
||||
error = 'squiggly-error', // TODO: @legomushroom - use "markers" instead?
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
type TDecoratedToken = FrontMatterHeader;
|
||||
|
||||
/**
|
||||
* Prompt syntax decorations provider for text models.
|
||||
*/
|
||||
export class TextModelPromptDecorator extends ProviderInstanceBase {
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
private readonly decorations: Decoration<BaseToken>[] = [];
|
||||
|
||||
constructor(
|
||||
editor: IPromptFileEditor,
|
||||
@IPromptsService promptsService: IPromptsService,
|
||||
) {
|
||||
super(editor, promptsService);
|
||||
|
||||
this.watchCursorPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the prompt parser update event.
|
||||
*/
|
||||
// TODO: @legomushroom - update existing decorations instead of recreating them every time
|
||||
protected override async onPromptParserUpdate(): Promise<this> {
|
||||
await this.parser.allSettled();
|
||||
|
||||
this.removeAllDecorations();
|
||||
this.addDecorations(this.parser.tokens);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
private watchCursorPosition(): this {
|
||||
const interval = setInterval(() => {
|
||||
const cursorPosition = this.editor.getPosition();
|
||||
|
||||
const changedDecorations: Decoration<BaseToken>[] = [];
|
||||
for (const decoration of this.decorations) {
|
||||
if ((decoration instanceof ReactiveDecoration) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (decoration.setCursorPosition(cursorPosition) === true) {
|
||||
changedDecorations.push(decoration);
|
||||
}
|
||||
}
|
||||
|
||||
if (changedDecorations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.changeEditorDecorations(changedDecorations);
|
||||
}, 25);
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
clearInterval(interval);
|
||||
}));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
private changeEditorDecorations(
|
||||
decorations: readonly Decoration<BaseToken>[],
|
||||
): this {
|
||||
this.editor.changeDecorations((accessor) => {
|
||||
for (const decoration of decorations) {
|
||||
decoration.render(accessor);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a decorations for all prompt tokens.
|
||||
*/
|
||||
private addDecorations(
|
||||
tokens: readonly BaseToken[],
|
||||
): this {
|
||||
if (tokens.length === 0) {
|
||||
return this;
|
||||
}
|
||||
|
||||
const decoratedTokens: TDecoratedToken[] = [];
|
||||
for (const token of tokens) {
|
||||
if (token instanceof FrontMatterHeader) {
|
||||
decoratedTokens.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
if (decoratedTokens.length === 0) {
|
||||
return this;
|
||||
}
|
||||
|
||||
this.editor.changeDecorations((accessor) => {
|
||||
for (const token of decoratedTokens) {
|
||||
if (token instanceof FrontMatterHeader) {
|
||||
const decoration = new FrontMatterHeaderDecoration(
|
||||
accessor,
|
||||
token,
|
||||
);
|
||||
|
||||
this.decorations.push(decoration);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
assertNever(
|
||||
token,
|
||||
`Unexpected decorated token '${token}'.`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all existing decorations.
|
||||
*/
|
||||
private removeAllDecorations(): this {
|
||||
if (this.decorations.length === 0) {
|
||||
return this;
|
||||
}
|
||||
|
||||
this.editor.changeDecorations((accessor) => {
|
||||
for (const decoration of this.decorations) {
|
||||
accessor.removeDecoration(decoration.id);
|
||||
}
|
||||
|
||||
this.decorations.splice(0);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this object.
|
||||
*/
|
||||
public override toString() {
|
||||
return `text-model-prompt-decorator:${this.model.uri.path}`;
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
this.removeAllDecorations();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register CSS styles.
|
||||
*/
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const styles = ['border-radius: 3px;'];
|
||||
|
||||
const backgroundColor = theme.getColor(chatSlashCommandBackground);
|
||||
if (backgroundColor) {
|
||||
styles.push(`background-color: ${backgroundColor};`);
|
||||
}
|
||||
|
||||
// TODO: @legomushroom
|
||||
// const color = theme.getColor(chatSlashCommandForeground);
|
||||
// if (color) {
|
||||
// styles.push(`color: ${color};`);
|
||||
// }
|
||||
|
||||
const defaultCssSelector = `.monaco-editor .${DecorationClassNames.default}`;
|
||||
collector.addRule(
|
||||
`${defaultCssSelector} { ${styles.join(' ')} }`,
|
||||
);
|
||||
|
||||
registerFrontMatterStyles(theme, collector);
|
||||
});
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
const frontMatterHeaderBackgroundColor = registerColor(
|
||||
'chat.prompt.frontMatterBackground',
|
||||
{ dark: new Color(new RGBA(0, 0, 0, 0.20)), light: new Color(new RGBA(0, 0, 0, 0.10)), hcDark: contrastBorder, hcLight: contrastBorder, },
|
||||
localize('chat.prompt.frontMatterBackground', "background color of a Front Matter header block."),
|
||||
);
|
||||
|
||||
/**
|
||||
* TODO: @legomushroom
|
||||
*/
|
||||
const registerFrontMatterStyles = (
|
||||
theme: IColorTheme,
|
||||
collector: ICssStyleCollector,
|
||||
) => {
|
||||
const styles = [];
|
||||
styles.push(
|
||||
`background-color: ${theme.getColor(frontMatterHeaderBackgroundColor)};`,
|
||||
);
|
||||
|
||||
const frontMatterHeaderCssSelector = `.monaco-editor .${FrontMatterCssClassNames.frontMatterHeader}`;
|
||||
collector.addRule(
|
||||
`${frontMatterHeaderCssSelector} { ${styles.join(' ')} }`,
|
||||
);
|
||||
|
||||
const inlineInactiveStyles = [];
|
||||
inlineInactiveStyles.push('color: var(--vscode-disabledForeground);');
|
||||
|
||||
const inlineActiveStyles = [];
|
||||
inlineActiveStyles.push('color: var(--vscode-foreground);');
|
||||
|
||||
const frontMatterHeaderInlineActiveCssSelector = `.monaco-editor .${FrontMatterCssClassNames.frontMatterHeaderInlineActive}`;
|
||||
collector.addRule(
|
||||
`${frontMatterHeaderInlineActiveCssSelector} { ${inlineActiveStyles.join(' ')} }`,
|
||||
);
|
||||
|
||||
const frontMatterHeaderInlineInactiveCssSelector = `.monaco-editor .${FrontMatterCssClassNames.frontMatterHeaderInlineInactive}`;
|
||||
collector.addRule(
|
||||
`${frontMatterHeaderInlineInactiveCssSelector} { ${inlineInactiveStyles.join(' ')} }`,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Provider for prompt syntax decorators on text models.
|
||||
*/
|
||||
export class PromptDecoratorsInstanceManager extends ProviderInstanceManagerBase<TextModelPromptDecorator> {
|
||||
protected override get InstanceClass() {
|
||||
return TextModelPromptDecorator;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user