mirror of
https://github.com/vadimmelnicuk/meo.git
synced 2026-09-26 18:05:36 +00:00
fix: Cursor file editor associations on AI pending edits
This commit is contained in:
+9
-1
@@ -158,7 +158,15 @@
|
||||
"chat-editing-text-model:**/*.md": "default",
|
||||
"chat-editing-text-model:**/*.markdown": "default",
|
||||
"chat-editing-text-model:**/*.mdx": "default",
|
||||
"chat-editing-text-model:**/*.mdc": "default"
|
||||
"chat-editing-text-model:**/*.mdc": "default",
|
||||
"chat-editing-snapshot-text-model:/**/*.md": "default",
|
||||
"chat-editing-snapshot-text-model:/**/*.markdown": "default",
|
||||
"chat-editing-snapshot-text-model:/**/*.mdx": "default",
|
||||
"chat-editing-snapshot-text-model:/**/*.mdc": "default",
|
||||
"chat-editing-snapshot-text-model:**/*.md": "default",
|
||||
"chat-editing-snapshot-text-model:**/*.markdown": "default",
|
||||
"chat-editing-snapshot-text-model:**/*.mdx": "default",
|
||||
"chat-editing-snapshot-text-model:**/*.mdc": "default"
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { isMarkdownDocumentPath } from '../shared/extensionConfig';
|
||||
import type { AgentReviewOverrideController } from './reviewOverrides';
|
||||
|
||||
const NATIVE_INIT_SETTLE_MS = 50;
|
||||
|
||||
type AgentReviewDocumentPrimerDeps = {
|
||||
getComparableResourceKey: (uri: vscode.Uri) => string | undefined;
|
||||
getOpenTextDocumentForUri: (uri: vscode.Uri) => vscode.TextDocument | undefined;
|
||||
overrides: AgentReviewOverrideController;
|
||||
};
|
||||
|
||||
export class AgentReviewDocumentPrimer {
|
||||
private readonly primedKeys = new Set<string>();
|
||||
private readonly inFlight = new Map<string, Promise<void>>();
|
||||
private primingDepth = 0;
|
||||
|
||||
constructor(private readonly deps: AgentReviewDocumentPrimerDeps) {}
|
||||
|
||||
get isPriming(): boolean {
|
||||
return this.primingDepth > 0;
|
||||
}
|
||||
|
||||
async ensureReady(uri: vscode.Uri, options?: { forceNative?: boolean }): Promise<void> {
|
||||
if (!isMarkdownFileUri(uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = this.getKey(uri);
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
const forceNative = options?.forceNative === true;
|
||||
if (!forceNative && this.primedKeys.has(key) && this.deps.getOpenTextDocumentForUri(uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = this.inFlight.get(key);
|
||||
if (existing) {
|
||||
await existing;
|
||||
if (!forceNative || this.primedKeys.has(key)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const run = this.prime(uri, key, forceNative);
|
||||
this.inFlight.set(key, run);
|
||||
try {
|
||||
await run;
|
||||
} finally {
|
||||
if (this.inFlight.get(key) === run) {
|
||||
this.inFlight.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async prime(uri: vscode.Uri, key: string, forceNative: boolean): Promise<void> {
|
||||
this.primingDepth += 1;
|
||||
const shouldPin = forceNative;
|
||||
if (shouldPin) {
|
||||
this.deps.overrides.pinFile(uri);
|
||||
}
|
||||
|
||||
try {
|
||||
if (shouldPin) {
|
||||
await this.deps.overrides.syncNow();
|
||||
}
|
||||
|
||||
try {
|
||||
await vscode.workspace.openTextDocument(uri);
|
||||
} catch {
|
||||
// Cursor may refuse to sync the document to the extension host.
|
||||
}
|
||||
|
||||
const needsNative = isCursorHost() && forceNative && !this.hasOpenNativeOrCustomTabForKey(key);
|
||||
if (needsNative) {
|
||||
await this.initializeViaDefaultEditor(uri, key);
|
||||
try {
|
||||
await vscode.workspace.openTextDocument(uri);
|
||||
} catch {
|
||||
// Native open can still leave the file unsyncable; the custom editor
|
||||
// can load it from disk instead.
|
||||
}
|
||||
}
|
||||
|
||||
this.primedKeys.add(key);
|
||||
} finally {
|
||||
if (shouldPin) {
|
||||
this.deps.overrides.unpinFile(uri);
|
||||
this.deps.overrides.scheduleSync();
|
||||
}
|
||||
this.primingDepth -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeViaDefaultEditor(uri: vscode.Uri, key: string): Promise<void> {
|
||||
const nativeBefore = this.findNativeTextTabs(key);
|
||||
try {
|
||||
await vscode.commands.executeCommand('vscode.openWith', uri, 'default', {
|
||||
preview: true,
|
||||
preserveFocus: true
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
await delay(NATIVE_INIT_SETTLE_MS);
|
||||
|
||||
const opened = this.findNativeTextTabs(key).filter((tab) => !nativeBefore.includes(tab));
|
||||
if (opened.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await vscode.window.tabGroups.close(opened, true);
|
||||
} catch {
|
||||
// Closing the priming tab is best-effort.
|
||||
}
|
||||
}
|
||||
|
||||
private hasOpenNativeOrCustomTabForKey(key: string): boolean {
|
||||
for (const group of vscode.window.tabGroups.all) {
|
||||
for (const tab of group.tabs) {
|
||||
const input = tab.input;
|
||||
if (!(input instanceof vscode.TabInputText) && !(input instanceof vscode.TabInputCustom)) {
|
||||
continue;
|
||||
}
|
||||
if (this.deps.getComparableResourceKey(input.uri) === key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private findNativeTextTabs(key: string): vscode.Tab[] {
|
||||
const matches: vscode.Tab[] = [];
|
||||
for (const group of vscode.window.tabGroups.all) {
|
||||
for (const tab of group.tabs) {
|
||||
const input = tab.input;
|
||||
if (!(input instanceof vscode.TabInputText)) {
|
||||
continue;
|
||||
}
|
||||
if (this.deps.getComparableResourceKey(input.uri) === key) {
|
||||
matches.push(tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
private getKey(uri: vscode.Uri): string | undefined {
|
||||
return this.deps.getComparableResourceKey(uri) ?? uri.with({ fragment: '' }).toString();
|
||||
}
|
||||
}
|
||||
|
||||
export function isMarkdownFileUri(uri: vscode.Uri): boolean {
|
||||
if (uri.scheme !== 'file') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const targetPath = (uri.path || uri.fsPath || '').toLowerCase();
|
||||
return isMarkdownDocumentPath(targetPath);
|
||||
}
|
||||
|
||||
function isCursorHost(): boolean {
|
||||
return /cursor/i.test(vscode.env.appName);
|
||||
}
|
||||
|
||||
function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
@@ -47,6 +47,27 @@ export function getPreferredCommandUri(value: unknown): vscode.Uri | undefined {
|
||||
return shouldPreferActiveContextUri(commandUri, activeContextUri) ? activeContextUri : commandUri;
|
||||
}
|
||||
|
||||
export function getFileEditorAssociationGlobs(fileUri: vscode.Uri): string[] {
|
||||
const globs = new Set<string>();
|
||||
if (fileUri.scheme && fileUri.path) {
|
||||
// VS Code matches path globs against `${scheme}:${path}` (e.g. file:/Users/me/note.md).
|
||||
globs.add(`${fileUri.scheme}:${fileUri.path}`);
|
||||
}
|
||||
|
||||
const comparableKey = getComparableResourceKey(fileUri);
|
||||
if (comparableKey) {
|
||||
globs.add(`file:${vscode.Uri.file(comparableKey).path}`);
|
||||
}
|
||||
|
||||
const relativeSource = comparableKey ? vscode.Uri.file(comparableKey) : fileUri;
|
||||
const relative = vscode.workspace.asRelativePath(relativeSource, false);
|
||||
if (relative && !path.isAbsolute(relative)) {
|
||||
globs.add(`**/${relative.split(path.sep).join(path.posix.sep)}`);
|
||||
}
|
||||
|
||||
return Array.from(globs);
|
||||
}
|
||||
|
||||
export function getComparableResourceKey(uri: vscode.Uri): string | undefined {
|
||||
if (uri.scheme === 'file') {
|
||||
return path.normalize(uri.fsPath);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as path from 'node:path';
|
||||
import * as vscode from 'vscode';
|
||||
import { getFileEditorAssociationGlobs } from './resourceMatching';
|
||||
import { areAgentReviewTextsEquivalent } from './reviewState';
|
||||
|
||||
const REVIEW_FILE_OVERRIDE_STATE_KEY = 'copilotReviewNativeFileOverrides';
|
||||
@@ -12,12 +12,25 @@ type AgentReviewOverrideDeps = {
|
||||
|
||||
export class AgentReviewOverrideController {
|
||||
private syncTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
private readonly pinnedOverrideKeys = new Set<string>();
|
||||
|
||||
constructor(
|
||||
private readonly context: vscode.ExtensionContext,
|
||||
private readonly deps: AgentReviewOverrideDeps
|
||||
) {}
|
||||
|
||||
pinFile(fileUri: vscode.Uri): void {
|
||||
for (const key of this.getOverridePatterns(fileUri)) {
|
||||
this.pinnedOverrideKeys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
unpinFile(fileUri: vscode.Uri): void {
|
||||
for (const key of this.getOverridePatterns(fileUri)) {
|
||||
this.pinnedOverrideKeys.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
scheduleSync(delayMs = 50): void {
|
||||
if (this.syncTimer) {
|
||||
clearTimeout(this.syncTimer);
|
||||
@@ -52,7 +65,7 @@ export class AgentReviewOverrideController {
|
||||
}
|
||||
|
||||
private collectOverrideKeys(): Set<string> {
|
||||
const keys = new Set<string>();
|
||||
const keys = new Set<string>(this.pinnedOverrideKeys);
|
||||
|
||||
for (const document of vscode.workspace.textDocuments) {
|
||||
if (!this.deps.isLikelyAgentReviewUri(document.uri)) {
|
||||
@@ -69,7 +82,7 @@ export class AgentReviewOverrideController {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const key of this.getOverridePatterns(targetKey)) {
|
||||
for (const key of this.getOverridePatterns(vscode.Uri.file(targetKey))) {
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
@@ -77,12 +90,8 @@ export class AgentReviewOverrideController {
|
||||
return keys;
|
||||
}
|
||||
|
||||
private getOverridePatterns(targetKey: string): string[] {
|
||||
const keys = new Set<string>([targetKey]);
|
||||
const posixKey = targetKey.split(path.sep).join(path.posix.sep);
|
||||
keys.add(posixKey);
|
||||
|
||||
return Array.from(keys);
|
||||
private getOverridePatterns(fileUri: vscode.Uri): string[] {
|
||||
return getFileEditorAssociationGlobs(fileUri);
|
||||
}
|
||||
|
||||
private applyOverrideAssociations(
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export const AGENT_REVIEW_MODEL_SCHEMES = ['chat-editing-text-model'] as const;
|
||||
export const AGENT_REVIEW_MODEL_SCHEMES = [
|
||||
'chat-editing-text-model',
|
||||
'chat-editing-snapshot-text-model'
|
||||
] as const;
|
||||
const agentReviewModelSchemeSet = new Set<string>(AGENT_REVIEW_MODEL_SCHEMES);
|
||||
|
||||
export type LikelyAgentReviewState = {
|
||||
|
||||
+156
-31
@@ -24,6 +24,7 @@ import {
|
||||
resolveWorktreeUriFromGitUri
|
||||
} from './agents/resourceMatching';
|
||||
import { AgentReviewOverrideController } from './agents/reviewOverrides';
|
||||
import { AgentReviewDocumentPrimer, isMarkdownFileUri } from './agents/documentPrimer';
|
||||
import {
|
||||
EXTENSION_CONFIG_SECTION,
|
||||
GIT_CHANGES_GUTTER_LEGACY_SETTING_KEY,
|
||||
@@ -68,6 +69,7 @@ import {
|
||||
resetThemeSettingsToDefault
|
||||
} from './shared/extensionConfig';
|
||||
import { createPanelSessionController, type ExportFormat, type PanelSession } from './extension/panelSession';
|
||||
import { MarkdownCustomDocument } from './extension/markdownCustomDocument';
|
||||
import { serializeThemeSettings, themePresets, type ThemeSettings, validateThemePayload } from './shared/themeDefaults';
|
||||
import {
|
||||
runWithTimedUiTimeout,
|
||||
@@ -155,6 +157,11 @@ export function activate(context: vscode.ExtensionContext): void {
|
||||
getOpenTextDocumentForComparableKey,
|
||||
isLikelyAgentReviewUri
|
||||
});
|
||||
const agentReviewPrimer = new AgentReviewDocumentPrimer({
|
||||
getComparableResourceKey,
|
||||
getOpenTextDocumentForUri,
|
||||
overrides: agentReviewOverrides
|
||||
});
|
||||
void agentReviewOverrides.syncNow();
|
||||
|
||||
const provider = new MarkdownWebviewProvider(context, agentReviewHandoff);
|
||||
@@ -188,11 +195,41 @@ export function activate(context: vscode.ExtensionContext): void {
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidOpenTextDocument((document) => {
|
||||
if (isMarkdownFileUri(document.uri)) {
|
||||
void agentReviewPrimer.ensureReady(document.uri);
|
||||
}
|
||||
if (!isLikelyAgentReviewUri(document.uri)) {
|
||||
return;
|
||||
}
|
||||
void agentReviewOverrides.syncNow();
|
||||
void provider.redirectOpenEditorsForCopilotReview(document.uri);
|
||||
if (!agentReviewPrimer.isPriming) {
|
||||
void provider.redirectOpenEditorsForCopilotReview(document.uri);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidCreateFiles((event) => {
|
||||
for (const uri of event.files) {
|
||||
void agentReviewPrimer.ensureReady(uri);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const markdownFileWatcher = vscode.workspace.createFileSystemWatcher('**/*.{md,markdown,mdx,mdc}');
|
||||
context.subscriptions.push(
|
||||
markdownFileWatcher,
|
||||
markdownFileWatcher.onDidCreate((uri) => {
|
||||
void agentReviewPrimer.ensureReady(uri);
|
||||
}),
|
||||
markdownFileWatcher.onDidChange((uri) => {
|
||||
if (agentReviewHandoff.hasRecentMEOOwnedFileChangeForUri(uri)) {
|
||||
return;
|
||||
}
|
||||
if (getOpenTextDocumentForUri(uri)) {
|
||||
return;
|
||||
}
|
||||
void agentReviewPrimer.ensureReady(uri);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -226,7 +263,7 @@ export function activate(context: vscode.ExtensionContext): void {
|
||||
if (isAgentReviewDocument) {
|
||||
agentReviewOverrides.scheduleSync();
|
||||
}
|
||||
if (!agentReviewHandoff.hasRecentMEOOwnedFileChangeForUri(event.document.uri)) {
|
||||
if (!agentReviewPrimer.isPriming && !agentReviewHandoff.hasRecentMEOOwnedFileChangeForUri(event.document.uri)) {
|
||||
void provider.redirectOpenEditorsForCopilotReview(event.document.uri);
|
||||
}
|
||||
|
||||
@@ -249,6 +286,11 @@ export function activate(context: vscode.ExtensionContext): void {
|
||||
vscode.window.tabGroups.onDidChangeTabs((event) => {
|
||||
agentReviewHandoff.noteRecentTextDiffActivity(event.opened);
|
||||
agentReviewHandoff.noteRecentTextDiffActivity(event.changed);
|
||||
if (!agentReviewPrimer.isPriming) {
|
||||
for (const tab of [...event.opened, ...event.changed]) {
|
||||
void primeDiffTabResources(tab, agentReviewPrimer);
|
||||
}
|
||||
}
|
||||
void agentReviewHandoff.flushPendingMEOtabDedups();
|
||||
if (!agentReviewHandoff.hasPendingDeferredReopens()) {
|
||||
return;
|
||||
@@ -275,11 +317,16 @@ export function activate(context: vscode.ExtensionContext): void {
|
||||
getOpenTextDocumentForUri,
|
||||
getOpenTextDocumentForUri(targetUri)?.getText()
|
||||
);
|
||||
await agentReviewPrimer.ensureReady(targetUri);
|
||||
try {
|
||||
await vscode.commands.executeCommand('vscode.openWith', targetUri, VIEW_TYPE);
|
||||
} catch {
|
||||
await agentReviewPrimer.ensureReady(targetUri, { forceNative: true });
|
||||
await vscode.commands.executeCommand('vscode.openWith', targetUri, VIEW_TYPE);
|
||||
}
|
||||
if (pendingReview) {
|
||||
agentReviewHandoff.scheduleDeferredReopen(targetUri);
|
||||
return;
|
||||
}
|
||||
await vscode.commands.executeCommand('vscode.openWith', targetUri, VIEW_TYPE);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -461,17 +508,80 @@ export function activate(context: vscode.ExtensionContext): void {
|
||||
);
|
||||
}
|
||||
|
||||
class MarkdownWebviewProvider implements vscode.CustomTextEditorProvider {
|
||||
class MarkdownWebviewProvider implements vscode.CustomEditorProvider<MarkdownCustomDocument> {
|
||||
private readonly activePanels = new Set<vscode.WebviewPanel>();
|
||||
private readonly panelSessions = new Map<vscode.WebviewPanel, PanelSession>();
|
||||
private readonly customDocuments = new Map<string, MarkdownCustomDocument>();
|
||||
private readonly spellDiagnosticCollection = vscode.languages.createDiagnosticCollection('meo-spell');
|
||||
private readonly customDocumentChangeEmitter = new vscode.EventEmitter<
|
||||
vscode.CustomDocumentContentChangeEvent<MarkdownCustomDocument>
|
||||
>();
|
||||
readonly onDidChangeCustomDocument = this.customDocumentChangeEmitter.event;
|
||||
private lastActivePanel: vscode.WebviewPanel | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly context: vscode.ExtensionContext,
|
||||
private readonly agentReviewHandoff: AgentReviewHandoffController
|
||||
) {
|
||||
this.context.subscriptions.push(this.spellDiagnosticCollection);
|
||||
this.context.subscriptions.push(this.spellDiagnosticCollection, this.customDocumentChangeEmitter);
|
||||
this.context.subscriptions.push(
|
||||
vscode.workspace.onDidChangeTextDocument((event) => {
|
||||
if (event.contentChanges.length === 0) {
|
||||
return;
|
||||
}
|
||||
const customDocument = this.customDocuments.get(event.document.uri.toString());
|
||||
if (!customDocument) {
|
||||
return;
|
||||
}
|
||||
this.customDocumentChangeEmitter.fire({ document: customDocument });
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async openCustomDocument(
|
||||
uri: vscode.Uri,
|
||||
openContext: vscode.CustomDocumentOpenContext,
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<MarkdownCustomDocument> {
|
||||
const customDocument = await MarkdownCustomDocument.open(uri, openContext);
|
||||
customDocument.bindLifecycle(() => {
|
||||
if (this.customDocuments.get(uri.toString()) === customDocument) {
|
||||
this.customDocuments.delete(uri.toString());
|
||||
}
|
||||
});
|
||||
this.customDocuments.set(uri.toString(), customDocument);
|
||||
customDocument.onDidChangeContent(() => {
|
||||
this.customDocumentChangeEmitter.fire({ document: customDocument });
|
||||
});
|
||||
return customDocument;
|
||||
}
|
||||
|
||||
async resolveCustomEditor(
|
||||
document: MarkdownCustomDocument,
|
||||
panel: vscode.WebviewPanel,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<void> {
|
||||
await this.resolveMarkdownEditor(document, panel, token);
|
||||
}
|
||||
|
||||
async saveCustomDocument(document: MarkdownCustomDocument): Promise<void> {
|
||||
await document.save();
|
||||
}
|
||||
|
||||
async saveCustomDocumentAs(document: MarkdownCustomDocument, destination: vscode.Uri): Promise<void> {
|
||||
await document.save(destination);
|
||||
}
|
||||
|
||||
async revertCustomDocument(document: MarkdownCustomDocument): Promise<void> {
|
||||
await document.revert();
|
||||
}
|
||||
|
||||
async backupCustomDocument(
|
||||
document: MarkdownCustomDocument,
|
||||
context: vscode.CustomDocumentBackupContext,
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<vscode.CustomDocumentBackup> {
|
||||
return document.backup(context.destination);
|
||||
}
|
||||
|
||||
async initializeGitWatcher(): Promise<void> {
|
||||
@@ -531,7 +641,7 @@ class MarkdownWebviewProvider implements vscode.CustomTextEditorProvider {
|
||||
}
|
||||
|
||||
this.agentReviewHandoff.scheduleDeferredReopen(session.documentUri);
|
||||
await this.redirectCopilotReviewToNativeEditor(session.document, session.panel);
|
||||
await this.redirectCopilotReviewToNativeEditor(session.documentUri, session.panel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -643,30 +753,32 @@ class MarkdownWebviewProvider implements vscode.CustomTextEditorProvider {
|
||||
await setReadOnlyEnabled(!getReadOnlyEnabled());
|
||||
}
|
||||
|
||||
async resolveCustomTextEditor(
|
||||
document: vscode.TextDocument,
|
||||
async resolveMarkdownEditor(
|
||||
document: MarkdownCustomDocument,
|
||||
panel: vscode.WebviewPanel,
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<void> {
|
||||
const pendingReview = findLikelyAgentReviewState(
|
||||
document.uri,
|
||||
getComparableResourceKey,
|
||||
getOpenTextDocumentForUri,
|
||||
document.getText()
|
||||
);
|
||||
const pendingReview = document.isSynchronized
|
||||
? findLikelyAgentReviewState(
|
||||
document.uri,
|
||||
getComparableResourceKey,
|
||||
getOpenTextDocumentForUri,
|
||||
document.getText()
|
||||
)
|
||||
: undefined;
|
||||
if (pendingReview) {
|
||||
this.agentReviewHandoff.scheduleDeferredReopen(document.uri);
|
||||
await this.redirectCopilotReviewToNativeEditor(document, panel, true);
|
||||
await this.redirectCopilotReviewToNativeEditor(document.uri, panel, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (await this.redirectGitResourceToNativeEditor(document, panel)) {
|
||||
if (await this.redirectGitResourceToNativeEditor(document.uri, panel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.activePanels.add(panel);
|
||||
|
||||
const documentUri = resolveWorktreeUri(document);
|
||||
const documentUri = resolveWorktreeUri(document.uri);
|
||||
const distRoot = vscode.Uri.joinPath(this.context.extensionUri, 'webview', 'dist');
|
||||
panel.webview.options = {
|
||||
enableScripts: true,
|
||||
@@ -891,10 +1003,10 @@ class MarkdownWebviewProvider implements vscode.CustomTextEditorProvider {
|
||||
}
|
||||
|
||||
private async redirectGitResourceToNativeEditor(
|
||||
document: vscode.TextDocument,
|
||||
uri: vscode.Uri,
|
||||
panel: vscode.WebviewPanel
|
||||
): Promise<boolean> {
|
||||
if (document.uri.scheme !== 'git') {
|
||||
if (uri.scheme !== 'git') {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -904,7 +1016,7 @@ class MarkdownWebviewProvider implements vscode.CustomTextEditorProvider {
|
||||
preview: true,
|
||||
override: 'default'
|
||||
};
|
||||
const existingDiff = findDiffContextForGitUri(document.uri);
|
||||
const existingDiff = findDiffContextForGitUri(uri);
|
||||
|
||||
if (existingDiff) {
|
||||
await vscode.commands.executeCommand(
|
||||
@@ -918,14 +1030,14 @@ class MarkdownWebviewProvider implements vscode.CustomTextEditorProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
const ref = getGitUriRef(document.uri);
|
||||
const ref = getGitUriRef(uri);
|
||||
if (isWorkingTreeOrIndexRef(ref)) {
|
||||
const targetUri = resolveWorktreeUri(document);
|
||||
const title = getNativeWorkingTreeTitle(document.uri, targetUri);
|
||||
const targetUri = resolveWorktreeUri(uri);
|
||||
const title = getNativeWorkingTreeTitle(uri, targetUri);
|
||||
|
||||
await vscode.commands.executeCommand(
|
||||
'_workbench.diff',
|
||||
document.uri,
|
||||
uri,
|
||||
targetUri,
|
||||
title,
|
||||
[viewColumn, editorOptions]
|
||||
@@ -938,14 +1050,14 @@ class MarkdownWebviewProvider implements vscode.CustomTextEditorProvider {
|
||||
}
|
||||
|
||||
private async redirectCopilotReviewToNativeEditor(
|
||||
document: vscode.TextDocument,
|
||||
uri: vscode.Uri,
|
||||
panel: vscode.WebviewPanel,
|
||||
preserveFocus = false
|
||||
): Promise<void> {
|
||||
const viewColumn = panel.viewColumn ?? vscode.ViewColumn.Active;
|
||||
await vscode.commands.executeCommand(
|
||||
'vscode.openWith',
|
||||
document.uri,
|
||||
uri,
|
||||
'default',
|
||||
{
|
||||
viewColumn,
|
||||
@@ -1111,12 +1223,12 @@ function replaceFileExtension(filePath: string, ext: '.html' | '.pdf'): string {
|
||||
return path.join(parsed.dir, `${parsed.name}${ext}`);
|
||||
}
|
||||
|
||||
function resolveWorktreeUri(document: vscode.TextDocument): vscode.Uri {
|
||||
if (document.uri.scheme === 'file') {
|
||||
return document.uri;
|
||||
function resolveWorktreeUri(uri: vscode.Uri): vscode.Uri {
|
||||
if (uri.scheme === 'file') {
|
||||
return uri;
|
||||
}
|
||||
|
||||
return resolveWorktreeUriFromGitUri(document.uri) ?? document.uri;
|
||||
return resolveWorktreeUriFromGitUri(uri) ?? uri;
|
||||
}
|
||||
|
||||
function findDiffContextForGitUri(uri: vscode.Uri): { original: vscode.Uri; modified: vscode.Uri; title: string } | undefined {
|
||||
@@ -1249,4 +1361,17 @@ function normalizeThemeId(id: string): string {
|
||||
return id.trim().toLowerCase();
|
||||
}
|
||||
|
||||
function primeDiffTabResources(tab: vscode.Tab, primer: AgentReviewDocumentPrimer): void {
|
||||
const input = tab.input;
|
||||
if (!(input instanceof vscode.TabInputTextDiff)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const uri of [input.original, input.modified]) {
|
||||
if (isMarkdownFileUri(uri)) {
|
||||
void primer.ensureReady(uri, { forceNative: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function deactivate(): void {}
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class MarkdownCustomDocument implements vscode.CustomDocument {
|
||||
private text: string;
|
||||
private localVersion = 1;
|
||||
private onDisposed?: () => void;
|
||||
private readonly changeEmitter = new vscode.EventEmitter<void>();
|
||||
readonly onDidChangeContent = this.changeEmitter.event;
|
||||
|
||||
constructor(
|
||||
readonly uri: vscode.Uri,
|
||||
text: string,
|
||||
readonly textDocument: vscode.TextDocument | undefined
|
||||
) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
static async open(
|
||||
uri: vscode.Uri,
|
||||
openContext?: vscode.CustomDocumentOpenContext
|
||||
): Promise<MarkdownCustomDocument> {
|
||||
const textDocument = await tryOpenWorkspaceTextDocument(uri);
|
||||
let text = textDocument ? textDocument.getText() : await readFileText(uri);
|
||||
|
||||
if (openContext?.backupId) {
|
||||
try {
|
||||
const backupUri = vscode.Uri.parse(openContext.backupId);
|
||||
text = Buffer.from(await vscode.workspace.fs.readFile(backupUri)).toString('utf8');
|
||||
if (textDocument && textDocument.getText() !== text) {
|
||||
await replaceWorkspaceDocumentText(textDocument, text);
|
||||
}
|
||||
} catch {
|
||||
// Keep the on-disk text if the backup cannot be restored.
|
||||
}
|
||||
}
|
||||
|
||||
return new MarkdownCustomDocument(uri, text, textDocument);
|
||||
}
|
||||
|
||||
bindLifecycle(onDisposed: () => void): void {
|
||||
this.onDisposed = onDisposed;
|
||||
}
|
||||
|
||||
get isSynchronized(): boolean {
|
||||
return Boolean(this.textDocument);
|
||||
}
|
||||
|
||||
get version(): number {
|
||||
return this.textDocument?.version ?? this.localVersion;
|
||||
}
|
||||
|
||||
get lineCount(): number {
|
||||
return lineCountOf(this.getText());
|
||||
}
|
||||
|
||||
getText(): string {
|
||||
return this.textDocument?.getText() ?? this.text;
|
||||
}
|
||||
|
||||
positionAt(offset: number): vscode.Position {
|
||||
if (this.textDocument) {
|
||||
return this.textDocument.positionAt(offset);
|
||||
}
|
||||
return positionAtOffset(this.text, offset);
|
||||
}
|
||||
|
||||
offsetAt(position: vscode.Position): number {
|
||||
if (this.textDocument) {
|
||||
return this.textDocument.offsetAt(position);
|
||||
}
|
||||
return offsetAtPosition(this.text, position);
|
||||
}
|
||||
|
||||
lineAt(line: number): { text: string; range: vscode.Range } {
|
||||
if (this.textDocument) {
|
||||
const info = this.textDocument.lineAt(line);
|
||||
return { text: info.text, range: info.range };
|
||||
}
|
||||
const start = offsetAtPosition(this.text, new vscode.Position(line, 0));
|
||||
const text = this.getText().slice(start).split(/\r\n|\n|\r/, 1)[0] ?? '';
|
||||
const range = new vscode.Range(
|
||||
new vscode.Position(line, 0),
|
||||
new vscode.Position(line, text.length)
|
||||
);
|
||||
return { text, range };
|
||||
}
|
||||
|
||||
async save(destination?: vscode.Uri): Promise<void> {
|
||||
const target = destination ?? this.uri;
|
||||
if (this.textDocument && target.toString() === this.uri.toString()) {
|
||||
await this.textDocument.save();
|
||||
this.text = this.textDocument.getText();
|
||||
return;
|
||||
}
|
||||
|
||||
await vscode.workspace.fs.writeFile(target, Buffer.from(this.getText(), 'utf8'));
|
||||
}
|
||||
|
||||
async revert(): Promise<void> {
|
||||
if (this.uri.scheme !== 'file') {
|
||||
return;
|
||||
}
|
||||
|
||||
const diskText = await readFileText(this.uri);
|
||||
if (diskText === this.getText()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.replaceFull(diskText);
|
||||
}
|
||||
|
||||
async backup(destination: vscode.Uri): Promise<vscode.CustomDocumentBackup> {
|
||||
await vscode.workspace.fs.writeFile(destination, Buffer.from(this.getText(), 'utf8'));
|
||||
return {
|
||||
id: destination.toString(),
|
||||
delete: async () => {
|
||||
try {
|
||||
await vscode.workspace.fs.delete(destination);
|
||||
} catch {
|
||||
// Best-effort cleanup of a hot-exit backup.
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async replaceFull(nextText: string): Promise<boolean> {
|
||||
if (nextText === this.getText()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.textDocument) {
|
||||
const applied = await replaceWorkspaceDocumentText(this.textDocument, nextText);
|
||||
if (applied) {
|
||||
this.text = this.textDocument.getText();
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
|
||||
this.text = nextText;
|
||||
this.localVersion += 1;
|
||||
this.changeEmitter.fire();
|
||||
return true;
|
||||
}
|
||||
|
||||
async applyReplacements(replacements: Array<{ startOffset: number; endOffset: number; insert: string }>): Promise<boolean> {
|
||||
if (this.textDocument) {
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
for (const replacement of replacements) {
|
||||
edit.replace(
|
||||
this.uri,
|
||||
new vscode.Range(
|
||||
this.textDocument.positionAt(replacement.startOffset),
|
||||
this.textDocument.positionAt(replacement.endOffset)
|
||||
),
|
||||
replacement.insert
|
||||
);
|
||||
}
|
||||
const applied = await vscode.workspace.applyEdit(edit);
|
||||
if (applied) {
|
||||
this.text = this.textDocument.getText();
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
|
||||
let nextText = this.text;
|
||||
const sorted = [...replacements].sort((left, right) => right.startOffset - left.startOffset);
|
||||
for (const replacement of sorted) {
|
||||
nextText = `${nextText.slice(0, replacement.startOffset)}${replacement.insert}${nextText.slice(replacement.endOffset)}`;
|
||||
}
|
||||
this.text = nextText;
|
||||
this.localVersion += 1;
|
||||
this.changeEmitter.fire();
|
||||
return true;
|
||||
}
|
||||
|
||||
notifyExternalChange(): void {
|
||||
if (this.textDocument) {
|
||||
this.text = this.textDocument.getText();
|
||||
}
|
||||
this.changeEmitter.fire();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.changeEmitter.dispose();
|
||||
this.onDisposed?.();
|
||||
}
|
||||
}
|
||||
|
||||
export async function tryOpenWorkspaceTextDocument(uri: vscode.Uri): Promise<vscode.TextDocument | undefined> {
|
||||
try {
|
||||
return await vscode.workspace.openTextDocument(uri);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function readFileText(uri: vscode.Uri): Promise<string> {
|
||||
const bytes = await vscode.workspace.fs.readFile(uri);
|
||||
return Buffer.from(bytes).toString('utf8');
|
||||
}
|
||||
|
||||
async function replaceWorkspaceDocumentText(textDocument: vscode.TextDocument, nextText: string): Promise<boolean> {
|
||||
const currentText = textDocument.getText();
|
||||
if (currentText === nextText) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
edit.replace(
|
||||
textDocument.uri,
|
||||
new vscode.Range(textDocument.positionAt(0), textDocument.positionAt(currentText.length)),
|
||||
nextText
|
||||
);
|
||||
return vscode.workspace.applyEdit(edit);
|
||||
}
|
||||
|
||||
function lineCountOf(text: string): number {
|
||||
if (!text) {
|
||||
return 1;
|
||||
}
|
||||
let lines = 1;
|
||||
for (let index = 0; index < text.length; index += 1) {
|
||||
const code = text.charCodeAt(index);
|
||||
if (code === 13) {
|
||||
lines += 1;
|
||||
if (text.charCodeAt(index + 1) === 10) {
|
||||
index += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (code === 10) {
|
||||
lines += 1;
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function positionAtOffset(text: string, offset: number): vscode.Position {
|
||||
const end = Math.max(0, Math.min(offset, text.length));
|
||||
let line = 0;
|
||||
let character = 0;
|
||||
let index = 0;
|
||||
while (index < end) {
|
||||
const code = text.charCodeAt(index);
|
||||
if (code === 13) {
|
||||
index += text.charCodeAt(index + 1) === 10 ? 2 : 1;
|
||||
line += 1;
|
||||
character = 0;
|
||||
continue;
|
||||
}
|
||||
if (code === 10) {
|
||||
index += 1;
|
||||
line += 1;
|
||||
character = 0;
|
||||
continue;
|
||||
}
|
||||
index += 1;
|
||||
character += 1;
|
||||
}
|
||||
return new vscode.Position(line, character);
|
||||
}
|
||||
|
||||
function offsetAtPosition(text: string, position: vscode.Position): number {
|
||||
const targetLine = Math.max(0, position.line);
|
||||
const targetCharacter = Math.max(0, position.character);
|
||||
let line = 0;
|
||||
let index = 0;
|
||||
while (index < text.length && line < targetLine) {
|
||||
const code = text.charCodeAt(index);
|
||||
if (code === 13) {
|
||||
index += text.charCodeAt(index + 1) === 10 ? 2 : 1;
|
||||
line += 1;
|
||||
continue;
|
||||
}
|
||||
if (code === 10) {
|
||||
index += 1;
|
||||
line += 1;
|
||||
continue;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
|
||||
let character = 0;
|
||||
while (index < text.length && character < targetCharacter) {
|
||||
const code = text.charCodeAt(index);
|
||||
if (code === 10 || code === 13) {
|
||||
break;
|
||||
}
|
||||
index += 1;
|
||||
character += 1;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as path from 'node:path';
|
||||
import * as vscode from 'vscode';
|
||||
import type { AgentReviewHandoffController } from '../agents/reviewHandoff';
|
||||
import type { MarkdownCustomDocument } from './markdownCustomDocument';
|
||||
import {
|
||||
EXTENSION_CONFIG_SECTION,
|
||||
LINE_NUMBERS_SETTING_KEY,
|
||||
@@ -385,7 +386,7 @@ const EMPTY_GIT_BASELINE_PAYLOAD: GitBaselinePayload = Object.freeze({
|
||||
|
||||
type PanelSessionControllerParams = {
|
||||
panel: vscode.WebviewPanel;
|
||||
document: vscode.TextDocument;
|
||||
document: MarkdownCustomDocument;
|
||||
documentUri: vscode.Uri;
|
||||
context: vscode.ExtensionContext;
|
||||
spellDiagnosticCollection: vscode.DiagnosticCollection;
|
||||
@@ -401,7 +402,7 @@ type PanelSessionControllerParams = {
|
||||
|
||||
export type PanelSession = {
|
||||
panel: vscode.WebviewPanel;
|
||||
document: vscode.TextDocument;
|
||||
document: MarkdownCustomDocument;
|
||||
documentUri: vscode.Uri;
|
||||
gitDocumentState: GitDocumentState;
|
||||
getMode: () => EditorMode;
|
||||
@@ -507,11 +508,8 @@ export function createPanelSessionController(params: PanelSessionControllerParam
|
||||
return false;
|
||||
}
|
||||
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
const fullRange = new vscode.Range(document.positionAt(0), document.positionAt(currentText.length));
|
||||
agentReviewHandoff.noteRecentMEOOwnedFileChangeForUri(document.uri);
|
||||
edit.replace(document.uri, fullRange, draftText);
|
||||
const applied = await vscode.workspace.applyEdit(edit);
|
||||
const applied = await document.replaceFull(draftText);
|
||||
if (applied) {
|
||||
pendingDraftText = null;
|
||||
}
|
||||
@@ -604,7 +602,9 @@ export function createPanelSessionController(params: PanelSessionControllerParam
|
||||
|
||||
const runSpellCheck = async (generation: number): Promise<void> => {
|
||||
try {
|
||||
const diagnostics = await collectMeoSpellDiagnostics(document);
|
||||
const diagnostics = document.textDocument
|
||||
? await collectMeoSpellDiagnostics(document.textDocument)
|
||||
: [];
|
||||
if (disposed || generation !== spellCheckGeneration) {
|
||||
return;
|
||||
}
|
||||
@@ -1238,6 +1238,19 @@ export function createPanelSessionController(params: PanelSessionControllerParam
|
||||
}), 'sendDocChanged');
|
||||
});
|
||||
|
||||
const localChangeSubscription = document.onDidChangeContent(() => {
|
||||
if (document.isSynchronized) {
|
||||
return;
|
||||
}
|
||||
scheduleSpellCheck();
|
||||
if (isApplyingOwnChange) {
|
||||
return;
|
||||
}
|
||||
runBackground(enqueue(async () => {
|
||||
await sendDocChanged();
|
||||
}), 'sendDocChanged');
|
||||
});
|
||||
|
||||
const documentSaveSubscription = vscode.workspace.onDidSaveTextDocument((savedDocument) => {
|
||||
if (savedDocument.uri.toString() !== documentKey) {
|
||||
return;
|
||||
@@ -1249,7 +1262,7 @@ export function createPanelSessionController(params: PanelSessionControllerParam
|
||||
if (!event.uris.some((uri) => uri.toString() === documentKey)) {
|
||||
return;
|
||||
}
|
||||
if (hasExternalSpellDiagnostics(document)) {
|
||||
if (document.textDocument && hasExternalSpellDiagnostics(document.textDocument)) {
|
||||
spellCheckGeneration += 1;
|
||||
if (pendingSpellCheckTimer !== null) {
|
||||
clearTimeout(pendingSpellCheckTimer);
|
||||
@@ -1324,6 +1337,7 @@ export function createPanelSessionController(params: PanelSessionControllerParam
|
||||
rejectPendingExportSnapshots(new Error('The editor was closed before export completed.'));
|
||||
messageSubscription.dispose();
|
||||
documentChangeSubscription.dispose();
|
||||
localChangeSubscription.dispose();
|
||||
documentSaveSubscription.dispose();
|
||||
diagnosticsSubscription.dispose();
|
||||
textEditorSelectionSubscription.dispose();
|
||||
@@ -1414,7 +1428,7 @@ function pruneRememberedViewPositionMap(
|
||||
}
|
||||
|
||||
async function applyDocumentChanges(
|
||||
document: vscode.TextDocument,
|
||||
document: MarkdownCustomDocument,
|
||||
message: ApplyChangesMessage,
|
||||
sendDocChanged: () => Promise<boolean>,
|
||||
sendApplied: (version: number) => Promise<boolean>,
|
||||
@@ -1426,7 +1440,6 @@ async function applyDocumentChanges(
|
||||
return;
|
||||
}
|
||||
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
const sortedChanges = [...message.changes].sort((a, b) => b.from - a.from);
|
||||
const documentText = document.getText();
|
||||
const mappedOffsetCache = new Map<number, number>();
|
||||
@@ -1440,20 +1453,17 @@ async function applyDocumentChanges(
|
||||
return mapped;
|
||||
};
|
||||
|
||||
for (const change of sortedChanges) {
|
||||
// Webview offsets are LF-normalized; remap to real document offsets before applying edits.
|
||||
const replacements = sortedChanges.map((change) => {
|
||||
const mappedFrom = mapOffset(change.from);
|
||||
const mappedTo = mapOffset(change.to);
|
||||
const startOffset = Math.min(mappedFrom, mappedTo);
|
||||
const endOffset = Math.max(mappedFrom, mappedTo);
|
||||
const range = new vscode.Range(
|
||||
document.positionAt(startOffset),
|
||||
document.positionAt(endOffset)
|
||||
);
|
||||
edit.replace(document.uri, range, change.insert);
|
||||
}
|
||||
return {
|
||||
startOffset: Math.min(mappedFrom, mappedTo),
|
||||
endOffset: Math.max(mappedFrom, mappedTo),
|
||||
insert: change.insert
|
||||
};
|
||||
});
|
||||
|
||||
const applied = await vscode.workspace.applyEdit(edit);
|
||||
const applied = await document.applyReplacements(replacements);
|
||||
|
||||
if (!applied) {
|
||||
await sendAppliedFailed();
|
||||
@@ -1546,7 +1556,7 @@ function clampDiagnosticRange(from: number, to: number, textLength: number): { f
|
||||
return { from: clampedFrom, to: clampedTo };
|
||||
}
|
||||
|
||||
function serializeDiagnostics(document: vscode.TextDocument): SerializedDiagnostic[] {
|
||||
function serializeDiagnostics(document: MarkdownCustomDocument): SerializedDiagnostic[] {
|
||||
const diagnostics = vscode.languages.getDiagnostics(document.uri);
|
||||
if (!diagnostics.length) {
|
||||
return [];
|
||||
@@ -1579,7 +1589,7 @@ function serializeDiagnostics(document: vscode.TextDocument): SerializedDiagnost
|
||||
}
|
||||
|
||||
async function resolveDiagnosticSuggestions(
|
||||
document: vscode.TextDocument,
|
||||
document: MarkdownCustomDocument,
|
||||
request: RequestDiagnosticSuggestionsMessage
|
||||
): Promise<DiagnosticSuggestionsResultMessage> {
|
||||
const emptyResponse: DiagnosticSuggestionsResultMessage = {
|
||||
@@ -1590,6 +1600,10 @@ async function resolveDiagnosticSuggestions(
|
||||
suggestions: []
|
||||
};
|
||||
|
||||
if (!document.textDocument) {
|
||||
return emptyResponse;
|
||||
}
|
||||
|
||||
const documentText = document.getText();
|
||||
const normalizedTextLength = documentText.replace(/\r\n?/g, '\n').length;
|
||||
const requestedRange = clampDiagnosticRange(request.from, request.to, normalizedTextLength);
|
||||
@@ -1633,7 +1647,9 @@ async function resolveDiagnosticSuggestions(
|
||||
}
|
||||
|
||||
if (suggestions.length === 0 && request.source === MEO_SPELL_DIAGNOSTIC_SOURCE) {
|
||||
const spellSuggestions = await collectMeoSpellSuggestions(document, requestedRange.from, requestedRange.to);
|
||||
const spellSuggestions = document.textDocument
|
||||
? await collectMeoSpellSuggestions(document.textDocument, requestedRange.from, requestedRange.to)
|
||||
: [];
|
||||
for (const suggestion of spellSuggestions) {
|
||||
if (seen.has(suggestion)) {
|
||||
continue;
|
||||
@@ -1653,7 +1669,7 @@ async function resolveDiagnosticSuggestions(
|
||||
}
|
||||
|
||||
function hasMatchingDiagnostic(
|
||||
document: vscode.TextDocument,
|
||||
document: MarkdownCustomDocument,
|
||||
request: RequestDiagnosticSuggestionsMessage,
|
||||
requestedRange: { from: number; to: number }
|
||||
): boolean {
|
||||
@@ -1676,7 +1692,7 @@ function hasMatchingDiagnostic(
|
||||
}
|
||||
|
||||
function simpleReplacementFromCodeAction(
|
||||
document: vscode.TextDocument,
|
||||
document: MarkdownCustomDocument,
|
||||
requestedRange: { from: number; to: number },
|
||||
action: vscode.Command | vscode.CodeAction
|
||||
): string | null {
|
||||
|
||||
@@ -399,7 +399,15 @@ async function syncEditorAssociationsForTarget(
|
||||
'chat-editing-text-model:**/*.md': 'default',
|
||||
'chat-editing-text-model:**/*.markdown': 'default',
|
||||
'chat-editing-text-model:**/*.mdx': 'default',
|
||||
'chat-editing-text-model:**/*.mdc': 'default'
|
||||
'chat-editing-text-model:**/*.mdc': 'default',
|
||||
'chat-editing-snapshot-text-model:/**/*.md': 'default',
|
||||
'chat-editing-snapshot-text-model:/**/*.markdown': 'default',
|
||||
'chat-editing-snapshot-text-model:/**/*.mdx': 'default',
|
||||
'chat-editing-snapshot-text-model:/**/*.mdc': 'default',
|
||||
'chat-editing-snapshot-text-model:**/*.md': 'default',
|
||||
'chat-editing-snapshot-text-model:**/*.markdown': 'default',
|
||||
'chat-editing-snapshot-text-model:**/*.mdx': 'default',
|
||||
'chat-editing-snapshot-text-model:**/*.mdc': 'default'
|
||||
};
|
||||
|
||||
await config.update('editorAssociations', markdownAssociations, target);
|
||||
|
||||
Reference in New Issue
Block a user