Skip to content

Commit 802e364

Browse files
author
Sander Ronde
committed
Resolve current config & show in language status panel
1 parent 7c38588 commit 802e364

File tree

11 files changed

+348
-66
lines changed

11 files changed

+348
-66
lines changed

client/src/extension.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getEditorConfiguration,
1010
registerEditorConfigurationListener,
1111
} from './lib/editorConfig';
12+
import { ConfigResolveLanguageStatus } from './notificationReceivers/configResolveLanguageStatus';
1213
import {
1314
getInstallationConfig,
1415
writeInstallationConfig,
@@ -83,7 +84,7 @@ async function startLanguageServer(
8384

8485
export async function activate(context: ExtensionContext): Promise<void> {
8586
log(context, CLIENT_PREFIX, 'Initializing PHPStan extension');
86-
createOutputChannel();
87+
const outputChannel = createOutputChannel();
8788

8889
const telemetry = new Telemetry();
8990
telemetry.report(context);
@@ -93,8 +94,9 @@ export async function activate(context: ExtensionContext): Promise<void> {
9394
const errorManager = new ErrorManager(client);
9495
const proManager = new PHPStanProManager(client);
9596
const zombieKiller = new ZombieKiller(client, context);
97+
const configResolveLanguageStatus = new ConfigResolveLanguageStatus(client);
9698

97-
registerListeners(context, client, errorManager, proManager);
99+
registerListeners(context, client, errorManager, proManager, outputChannel);
98100
registerEditorConfigurationListener(context, client);
99101
registerLogMessager(context, client);
100102
context.subscriptions.push(
@@ -103,7 +105,9 @@ export async function activate(context: ExtensionContext): Promise<void> {
103105
errorManager,
104106
proManager,
105107
zombieKiller,
106-
telemetry
108+
telemetry,
109+
configResolveLanguageStatus,
110+
outputChannel
107111
);
108112

109113
let wasReady = false;

client/src/lib/commands.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export function registerListeners(
1717
context: vscode.ExtensionContext,
1818
client: LanguageClient,
1919
errorManager: ErrorManager,
20-
phpstanProManager: PHPStanProManager
20+
phpstanProManager: PHPStanProManager,
21+
outputChannel: vscode.OutputChannel
2122
): void {
2223
context.subscriptions.push(
2324
autoRegisterCommand(
@@ -140,6 +141,14 @@ export function registerListeners(
140141
)
141142
);
142143

144+
context.subscriptions.push(
145+
autoRegisterCommand(
146+
Commands.SHOW_OUTPUT_CHANNEL,
147+
() => outputChannel.show(),
148+
commands
149+
)
150+
);
151+
143152
context.subscriptions.push(
144153
client.onNotification(
145154
commandNotification,

client/src/lib/log.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import { ExtensionMode, window } from 'vscode';
55

66
let channel: OutputChannel | null;
77

8-
export function createOutputChannel(): void {
8+
export function createOutputChannel(): OutputChannel {
99
channel = window.createOutputChannel('PHPStan Client');
10+
return channel;
1011
}
1112

1213
export function registerLogMessager(

client/src/lib/requestChannels.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import type {
2+
ConfigResolveRequestType,
3+
FindFilesRequestType,
24
InitRequestType,
35
TestRunRequestType,
46
} from '../../../shared/requestChannels';
@@ -16,3 +18,15 @@ export const testRunRequest = new RequestType<
1618
TestRunRequestType['response'],
1719
TestRunRequestType['error']
1820
>(RequestChannel.TEST_RUN);
21+
22+
export const configResolveRequest = new RequestType<
23+
ConfigResolveRequestType['request'],
24+
ConfigResolveRequestType['response'],
25+
ConfigResolveRequestType['error']
26+
>(RequestChannel.CONFIG_RESOLVE);
27+
28+
export const findFilesRequest = new RequestType<
29+
FindFilesRequestType['request'],
30+
FindFilesRequestType['response'],
31+
FindFilesRequestType['error']
32+
>(RequestChannel.FIND_FILES);
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import {
2+
languages,
3+
type Disposable,
4+
window,
5+
CancellationTokenSource,
6+
workspace,
7+
} from 'vscode';
8+
import { configResolveRequest, findFilesRequest } from '../lib/requestChannels';
9+
import type { FindFilesRequestType } from '../../../shared/requestChannels';
10+
import type { LanguageClient } from 'vscode-languageclient/node';
11+
import { Commands } from '../../../shared/commands/defs';
12+
import type { Command } from 'vscode';
13+
import { Uri } from 'vscode';
14+
import path from 'path';
15+
16+
export class ConfigResolveLanguageStatus implements Disposable {
17+
private _disposables: Disposable[] = [];
18+
private _languageStatus = languages.createLanguageStatusItem(
19+
'phpstan.languageStatusItem',
20+
[{ language: 'php' }, { pattern: '**/*.neon' }]
21+
);
22+
private _outstandingTokens = new Set<CancellationTokenSource>();
23+
24+
public constructor(private readonly _client: LanguageClient) {
25+
this._languageStatus.name = 'PHPStan';
26+
this._disposables.push(this._languageStatus);
27+
28+
this._disposables.push(
29+
_client.onRequest(
30+
findFilesRequest,
31+
async (params): Promise<FindFilesRequestType['response']> => {
32+
return {
33+
files: (await workspace.findFiles(params.pattern)).map(
34+
(file) => file.toString()
35+
),
36+
};
37+
}
38+
)
39+
);
40+
this._disposables.push(
41+
window.onDidChangeActiveTextEditor((editor) => {
42+
this._outstandingTokens.forEach((token) => token.cancel());
43+
this._outstandingTokens.clear();
44+
45+
if (!editor) {
46+
// Should not be visible
47+
this._setStatus({
48+
text: 'PHPStan resolving config...',
49+
command: undefined,
50+
busy: true,
51+
});
52+
return;
53+
}
54+
void this._update(editor.document.uri);
55+
})
56+
);
57+
}
58+
59+
private _setStatus(config: {
60+
text: string;
61+
command: Command | undefined;
62+
busy: boolean;
63+
}): void {
64+
this._languageStatus.text = config.text;
65+
this._languageStatus.command = config.command;
66+
this._languageStatus.busy = config.busy ?? false;
67+
}
68+
69+
private async _update(uri: Uri): Promise<void> {
70+
const cancelToken = new CancellationTokenSource();
71+
this._outstandingTokens.add(cancelToken);
72+
73+
this._setStatus({
74+
text: 'PHPStan resolving config...',
75+
command: undefined,
76+
busy: true,
77+
});
78+
const result = await this._client.sendRequest(
79+
configResolveRequest,
80+
{
81+
uri: uri.toString(),
82+
},
83+
cancelToken.token
84+
);
85+
86+
this._languageStatus.busy = false;
87+
if (result.uri) {
88+
const configUri = Uri.parse(result.uri);
89+
this._setStatus({
90+
text: path.basename(configUri.fsPath),
91+
busy: false,
92+
command: {
93+
title: 'Open config file',
94+
command: 'vscode.open',
95+
arguments: [configUri],
96+
},
97+
});
98+
} else {
99+
this._setStatus({
100+
text: 'PHPStan (no config found)',
101+
busy: false,
102+
command: {
103+
title: 'Show output channel',
104+
command: Commands.SHOW_OUTPUT_CHANNEL,
105+
arguments: [],
106+
},
107+
});
108+
}
109+
}
110+
111+
public dispose(): void {
112+
this._disposables.forEach((disposable) => void disposable.dispose());
113+
}
114+
}

server/src/lib/configResolve.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import type { ConfigResolveRequestType } from '../../../shared/requestChannels';
2+
import { configResolveRequest, findFilesRequest } from './requestChannels';
3+
import type { Disposable } from 'vscode-languageserver';
4+
import { ParsedConfigFile } from '../../../shared/neon';
5+
import { getEditorConfiguration } from './editorConfig';
6+
import type { ClassConfig } from './types';
7+
import { URI } from 'vscode-uri';
8+
import path from 'path';
9+
10+
interface Config {
11+
uri: URI;
12+
file: ParsedConfigFile;
13+
}
14+
15+
export class ConfigResolver implements Disposable {
16+
private readonly _disposables: Disposable[] = [];
17+
private _configs: Config[] | undefined;
18+
19+
public constructor(private readonly _classConfig: ClassConfig) {
20+
this._disposables.push(
21+
this._classConfig.connection.onRequest(
22+
configResolveRequest,
23+
async (
24+
params
25+
): Promise<ConfigResolveRequestType['response']> => {
26+
return {
27+
uri:
28+
(
29+
await this.resolveConfig(URI.parse(params.uri))
30+
)?.uri.toString() ?? null,
31+
};
32+
}
33+
)
34+
);
35+
}
36+
37+
private async _findConfigs(): Promise<Config[]> {
38+
if (!this._configs) {
39+
const editorConfig = await getEditorConfiguration(
40+
this._classConfig
41+
);
42+
const configFilePaths = editorConfig.configFile
43+
.split(',')
44+
.map((configFile) => path.basename(configFile.trim()));
45+
const findFilesResult =
46+
await this._classConfig.connection.sendRequest(
47+
findFilesRequest,
48+
{ pattern: `**/{${configFilePaths.join(',')}}` }
49+
);
50+
const fileURIs = findFilesResult.files.map((file) =>
51+
URI.parse(file)
52+
);
53+
this._configs = await Promise.all(
54+
fileURIs.map(async (fileURI) => ({
55+
uri: fileURI,
56+
file: await ParsedConfigFile.from(fileURI.fsPath),
57+
}))
58+
);
59+
}
60+
return this._configs;
61+
}
62+
63+
public async resolveConfig(filePath: URI): Promise<Config | null> {
64+
const configs = await this._findConfigs();
65+
for (const config of configs) {
66+
if (config.file.isInPaths(filePath.fsPath)) {
67+
return config;
68+
}
69+
}
70+
return null;
71+
}
72+
73+
public dispose(): void {
74+
this._disposables.forEach((d) => d.dispose());
75+
}
76+
}

server/src/lib/requestChannels.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import type {
2+
ConfigResolveRequestType,
3+
FindFilesRequestType,
24
InitRequestType,
35
TestRunRequestType,
46
} from '../../../shared/requestChannels';
@@ -16,3 +18,15 @@ export const testRunRequest = new RequestType<
1618
TestRunRequestType['response'],
1719
TestRunRequestType['error']
1820
>(RequestChannel.TEST_RUN);
21+
22+
export const configResolveRequest = new RequestType<
23+
ConfigResolveRequestType['request'],
24+
ConfigResolveRequestType['response'],
25+
ConfigResolveRequestType['error']
26+
>(RequestChannel.CONFIG_RESOLVE);
27+
28+
export const findFilesRequest = new RequestType<
29+
FindFilesRequestType['request'],
30+
FindFilesRequestType['response'],
31+
FindFilesRequestType['error']
32+
>(RequestChannel.FIND_FILES);

server/src/server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { ProviderCheckHooks } from './providers/providerUtil';
2626
import type { DocumentManager } from './lib/documentManager';
2727
import { getEditorConfiguration } from './lib/editorConfig';
2828
import type { PHPStanVersion } from './start/getVersion';
29+
import { ConfigResolver } from './lib/configResolve';
2930
import { initRequest } from './lib/requestChannels';
3031
import { getVersion } from './start/getVersion';
3132
import type { ClassConfig } from './lib/types';
@@ -123,6 +124,7 @@ async function main(): Promise<void> {
123124
version,
124125
editorConfigOverride: editorConfigOverride,
125126
};
127+
disposables.push(new ConfigResolver(classConfig));
126128

127129
// Check version
128130
void getVersion(classConfig).then((result) => {

shared/commands/defs.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export enum Commands {
1212
PREVIOUS_ERROR = 'phpstan.previousError',
1313
OPEN_PHPSTAN_PRO = 'phpstan.openPhpstanPro',
1414
LAUNCH_SETUP = 'phpstan.launchSetup',
15+
SHOW_OUTPUT_CHANNEL = 'phpstan.showOutputChannel',
1516
}
1617

1718
export const commands: Record<Commands, CommandDefinition> = {
@@ -43,6 +44,10 @@ export const commands: Record<Commands, CommandDefinition> = {
4344
title: 'Launch setup',
4445
inCommandPalette: true,
4546
},
47+
[Commands.SHOW_OUTPUT_CHANNEL]: {
48+
title: 'Show output channel',
49+
inCommandPalette: false,
50+
},
4651
};
4752

4853
export const config = {
@@ -82,7 +87,7 @@ export const config = {
8287
'phpstan.neon,phpstan.neon.dist',
8388
],
8489
description:
85-
'Path to the config file (use a comma-separated list to resolve in order)',
90+
'Filename or path to the config file (use a comma-separated list to resolve in order)',
8691
},
8792
},
8893
'phpstan.paths': {

0 commit comments

Comments
 (0)