* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as vscode from 'vscode';
import { buildCjmpProject } from './keelsBuild';
import { cleanCjmpProject } from './keelsClean';
import { disposeCjmpOutputChannel } from './cjmpOutputChannel';
import { createCjmpProject, executeSDK } from './keelsCreate';
import { LaunchEmulator } from './keelsEmulators';
import { KeelsDebugAdapterDescriptorFactory, KeelsConfigurationProvider } from './keelsRun';
import {
getAdbPath,
getPackageName,
getProjectType,
KeelsDeviceManager,
TimeDiff,
activateStripAnsi,
configDefaultEnvToSettings,
configureCjpmTomlPath
} from './utils';
import { execNativeCommand } from './commandExecutor';
import path from 'path';
import * as fs from 'fs';
import { DebugSession } from 'vscode';
import { RemotePlatform } from './types';
import {
cangjieBuildContextKey,
cjmpProjectContextKey,
getCjmpProjectWorkspaceFolder,
} from './cjmpProjectContext';
export async function activate(context: vscode.ExtensionContext) {
console.log('Extension "CJMP" is now active!');
activateStripAnsi();
const isCjmpProject = await updateProjectContexts();
const deviceManager = new KeelsDeviceManager(context, isCjmpProject);
const createCommand = vscode.commands.registerCommand('cjmp.createCjmpProject', async () => {
await createCjmpProject(context);
});
const launchCommand = vscode.commands.registerCommand('cjmp.launchEmulator', async () => {
await LaunchEmulator(context);
});
const buildCommand = vscode.commands.registerCommand('cjmp.build', async () => {
const isSdkReady = await executeSDK(context);
if (!isSdkReady) {
return;
}
await buildCjmpProject();
});
const cleanCommand = vscode.commands.registerCommand('cjmp.clean', async () => {
const isSdkReady = await executeSDK(context);
if (!isSdkReady) {
return;
}
await cleanCjmpProject();
});
context.subscriptions.push(
vscode.commands.registerCommand(
'cjmp.selectDevice',
deviceManager.showDevicePicker,
deviceManager,
),
);
const provider = new KeelsConfigurationProvider(deviceManager);
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('keels', provider));
const factory = new KeelsDebugAdapterDescriptorFactory(deviceManager, context);
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('keels', factory));
context.subscriptions.push(buildCommand);
context.subscriptions.push(cleanCommand);
context.subscriptions.push(createCommand);
context.subscriptions.push(launchCommand);
context.subscriptions.push({ dispose: disposeCjmpOutputChannel });
context.subscriptions.push(deviceManager);
context.subscriptions.push(vscode.debug.onDidTerminateDebugSession((session) => sessionTerminated(session)));
context.subscriptions.push(vscode.workspace.onDidChangeWorkspaceFolders(() => {
void updateProjectContexts();
}));
if (isCjmpProject) {
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration((event) => {
if (event.affectsConfiguration('CJMP.sdkPath')) {
void configDefaultEnvToSettings();
}
}));
await checkAndOpenTheDefaultFile();
await configDefaultEnvToSettings();
await configureCjpmTomlPath();
}
}
async function updateProjectContexts(): Promise<boolean> {
const isCjmpProject = getCjmpProjectWorkspaceFolder(vscode.workspace.workspaceFolders) !== undefined;
await vscode.commands.executeCommand('setContext', cjmpProjectContextKey, isCjmpProject);
await vscode.commands.executeCommand('setContext', cangjieBuildContextKey, !isCjmpProject);
return isCjmpProject;
}
async function sessionTerminated(session: DebugSession) {
if (session.type !== 'cangjieDebug') {
return;
}
const config = session.configuration;
if (config.configPlatform !== 'keels') {
return;
}
if (config.remotePlatform === RemotePlatform.ANDROID && config.deviceSerialNum && config.bundleName) {
const adbPath = getAdbPath();
if (!adbPath) {
return;
}
const adbCmd = process.platform === 'win32' ? `"${adbPath}" -s ${config.deviceSerialNum}` : `${adbPath.replace(/ /g, '\\ ')} -s ${config.deviceSerialNum}`;
await execNativeCommand(`${adbCmd} shell "run-as ${config.bundleName} sh -c 'pkill -9 lldb-server || true'"`);
}
}
export function deactivate() { }
async function checkAndOpenTheDefaultFile() {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders) {
return;
}
const projectPath = workspaceFolders[0].uri.fsPath;
const lockPath = path.join(projectPath, '.vscode', '.CJMP_create.lock');
if (fs.existsSync(lockPath)) {
setTimeout(async () => {
try {
const timeDiff = Date.now() - parseInt(fs.readFileSync(lockPath, 'utf8').trim());
if (timeDiff > TimeDiff) {
fs.unlinkSync(lockPath);
return;
}
const initialSourceFilePath = await resolveInitialSourceFilePath(projectPath);
const document = await vscode.workspace.openTextDocument(
vscode.Uri.file(initialSourceFilePath),
);
await vscode.window.showTextDocument(document);
fs.unlinkSync(lockPath);
} catch (error) {
vscode.window.showErrorMessage(`Open initial Cangjie source file failed: ${error}`);
}
}, 300);
}
}
export async function resolveInitialSourceFilePath(projectPath: string): Promise<string> {
const projectType = getProjectType();
switch (projectType) {
case 'logic-module': {
const packageName = await getPackageName();
if (!packageName) {
throw new Error('Package name is missing from project.conf.');
}
return path.join(projectPath, 'logic-module', 'common', `${packageName}.cj`);
}
case 'library': {
const packageName = await getPackageName();
if (!packageName) {
throw new Error('Package name is missing from project.conf.');
}
return path.join(projectPath, 'lib', 'common', `${packageName}.cj`);
}
case 'app':
case 'module':
return path.join(projectPath, 'lib', 'common', 'main.cj');
default:
throw new Error(`Unsupported CJMP project type: ${projectType ?? 'unknown'}.`);
}
}