mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-22 09:47:47 +00:00
Sync upstream Gemini-CLI v0.8.2 (#838)
This commit is contained in:
@@ -4,120 +4,24 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
IDE_MAX_OPEN_FILES,
|
||||
IDE_MAX_SELECTED_TEXT_LENGTH,
|
||||
} from './constants.js';
|
||||
import type { IdeContext } from './types.js';
|
||||
|
||||
/**
|
||||
* Zod schema for validating a file context from the IDE.
|
||||
*/
|
||||
export const FileSchema = z.object({
|
||||
path: z.string(),
|
||||
timestamp: z.number(),
|
||||
isActive: z.boolean().optional(),
|
||||
selectedText: z.string().optional(),
|
||||
cursor: z
|
||||
.object({
|
||||
line: z.number(),
|
||||
character: z.number(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
export type File = z.infer<typeof FileSchema>;
|
||||
type IdeContextSubscriber = (ideContext?: IdeContext) => void;
|
||||
|
||||
export const IdeContextSchema = z.object({
|
||||
workspaceState: z
|
||||
.object({
|
||||
openFiles: z.array(FileSchema).optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
export type IdeContext = z.infer<typeof IdeContextSchema>;
|
||||
|
||||
/**
|
||||
* Zod schema for validating the 'ide/contextUpdate' notification from the IDE.
|
||||
*/
|
||||
export const IdeContextNotificationSchema = z.object({
|
||||
jsonrpc: z.literal('2.0'),
|
||||
method: z.literal('ide/contextUpdate'),
|
||||
params: IdeContextSchema,
|
||||
});
|
||||
|
||||
export const IdeDiffAcceptedNotificationSchema = z.object({
|
||||
jsonrpc: z.literal('2.0'),
|
||||
method: z.literal('ide/diffAccepted'),
|
||||
params: z.object({
|
||||
filePath: z.string(),
|
||||
content: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const IdeDiffClosedNotificationSchema = z.object({
|
||||
jsonrpc: z.literal('2.0'),
|
||||
method: z.literal('ide/diffClosed'),
|
||||
params: z.object({
|
||||
filePath: z.string(),
|
||||
content: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const CloseDiffResponseSchema = z
|
||||
.object({
|
||||
content: z
|
||||
.array(
|
||||
z.object({
|
||||
text: z.string(),
|
||||
type: z.literal('text'),
|
||||
}),
|
||||
)
|
||||
.min(1),
|
||||
})
|
||||
.transform((val, ctx) => {
|
||||
try {
|
||||
const parsed = JSON.parse(val.content[0].text);
|
||||
const innerSchema = z.object({ content: z.string().optional() });
|
||||
const validationResult = innerSchema.safeParse(parsed);
|
||||
if (!validationResult.success) {
|
||||
validationResult.error.issues.forEach((issue) => ctx.addIssue(issue));
|
||||
return z.NEVER;
|
||||
}
|
||||
return validationResult.data;
|
||||
} catch (_) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'Invalid JSON in text content',
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
});
|
||||
|
||||
export type DiffUpdateResult =
|
||||
| {
|
||||
status: 'accepted';
|
||||
content?: string;
|
||||
}
|
||||
| {
|
||||
status: 'rejected';
|
||||
content: undefined;
|
||||
};
|
||||
|
||||
type IdeContextSubscriber = (ideContext: IdeContext | undefined) => void;
|
||||
|
||||
/**
|
||||
* Creates a new store for managing the IDE's context.
|
||||
* This factory function encapsulates the state and logic, allowing for the creation
|
||||
* of isolated instances, which is particularly useful for testing.
|
||||
*
|
||||
* @returns An object with methods to interact with the IDE context.
|
||||
*/
|
||||
export function createIdeContextStore() {
|
||||
let ideContextState: IdeContext | undefined = undefined;
|
||||
const subscribers = new Set<IdeContextSubscriber>();
|
||||
export class IdeContextStore {
|
||||
private ideContextState?: IdeContext;
|
||||
private readonly subscribers = new Set<IdeContextSubscriber>();
|
||||
|
||||
/**
|
||||
* Notifies all registered subscribers about the current IDE context.
|
||||
*/
|
||||
function notifySubscribers(): void {
|
||||
for (const subscriber of subscribers) {
|
||||
subscriber(ideContextState);
|
||||
private notifySubscribers(): void {
|
||||
for (const subscriber of this.subscribers) {
|
||||
subscriber(this.ideContextState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,25 +29,76 @@ export function createIdeContextStore() {
|
||||
* Sets the IDE context and notifies all registered subscribers of the change.
|
||||
* @param newIdeContext The new IDE context from the IDE.
|
||||
*/
|
||||
function setIdeContext(newIdeContext: IdeContext): void {
|
||||
ideContextState = newIdeContext;
|
||||
notifySubscribers();
|
||||
set(newIdeContext: IdeContext): void {
|
||||
const { workspaceState } = newIdeContext;
|
||||
if (!workspaceState) {
|
||||
this.ideContextState = newIdeContext;
|
||||
this.notifySubscribers();
|
||||
return;
|
||||
}
|
||||
|
||||
const { openFiles } = workspaceState;
|
||||
|
||||
if (openFiles && openFiles.length > 0) {
|
||||
// Sort by timestamp descending (newest first)
|
||||
openFiles.sort((a, b) => b.timestamp - a.timestamp);
|
||||
|
||||
// The most recent file is now at index 0.
|
||||
const mostRecentFile = openFiles[0];
|
||||
|
||||
// If the most recent file is not active, then no file is active.
|
||||
if (!mostRecentFile.isActive) {
|
||||
openFiles.forEach((file) => {
|
||||
file.isActive = false;
|
||||
file.cursor = undefined;
|
||||
file.selectedText = undefined;
|
||||
});
|
||||
} else {
|
||||
// The most recent file is active. Ensure it's the only one.
|
||||
openFiles.forEach((file, index: number) => {
|
||||
if (index !== 0) {
|
||||
file.isActive = false;
|
||||
file.cursor = undefined;
|
||||
file.selectedText = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// Truncate selected text in the active file
|
||||
if (
|
||||
mostRecentFile.selectedText &&
|
||||
mostRecentFile.selectedText.length > IDE_MAX_SELECTED_TEXT_LENGTH
|
||||
) {
|
||||
mostRecentFile.selectedText =
|
||||
mostRecentFile.selectedText.substring(
|
||||
0,
|
||||
IDE_MAX_SELECTED_TEXT_LENGTH,
|
||||
) + '... [TRUNCATED]';
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate files list
|
||||
if (openFiles.length > IDE_MAX_OPEN_FILES) {
|
||||
workspaceState.openFiles = openFiles.slice(0, IDE_MAX_OPEN_FILES);
|
||||
}
|
||||
}
|
||||
this.ideContextState = newIdeContext;
|
||||
this.notifySubscribers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the IDE context and notifies all registered subscribers of the change.
|
||||
*/
|
||||
function clearIdeContext(): void {
|
||||
ideContextState = undefined;
|
||||
notifySubscribers();
|
||||
clear(): void {
|
||||
this.ideContextState = undefined;
|
||||
this.notifySubscribers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current IDE context.
|
||||
* @returns The `IdeContext` object if a file is active; otherwise, `undefined`.
|
||||
*/
|
||||
function getIdeContext(): IdeContext | undefined {
|
||||
return ideContextState;
|
||||
get(): IdeContext | undefined {
|
||||
return this.ideContextState;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,22 +110,15 @@ export function createIdeContextStore() {
|
||||
* @param subscriber The function to be called when the IDE context changes.
|
||||
* @returns A function that, when called, will unsubscribe the provided subscriber.
|
||||
*/
|
||||
function subscribeToIdeContext(subscriber: IdeContextSubscriber): () => void {
|
||||
subscribers.add(subscriber);
|
||||
subscribe(subscriber: IdeContextSubscriber): () => void {
|
||||
this.subscribers.add(subscriber);
|
||||
return () => {
|
||||
subscribers.delete(subscriber);
|
||||
this.subscribers.delete(subscriber);
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
setIdeContext,
|
||||
getIdeContext,
|
||||
subscribeToIdeContext,
|
||||
clearIdeContext,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The default, shared instance of the IDE context store for the application.
|
||||
*/
|
||||
export const ideContext = createIdeContextStore();
|
||||
export const ideContextStore = new IdeContextStore();
|
||||
|
||||
Reference in New Issue
Block a user