mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
324 lines
8.6 KiB
TypeScript
324 lines
8.6 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import {
|
|
type Config,
|
|
IdeClient,
|
|
type File,
|
|
logIdeConnection,
|
|
IdeConnectionEvent,
|
|
IdeConnectionType,
|
|
} from '@qwen-code/qwen-code-core';
|
|
import {
|
|
QWEN_CODE_COMPANION_EXTENSION_NAME,
|
|
getIdeInstaller,
|
|
IDEConnectionStatus,
|
|
ideContextStore,
|
|
} from '@qwen-code/qwen-code-core';
|
|
import path from 'node:path';
|
|
import type {
|
|
CommandContext,
|
|
SlashCommand,
|
|
SlashCommandActionReturn,
|
|
} from './types.js';
|
|
import { CommandKind } from './types.js';
|
|
import { SettingScope } from '../../config/settings.js';
|
|
import { t } from '../../i18n/index.js';
|
|
|
|
function getIdeStatusMessage(ideClient: IdeClient): {
|
|
messageType: 'info' | 'error';
|
|
content: string;
|
|
} {
|
|
const connection = ideClient.getConnectionStatus();
|
|
switch (connection.status) {
|
|
case IDEConnectionStatus.Connected:
|
|
return {
|
|
messageType: 'info',
|
|
content: `🟢 Connected to ${ideClient.getDetectedIdeDisplayName()}`,
|
|
};
|
|
case IDEConnectionStatus.Connecting:
|
|
return {
|
|
messageType: 'info',
|
|
content: `🟡 Connecting...`,
|
|
};
|
|
default: {
|
|
let content = `🔴 Disconnected`;
|
|
if (connection?.details) {
|
|
content += `: ${connection.details}`;
|
|
}
|
|
return {
|
|
messageType: 'error',
|
|
content,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
function formatFileList(openFiles: File[]): string {
|
|
const basenameCounts = new Map<string, number>();
|
|
for (const file of openFiles) {
|
|
const basename = path.basename(file.path);
|
|
basenameCounts.set(basename, (basenameCounts.get(basename) || 0) + 1);
|
|
}
|
|
|
|
const fileList = openFiles
|
|
.map((file: File) => {
|
|
const basename = path.basename(file.path);
|
|
const isDuplicate = (basenameCounts.get(basename) || 0) > 1;
|
|
const parentDir = path.basename(path.dirname(file.path));
|
|
const displayName = isDuplicate
|
|
? `${basename} (/${parentDir})`
|
|
: basename;
|
|
|
|
return ` - ${displayName}${file.isActive ? ' (active)' : ''}`;
|
|
})
|
|
.join('\n');
|
|
|
|
const infoMessage = `
|
|
(Note: The file list is limited to a number of recently accessed files within your workspace and only includes local files on disk)`;
|
|
|
|
return `\n\nOpen files:\n${fileList}\n${infoMessage}`;
|
|
}
|
|
|
|
async function getIdeStatusMessageWithFiles(ideClient: IdeClient): Promise<{
|
|
messageType: 'info' | 'error';
|
|
content: string;
|
|
}> {
|
|
const connection = ideClient.getConnectionStatus();
|
|
switch (connection.status) {
|
|
case IDEConnectionStatus.Connected: {
|
|
let content = `🟢 Connected to ${ideClient.getDetectedIdeDisplayName()}`;
|
|
const context = ideContextStore.get();
|
|
const openFiles = context?.workspaceState?.openFiles;
|
|
if (openFiles && openFiles.length > 0) {
|
|
content += formatFileList(openFiles);
|
|
}
|
|
return {
|
|
messageType: 'info',
|
|
content,
|
|
};
|
|
}
|
|
case IDEConnectionStatus.Connecting:
|
|
return {
|
|
messageType: 'info',
|
|
content: `🟡 Connecting...`,
|
|
};
|
|
default: {
|
|
let content = `🔴 Disconnected`;
|
|
if (connection?.details) {
|
|
content += `: ${connection.details}`;
|
|
}
|
|
return {
|
|
messageType: 'error',
|
|
content,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
async function setIdeModeAndSyncConnection(
|
|
config: Config,
|
|
value: boolean,
|
|
): Promise<void> {
|
|
config.setIdeMode(value);
|
|
const ideClient = await IdeClient.getInstance();
|
|
if (value) {
|
|
await ideClient.connect();
|
|
logIdeConnection(config, new IdeConnectionEvent(IdeConnectionType.SESSION));
|
|
} else {
|
|
await ideClient.disconnect();
|
|
}
|
|
}
|
|
|
|
export const ideCommand = async (): Promise<SlashCommand> => {
|
|
const ideClient = await IdeClient.getInstance();
|
|
const currentIDE = ideClient.getCurrentIde();
|
|
if (!currentIDE) {
|
|
return {
|
|
name: 'ide',
|
|
get description() {
|
|
return t('manage IDE integration');
|
|
},
|
|
kind: CommandKind.BUILT_IN,
|
|
action: (): SlashCommandActionReturn =>
|
|
({
|
|
type: 'message',
|
|
messageType: 'error',
|
|
content: t(
|
|
'IDE integration is not supported in your current environment. To use this feature, run Qwen Code in one of these supported IDEs: VS Code or VS Code forks.',
|
|
),
|
|
}) as const,
|
|
};
|
|
}
|
|
|
|
const ideSlashCommand: SlashCommand = {
|
|
name: 'ide',
|
|
get description() {
|
|
return t('manage IDE integration');
|
|
},
|
|
kind: CommandKind.BUILT_IN,
|
|
subCommands: [],
|
|
};
|
|
|
|
const statusCommand: SlashCommand = {
|
|
name: 'status',
|
|
get description() {
|
|
return t('check status of IDE integration');
|
|
},
|
|
kind: CommandKind.BUILT_IN,
|
|
action: async (): Promise<SlashCommandActionReturn> => {
|
|
const { messageType, content } =
|
|
await getIdeStatusMessageWithFiles(ideClient);
|
|
return {
|
|
type: 'message',
|
|
messageType,
|
|
content,
|
|
} as const;
|
|
},
|
|
};
|
|
|
|
const installCommand: SlashCommand = {
|
|
name: 'install',
|
|
get description() {
|
|
const ideName = ideClient.getDetectedIdeDisplayName() ?? 'IDE';
|
|
return t('install required IDE companion for {{ideName}}', {
|
|
ideName,
|
|
});
|
|
},
|
|
kind: CommandKind.BUILT_IN,
|
|
action: async (context) => {
|
|
const installer = getIdeInstaller(currentIDE);
|
|
if (!installer) {
|
|
context.ui.addItem(
|
|
{
|
|
type: 'error',
|
|
text: `No installer is available for ${ideClient.getDetectedIdeDisplayName()}. Please install the '${QWEN_CODE_COMPANION_EXTENSION_NAME}' extension manually from the marketplace.`,
|
|
},
|
|
Date.now(),
|
|
);
|
|
return;
|
|
}
|
|
|
|
context.ui.addItem(
|
|
{
|
|
type: 'info',
|
|
text: `Installing IDE companion...`,
|
|
},
|
|
Date.now(),
|
|
);
|
|
|
|
const result = await installer.install();
|
|
context.ui.addItem(
|
|
{
|
|
type: result.success ? 'info' : 'error',
|
|
text: result.message,
|
|
},
|
|
Date.now(),
|
|
);
|
|
if (result.success) {
|
|
context.services.settings.setValue(
|
|
SettingScope.User,
|
|
'ide.enabled',
|
|
true,
|
|
);
|
|
// Poll for up to 5 seconds for the extension to activate.
|
|
for (let i = 0; i < 10; i++) {
|
|
await setIdeModeAndSyncConnection(context.services.config!, true);
|
|
if (
|
|
ideClient.getConnectionStatus().status ===
|
|
IDEConnectionStatus.Connected
|
|
) {
|
|
break;
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
}
|
|
|
|
const { messageType, content } = getIdeStatusMessage(ideClient);
|
|
if (messageType === 'error') {
|
|
context.ui.addItem(
|
|
{
|
|
type: messageType,
|
|
text: `Failed to automatically enable IDE integration. To fix this, run the CLI in a new terminal window.`,
|
|
},
|
|
Date.now(),
|
|
);
|
|
} else {
|
|
context.ui.addItem(
|
|
{
|
|
type: messageType,
|
|
text: content,
|
|
},
|
|
Date.now(),
|
|
);
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
const enableCommand: SlashCommand = {
|
|
name: 'enable',
|
|
get description() {
|
|
return t('enable IDE integration');
|
|
},
|
|
kind: CommandKind.BUILT_IN,
|
|
action: async (context: CommandContext) => {
|
|
context.services.settings.setValue(
|
|
SettingScope.User,
|
|
'ide.enabled',
|
|
true,
|
|
);
|
|
await setIdeModeAndSyncConnection(context.services.config!, true);
|
|
const { messageType, content } = getIdeStatusMessage(ideClient);
|
|
context.ui.addItem(
|
|
{
|
|
type: messageType,
|
|
text: content,
|
|
},
|
|
Date.now(),
|
|
);
|
|
},
|
|
};
|
|
|
|
const disableCommand: SlashCommand = {
|
|
name: 'disable',
|
|
get description() {
|
|
return t('disable IDE integration');
|
|
},
|
|
kind: CommandKind.BUILT_IN,
|
|
action: async (context: CommandContext) => {
|
|
context.services.settings.setValue(
|
|
SettingScope.User,
|
|
'ide.enabled',
|
|
false,
|
|
);
|
|
await setIdeModeAndSyncConnection(context.services.config!, false);
|
|
const { messageType, content } = getIdeStatusMessage(ideClient);
|
|
context.ui.addItem(
|
|
{
|
|
type: messageType,
|
|
text: content,
|
|
},
|
|
Date.now(),
|
|
);
|
|
},
|
|
};
|
|
|
|
const { status } = ideClient.getConnectionStatus();
|
|
const isConnected = status === IDEConnectionStatus.Connected;
|
|
|
|
if (isConnected) {
|
|
ideSlashCommand.subCommands = [statusCommand, disableCommand];
|
|
} else {
|
|
ideSlashCommand.subCommands = [
|
|
enableCommand,
|
|
statusCommand,
|
|
installCommand,
|
|
];
|
|
}
|
|
|
|
return ideSlashCommand;
|
|
};
|