Skip to content

Commit 6647615

Browse files
authored
Add command to display language server output panel (microsoft#7461)
* Add command to display ls output panel * Code review changes * Add tests
1 parent ca91f94 commit 6647615

5 files changed

Lines changed: 101 additions & 9 deletions

File tree

‎news/1 Enhancements/7459.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a command to show the `Language Server` output panel.

‎package.nls.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"python.command.python.refactorExtractMethod.title": "Extract Method",
1616
"python.command.python.viewOutput.title": "Show Output",
1717
"python.command.python.viewTestOutput.title": "Show Test Output",
18+
"python.command.python.viewLanguageServerOutput.title": "Show Language Server Output",
1819
"python.command.python.selectAndRunTestMethod.title": "Run Test Method ...",
1920
"python.command.python.selectAndDebugTestMethod.title": "Debug Test Method ...",
2021
"python.command.python.selectAndRunTestFile.title": "Run Test File ...",

‎src/client/activation/languageServer/outputChannel.ts‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,36 @@
44
'use strict';
55

66
import { inject, injectable } from 'inversify';
7-
import { IApplicationShell } from '../../common/application/types';
7+
import { IApplicationShell, ICommandManager } from '../../common/application/types';
8+
import '../../common/extensions';
89
import { IOutputChannel } from '../../common/types';
910
import { OutputChannelNames } from '../../common/utils/localize';
1011
import { ILanguageServerOutputChannel } from '../types';
1112

1213
@injectable()
1314
export class LanguageServerOutputChannel implements ILanguageServerOutputChannel {
1415
public output: IOutputChannel | undefined;
16+
private registered = false;
1517
constructor(
16-
@inject(IApplicationShell) private readonly appShell: IApplicationShell
18+
@inject(IApplicationShell) private readonly appShell: IApplicationShell,
19+
@inject(ICommandManager) private readonly commandManager: ICommandManager
1720
) { }
1821

1922
public get channel() {
2023
if (!this.output) {
2124
this.output = this.appShell.createOutputChannel(OutputChannelNames.languageServer());
25+
this.registerCommand().ignoreErrors();
2226
}
2327
return this.output;
2428
}
29+
private async registerCommand() {
30+
if (this.registered) {
31+
return;
32+
}
33+
this.registered = true;
34+
// This controls the visibility of the command used to display the LS Output panel.
35+
// We don't want to display it when Jedi is used instead of LS.
36+
await this.commandManager.executeCommand('setContext', 'python.hasLanguageServerOutputChannel', true);
37+
this.commandManager.registerCommand('python.viewLanguageServerOutput', () => this.output!.show(true));
38+
}
2539
}

‎src/client/common/application/commands.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu
7878
['revealLine']: [{ lineNumber: number; at: 'top' | 'center' | 'bottom' }];
7979
['python._loadLanguageServerExtension']: {}[];
8080
['python.SelectAndInsertDebugConfiguration']: [TextDocument, Position, CancellationToken];
81+
['python.viewLanguageServerOutput']: [];
8182
[Commands.Build_Workspace_Symbols]: [boolean, CancellationToken];
8283
[Commands.Sort_Imports]: [undefined, Uri];
8384
[Commands.Exec_In_Terminal]: [undefined, Uri];

‎src/test/activation/languageServer/outputChannel.unit.test.ts‎

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,113 @@
66
import { expect } from 'chai';
77
import * as TypeMoq from 'typemoq';
88
import { LanguageServerOutputChannel } from '../../../client/activation/languageServer/outputChannel';
9-
import { IApplicationShell } from '../../../client/common/application/types';
9+
import { IApplicationShell, ICommandManager } from '../../../client/common/application/types';
1010
import { IOutputChannel } from '../../../client/common/types';
11+
import { sleep } from '../../../client/common/utils/async';
1112
import { OutputChannelNames } from '../../../client/common/utils/localize';
1213

14+
// tslint:disable-next-line:max-func-body-length
1315
suite('Language Server Output Channel', () => {
1416
let appShell: TypeMoq.IMock<IApplicationShell>;
1517
let languageServerOutputChannel: LanguageServerOutputChannel;
16-
let output: IOutputChannel;
18+
let commandManager: TypeMoq.IMock<ICommandManager>;
19+
let output: TypeMoq.IMock<IOutputChannel>;
1720
setup(() => {
1821
appShell = TypeMoq.Mock.ofType<IApplicationShell>();
19-
output = TypeMoq.Mock.ofType<IOutputChannel>().object;
20-
languageServerOutputChannel = new LanguageServerOutputChannel(appShell.object);
22+
output = TypeMoq.Mock.ofType<IOutputChannel>();
23+
commandManager = TypeMoq.Mock.ofType<ICommandManager>();
24+
languageServerOutputChannel = new LanguageServerOutputChannel(appShell.object, commandManager.object);
2125
});
2226

2327
test('Create output channel if one does not exist before and return it', async () => {
2428
appShell
2529
.setup(a => a.createOutputChannel(OutputChannelNames.languageServer()))
26-
.returns(() => output)
30+
.returns(() => output.object)
2731
.verifiable(TypeMoq.Times.once());
2832
const channel = languageServerOutputChannel.channel;
2933
appShell.verifyAll();
3034
expect(channel).to.not.equal(undefined, 'Channel should not be undefined');
3135
});
3236

3337
test('Do not create output channel if one already exists', async () => {
34-
languageServerOutputChannel.output = output;
38+
languageServerOutputChannel.output = output.object;
3539
appShell
3640
.setup(a => a.createOutputChannel(TypeMoq.It.isAny()))
37-
.returns(() => output)
41+
.returns(() => output.object)
3842
.verifiable(TypeMoq.Times.never());
3943
const channel = languageServerOutputChannel.channel;
4044
appShell.verifyAll();
4145
expect(channel).to.not.equal(undefined, 'Channel should not be undefined');
4246
});
47+
test('Register Command to display output panel', async () => {
48+
appShell
49+
.setup(a => a.createOutputChannel(TypeMoq.It.isAny()))
50+
.returns(() => output.object)
51+
.verifiable(TypeMoq.Times.once());
52+
commandManager
53+
.setup(c => c.executeCommand(TypeMoq.It.isValue('setContext'),
54+
TypeMoq.It.isValue('python.hasLanguageServerOutputChannel'),
55+
TypeMoq.It.isValue(true)))
56+
.returns(() => Promise.resolve())
57+
.verifiable(TypeMoq.Times.once());
58+
commandManager
59+
.setup(c => c.registerCommand(TypeMoq.It.isValue('python.viewLanguageServerOutput'),
60+
TypeMoq.It.isAny()))
61+
.verifiable(TypeMoq.Times.once());
62+
63+
// Doesn't matter how many times we access channel propery.
64+
let channel = languageServerOutputChannel.channel;
65+
channel = languageServerOutputChannel.channel;
66+
channel = languageServerOutputChannel.channel;
67+
68+
await sleep(1);
69+
70+
appShell.verifyAll();
71+
commandManager.verifyAll();
72+
expect(channel).to.not.equal(undefined, 'Channel should not be undefined');
73+
});
74+
test('Display panel when invoking command python.viewLanguageServerOutput', async () => {
75+
let cmdCallback: Function | undefined;
76+
appShell
77+
.setup(a => a.createOutputChannel(TypeMoq.It.isAny()))
78+
.returns(() => output.object)
79+
.verifiable(TypeMoq.Times.once());
80+
commandManager
81+
.setup(c => c.executeCommand(TypeMoq.It.isValue('setContext'),
82+
TypeMoq.It.isValue('python.hasLanguageServerOutputChannel'),
83+
TypeMoq.It.isValue(true)))
84+
.returns(() => Promise.resolve())
85+
.verifiable(TypeMoq.Times.once());
86+
commandManager
87+
.setup(c => c.registerCommand(TypeMoq.It.isValue('python.viewLanguageServerOutput'),
88+
TypeMoq.It.isAny()))
89+
.callback((_: string, callback: Function) => cmdCallback = callback)
90+
.verifiable(TypeMoq.Times.once());
91+
output
92+
.setup(o => o.show(true))
93+
.verifiable(TypeMoq.Times.never());
94+
// Doesn't matter how many times we access channel propery.
95+
let channel = languageServerOutputChannel.channel;
96+
channel = languageServerOutputChannel.channel;
97+
channel = languageServerOutputChannel.channel;
98+
99+
await sleep(1);
100+
101+
appShell.verifyAll();
102+
commandManager.verifyAll();
103+
output.verifyAll();
104+
expect(channel).to.not.equal(undefined, 'Channel should not be undefined');
105+
expect(cmdCallback).to.not.equal(undefined, 'Command handler should not be undefined');
106+
107+
// Confirm panel is displayed when command handler is invoked.
108+
output.reset();
109+
output
110+
.setup(o => o.show(true))
111+
.verifiable(TypeMoq.Times.once());
112+
113+
// Invoke callback.
114+
cmdCallback!();
115+
116+
output.verifyAll();
117+
});
43118
});

0 commit comments

Comments
 (0)