Skip to content

Commit d411287

Browse files
authored
More fixes collected from various extensions built using this template. (#100)
* Report error in logs when Python < 3.7 * Do not add debugger script when packaging VSIX * Improve logging support
1 parent eb1f0a0 commit d411287

File tree

9 files changed

+100
-49
lines changed

9 files changed

+100
-49
lines changed

.vscodeignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ noxfile.py
2020
.pytest_cache/**
2121
.pylintrc
2222
**/requirements.txt
23-
**/requirements.in
23+
**/requirements.in
24+
**/tool/_debug_server.py

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ After finishing the getting started part, this template would have added the fol
4848
1. Following triggers for extension activation:
4949
- On Language `python`.
5050
- On File with `.py` extension found in the opened workspace.
51-
- On Command `mytool.restart`.
51+
1. Following commands are registered:
52+
- `mytool.restart`: Restarts the language server.
5253
1. Output Channel for logging `Output` > `My Tool`
5354

5455
## Adding features from your tool
@@ -82,6 +83,12 @@ To debug only TypeScript code, use `Debug Extension` debug config.
8283

8384
To debug a already running server or in production server, use `Python Attach`, and select the process that is running `lsp_server.py`.
8485

86+
## Logging and Logs
87+
88+
The template creates a logging Output channel that can be found under `Output` > `mytool` panel. You can control the log level running the `Developer: Set Log Level...` command from the Command Pallet, and selecting your extension from the list. It should be listed using the display name for your tool. You can also set the global log level, and that will apply to all extensions and the editor.
89+
90+
If you need logs that involve messages between the Language Client and Language Server, you can set `"mytool.server.trace": "verbose"`, to get the messaging logs. These logs are also available `Output` > `mytool` panel.
91+
8592
## Adding new Settings or Commands
8693

8794
You can add new settings by adding details for the settings in `package.json` file. To pass this configuration to your python tool server (i.e, `lsp_server.py`) update the `settings.ts` as need. There are examples of different types of settings in that file that you can base your new settings on.

src/common/log/logging.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class OutputChannelLogger {
99
constructor(private readonly channel: LogOutputChannel) {}
1010

1111
public traceLog(...data: Arguments): void {
12-
this.channel.trace(util.format(...data));
12+
this.channel.appendLine(util.format(...data));
1313
}
1414

1515
public traceError(...data: Arguments): void {

src/common/python.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,17 @@ export async function initializePython(disposables: Disposable[]): Promise<void>
253253
}
254254
}
255255

256+
export async function resolveInterpreter(interpreter: string[]): Promise<ResolvedEnvironment | undefined> {
257+
const api = await getPythonExtensionAPI();
258+
return api?.environments.resolveEnvironment(interpreter[0]);
259+
}
260+
256261
export async function getInterpreterDetails(resource?: Uri): Promise<IInterpreterDetails> {
257262
const api = await getPythonExtensionAPI();
258263
const environment = await api?.environments.resolveEnvironment(
259264
api?.environments.getActiveEnvironmentPath(resource),
260265
);
261-
if (environment?.executable.uri) {
266+
if (environment?.executable.uri && checkVersion(environment)) {
262267
return { path: [environment?.executable.uri.fsPath], resource };
263268
}
264269
return { path: undefined, resource };
@@ -273,3 +278,14 @@ export async function runPythonExtensionCommand(command: string, ...rest: any[])
273278
await activateExtension();
274279
return await commands.executeCommand(command, ...rest);
275280
}
281+
282+
export function checkVersion(resolved: ResolvedEnvironment | undefined): boolean {
283+
const version = resolved?.version;
284+
if (version?.major === 3 && version?.minor >= 7) {
285+
return true;
286+
}
287+
traceError(`Python version ${version?.major}.${version?.minor} is not supported.`);
288+
traceError(`Selected python path: ${resolved?.executable.uri?.fsPath}`);
289+
traceError('Supported versions are 3.7 and above.');
290+
return false;
291+
}

src/common/server.ts

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
import { Disposable, LogOutputChannel, WorkspaceFolder } from 'vscode';
4+
import * as fsapi from 'fs-extra';
5+
import { Disposable, env, LogOutputChannel } from 'vscode';
56
import { State } from 'vscode-languageclient';
67
import {
78
LanguageClient,
@@ -31,6 +32,7 @@ async function createServer(
3132
// Set debugger path needed for debugging python code.
3233
const newEnv = { ...process.env };
3334
const debuggerPath = await getDebuggerPath();
35+
const isDebugScript = await fsapi.pathExists(DEBUG_SERVER_SCRIPT_PATH);
3436
if (newEnv.USE_DEBUGPY && debuggerPath) {
3537
newEnv.DEBUGPY_PATH = debuggerPath;
3638
} else {
@@ -44,7 +46,7 @@ async function createServer(
4446
newEnv.LS_SHOW_NOTIFICATION = settings.showNotifications;
4547

4648
const args =
47-
newEnv.USE_DEBUGPY === 'False'
49+
newEnv.USE_DEBUGPY === 'False' || !isDebugScript
4850
? settings.interpreter.slice(1).concat([SERVER_SCRIPT_PATH])
4951
: settings.interpreter.slice(1).concat([DEBUG_SERVER_SCRIPT_PATH]);
5052
traceInfo(`Server run command: ${[command, ...args].join(' ')}`);
@@ -90,14 +92,6 @@ export async function restartServer(
9092
}
9193
const projectRoot = await getProjectRoot();
9294
const workspaceSetting = await getWorkspaceSettings(serverId, projectRoot, true);
93-
if (workspaceSetting.interpreter.length === 0) {
94-
traceError(
95-
'Python interpreter missing:\r\n' +
96-
'[Option 1] Select python interpreter using the ms-python.python.\r\n' +
97-
`[Option 2] Set an interpreter using "${serverId}.interpreter" setting.\r\n`,
98-
);
99-
return undefined;
100-
}
10195

10296
const newLSClient = await createServer(workspaceSetting, serverId, serverName, outputChannel, {
10397
settings: await getExtensionSettings(serverId, true),
@@ -118,16 +112,15 @@ export async function restartServer(
118112
break;
119113
}
120114
}),
121-
outputChannel.onDidChangeLogLevel((e) => {
122-
newLSClient.setTrace(getLSClientTraceLevel(e));
123-
}),
124115
);
125116
try {
126117
await newLSClient.start();
127118
} catch (ex) {
128119
traceError(`Server: Start failed: ${ex}`);
129120
return undefined;
130121
}
131-
newLSClient.setTrace(getLSClientTraceLevel(outputChannel.logLevel));
122+
123+
const level = getLSClientTraceLevel(outputChannel.logLevel, env.logLevel);
124+
await newLSClient.setTrace(level);
132125
return newLSClient;
133126
}

src/common/settings.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ export async function getGlobalSettings(namespace: string, includeInterpreter?:
8989

9090
export function checkIfConfigurationChanged(e: ConfigurationChangeEvent, namespace: string): boolean {
9191
const settings = [
92-
`${namespace}.trace`,
9392
`${namespace}.args`,
9493
`${namespace}.path`,
9594
`${namespace}.interpreter`,

src/common/utilities.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,38 @@
33

44
import * as fs from 'fs-extra';
55
import * as path from 'path';
6-
import { env, LogLevel, Uri, WorkspaceFolder } from 'vscode';
7-
import { Trace, TraceValues } from 'vscode-jsonrpc/node';
6+
import { LogLevel, Uri, WorkspaceFolder } from 'vscode';
7+
import { Trace } from 'vscode-jsonrpc/node';
88
import { getWorkspaceFolders } from './vscodeapi';
99

10-
export function getLSClientTraceLevel(logLevel: LogLevel): Trace {
10+
function logLevelToTrace(logLevel: LogLevel): Trace {
1111
switch (logLevel) {
1212
case LogLevel.Error:
1313
case LogLevel.Warning:
1414
case LogLevel.Info:
1515
return Trace.Messages;
16+
1617
case LogLevel.Debug:
18+
case LogLevel.Trace:
1719
return Trace.Verbose;
20+
1821
case LogLevel.Off:
1922
default:
2023
return Trace.Off;
2124
}
2225
}
2326

27+
export function getLSClientTraceLevel(channelLogLevel: LogLevel, globalLogLevel: LogLevel): Trace {
28+
if (channelLogLevel === LogLevel.Off) {
29+
return logLevelToTrace(globalLogLevel);
30+
}
31+
if (globalLogLevel === LogLevel.Off) {
32+
return logLevelToTrace(channelLogLevel);
33+
}
34+
const level = logLevelToTrace(channelLogLevel <= globalLogLevel ? channelLogLevel : globalLogLevel);
35+
return level;
36+
}
37+
2438
export async function getProjectRoot(): Promise<WorkspaceFolder> {
2539
const workspaces: readonly WorkspaceFolder[] = getWorkspaceFolders();
2640
if (workspaces.length === 0) {

src/extension.ts

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@
33

44
import * as vscode from 'vscode';
55
import { LanguageClient } from 'vscode-languageclient/node';
6-
import { registerLogger, traceLog, traceVerbose } from './common/log/logging';
6+
import { registerLogger, traceError, traceLog, traceVerbose } from './common/log/logging';
77
import {
8+
checkVersion,
89
getInterpreterDetails,
910
initializePython,
1011
onDidChangePythonInterpreter,
11-
runPythonExtensionCommand,
12+
resolveInterpreter,
1213
} from './common/python';
1314
import { restartServer } from './common/server';
1415
import { checkIfConfigurationChanged, getInterpreterFromSetting } from './common/settings';
1516
import { loadServerDefaults } from './common/setup';
16-
import { getProjectRoot } from './common/utilities';
17+
import { getLSClientTraceLevel } from './common/utilities';
1718
import { createOutputChannel, onDidChangeConfiguration, registerCommand } from './common/vscodeapi';
1819

1920
let lsClient: LanguageClient | undefined;
@@ -28,39 +29,60 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
2829
const outputChannel = createOutputChannel(serverName);
2930
context.subscriptions.push(outputChannel, registerLogger(outputChannel));
3031

31-
traceLog(`Name: ${serverName}`);
32+
const changeLogLevel = async (c: vscode.LogLevel, g: vscode.LogLevel) => {
33+
const level = getLSClientTraceLevel(c, g);
34+
await lsClient?.setTrace(level);
35+
};
36+
37+
context.subscriptions.push(
38+
outputChannel.onDidChangeLogLevel(async (e) => {
39+
await changeLogLevel(e, vscode.env.logLevel);
40+
}),
41+
vscode.env.onDidChangeLogLevel(async (e) => {
42+
await changeLogLevel(outputChannel.logLevel, e);
43+
}),
44+
);
45+
46+
// Log Server information
47+
traceLog(`Name: ${serverInfo.name}`);
3248
traceLog(`Module: ${serverInfo.module}`);
33-
traceVerbose(`Configuration: ${JSON.stringify(serverInfo)}`);
49+
traceVerbose(`Full Server Info: ${JSON.stringify(serverInfo)}`);
3450

3551
const runServer = async () => {
36-
lsClient = await restartServer(serverId, serverName, outputChannel, lsClient);
52+
const interpreter = getInterpreterFromSetting(serverId);
53+
if (interpreter && interpreter.length > 0 && checkVersion(await resolveInterpreter(interpreter))) {
54+
traceVerbose(`Using interpreter from ${serverInfo.module}.interpreter: ${interpreter.join(' ')}`);
55+
lsClient = await restartServer(serverId, serverName, outputChannel, lsClient);
56+
return;
57+
}
58+
59+
const interpreterDetails = await getInterpreterDetails();
60+
if (interpreterDetails.path) {
61+
traceVerbose(`Using interpreter from Python extension: ${interpreterDetails.path.join(' ')}`);
62+
lsClient = await restartServer(serverId, serverName, outputChannel, lsClient);
63+
return;
64+
}
65+
66+
traceError(
67+
'Python interpreter missing:\r\n' +
68+
'[Option 1] Select python interpreter using the ms-python.python.\r\n' +
69+
`[Option 2] Set an interpreter using "${serverId}.interpreter" setting.\r\n` +
70+
'Please use Python 3.7 or greater.',
71+
);
3772
};
3873

3974
context.subscriptions.push(
4075
onDidChangePythonInterpreter(async () => {
4176
await runServer();
4277
}),
43-
);
44-
45-
context.subscriptions.push(
46-
registerCommand(`${serverId}.restart`, async () => {
47-
const interpreter = getInterpreterFromSetting(serverId);
48-
const interpreterDetails = await getInterpreterDetails();
49-
if (interpreter?.length || interpreterDetails.path) {
50-
await runServer();
51-
} else {
52-
const projectRoot = await getProjectRoot();
53-
runPythonExtensionCommand('python.triggerEnvSelection', projectRoot.uri);
54-
}
55-
}),
56-
);
57-
58-
context.subscriptions.push(
5978
onDidChangeConfiguration(async (e: vscode.ConfigurationChangeEvent) => {
6079
if (checkIfConfigurationChanged(e, serverId)) {
6180
await runServer();
6281
}
6382
}),
83+
registerCommand(`${serverId}.restart`, async () => {
84+
await runServer();
85+
}),
6486
);
6587

6688
setImmediate(async () => {

tsconfig.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
"lib": ["ES2020"],
66
"sourceMap": true,
77
"rootDir": "src",
8-
"strict": true /* enable all strict type-checking options */
9-
/* Additional Checks */
10-
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
11-
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
12-
// "noUnusedParameters": true, /* Report errors on unused parameters. */
8+
"strict": true,
9+
"noImplicitReturns": true,
10+
"noFallthroughCasesInSwitch": true,
11+
"noUnusedParameters": true
1312
}
1413
}

0 commit comments

Comments
 (0)