Skip to content

Commit 2d36283

Browse files
glennsartiRobert Holt
authored and
Robert Holt
committed
(GH-1413) Update grammar parsing for vscode-textmate v4 module (#1414)
* (maint) Add a logging interface Previously the logging object had no interface which made testing difficult. This commit adds a new interface which new features can use, which makes mocking the logging object possible. * (GH-1413) Update grammar parsing for vscode-textmate v4 module VS Code 1.25 has updated the imported vsode-textmate module from v3 to v4, which introduced some breaking changes. This commit updates the grammar loader; * Uses a thenable grammar parser, as V4 of the module is all asynchronous * Uses a simple method detection technique on the Registry object to determine if we're using v3 or v4+ of the textmate module, and then branch the code accordingly. Note that this functionality can only be tested manually due to how the vscode-textmate module is incorporated into VSCode and the test-suite. * Update Folding.ts Add newline between copyright header and body
1 parent a0ee7a1 commit 2d36283

File tree

4 files changed

+48
-36
lines changed

4 files changed

+48
-36
lines changed

src/features/Folding.ts

+31-19
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
* Copyright (C) Microsoft Corporation. All rights reserved.
33
*--------------------------------------------------------*/
44

5+
import fs = require("fs");
56
import * as path from "path";
67
import * as vscode from "vscode";
78
import {
89
DocumentSelector,
910
LanguageClient,
1011
} from "vscode-languageclient";
1112
import { IFeature } from "../feature";
12-
import { Logger } from "../logging";
13+
import { ILogger } from "../logging";
1314
import * as Settings from "../settings";
1415

1516
/**
@@ -497,23 +498,26 @@ export class FoldingFeature implements IFeature {
497498
* @param logger The logging object to send messages to
498499
* @param documentSelector documentSelector object for this Folding Provider
499500
*/
500-
constructor(private logger: Logger, documentSelector: DocumentSelector) {
501-
const grammar: IGrammar = this.grammar(logger);
502-
501+
constructor(private logger: ILogger, documentSelector: DocumentSelector) {
503502
const settings = Settings.load();
504503
if (!(settings.codeFolding && settings.codeFolding.enable)) { return; }
505504

506-
// If the PowerShell grammar is not available for some reason, don't register a folding provider,
507-
// which reverts VSCode to the default indentation style folding
508-
if (grammar == null) {
509-
logger.writeWarning("Unable to load the PowerShell grammar file");
510-
return;
511-
}
505+
this.loadPSGrammar(logger)
506+
.then((grammar) => {
507+
// If the PowerShell grammar is not available for some reason, don't register a folding provider,
508+
// which reverts VSCode to the default indentation style folding
509+
if (!grammar) {
510+
logger.writeWarning("Unable to load the PowerShell grammar file");
511+
return;
512+
}
512513

513-
this.foldingProvider = new FoldingProvider(grammar);
514-
vscode.languages.registerFoldingRangeProvider(documentSelector, this.foldingProvider);
514+
this.foldingProvider = new FoldingProvider(grammar);
515+
vscode.languages.registerFoldingRangeProvider(documentSelector, this.foldingProvider);
515516

516-
logger.write("Syntax Folding Provider registered");
517+
logger.write("Syntax Folding Provider registered");
518+
}, (err) => {
519+
this.logger.writeError(`Failed to load grammar file - error: ${err}`);
520+
});
517521
}
518522

519523
/* dispose() is required by the IFeature interface, but is not required by this feature */
@@ -527,7 +531,7 @@ export class FoldingFeature implements IFeature {
527531
* @param logger The logging object to send messages to
528532
* @returns A grammar parser for the PowerShell language is succesful or undefined if an error occured
529533
*/
530-
public grammar(logger: Logger): IGrammar {
534+
public loadPSGrammar(logger: ILogger): Thenable<IGrammar> {
531535
const tm = this.getCoreNodeModule("vscode-textmate", logger);
532536
if (tm == null) { return undefined; }
533537
logger.writeDiagnostic(`Loaded the vscode-textmate module`);
@@ -537,10 +541,18 @@ export class FoldingFeature implements IFeature {
537541
const grammarPath = this.powerShellGrammarPath();
538542
if (grammarPath == null) { return undefined; }
539543
logger.writeDiagnostic(`PowerShell grammar file specified as ${grammarPath}`);
540-
try {
541-
return registry.loadGrammarFromPathSync(grammarPath);
542-
} catch (err) {
543-
logger.writeError(`Error while loading the PowerShell grammar file at ${grammarPath}`, err);
544+
545+
// Branching for the different vscode-textmate modules
546+
if ("loadGrammarFromPathSync" in registry) {
547+
// V3 of the module allows synchronous loading of a grammar
548+
return new Promise( (grammar) => {
549+
return registry.loadGrammarFromPathSync(grammarPath);
550+
});
551+
} else {
552+
// However in V4+ this is async only
553+
const content = fs.readFileSync(grammarPath);
554+
const rawGrammar = tm.parseRawGrammar(content.toString(), grammarPath);
555+
return registry.addGrammar(rawGrammar);
544556
}
545557
}
546558

@@ -552,7 +564,7 @@ export class FoldingFeature implements IFeature {
552564
* @param logger The logging object to send messages to
553565
* @returns The required module, or null if the module cannot be required
554566
*/
555-
private getCoreNodeModule(moduleName: string, logger: Logger) {
567+
private getCoreNodeModule(moduleName: string, logger: ILogger) {
556568
// Attempt to load the module from known locations
557569
const loadLocations: string[] = [
558570
`${vscode.env.appRoot}/node_modules.asar/${moduleName}`,

src/logging.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,19 @@ export enum LogLevel {
1616
Error,
1717
}
1818

19-
export class Logger {
19+
/** Interface for logging operations. New features should use this interface for the "type" of logger.
20+
* This will allow for easy mocking of the logger during unit tests.
21+
*/
22+
export interface ILogger {
23+
write(message: string, ...additionalMessages: string[]);
24+
writeDiagnostic(message: string, ...additionalMessages: string[]);
25+
writeVerbose(message: string, ...additionalMessages: string[]);
26+
writeWarning(message: string, ...additionalMessages: string[]);
27+
writeAndShowWarning(message: string, ...additionalMessages: string[]);
28+
writeError(message: string, ...additionalMessages: string[]);
29+
}
30+
31+
export class Logger implements ILogger {
2032

2133
public logBasePath: string;
2234
public logSessionPath: string;

test/features/folding.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ function assertFoldingRegions(result, expected): void {
2424

2525
suite("Features", () => {
2626

27-
suite("Folding Provider", () => {
27+
suite("Folding Provider", async () => {
2828
const logger: MockLogger = new MockLogger();
2929
const mockSelector: DocumentSelector = [
3030
{ language: "powershell", scheme: "file" },
3131
];
32-
const psGrammar = (new folding.FoldingFeature(logger, mockSelector)).grammar(logger);
32+
const psGrammar = await (new folding.FoldingFeature(logger, mockSelector)).loadPSGrammar(logger);
3333
const provider = (new folding.FoldingProvider(psGrammar));
3434

3535
test("Can detect the PowerShell Grammar", () => {

test/test_utils.ts

+2-14
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,9 @@
22
* Copyright (C) Microsoft Corporation. All rights reserved.
33
*--------------------------------------------------------*/
44

5-
import { Logger, LogLevel } from "../src/logging";
6-
7-
export class MockLogger extends Logger {
8-
// Note - This is not a true mock as the constructor is inherited and causes errors due to trying load
9-
// the "PowerShell Extension Logs" multiple times. Ideally logging should be via an interface and then
10-
// we can mock correctly.
11-
12-
public dispose() { return undefined; }
13-
14-
public getLogFilePath(baseName: string): string { return "mock"; }
15-
16-
public writeAtLevel(logLevel: LogLevel, message: string, ...additionalMessages: string[]) { return undefined; }
5+
import { ILogger } from "../src/logging";
176

7+
export class MockLogger implements ILogger {
188
public write(message: string, ...additionalMessages: string[]) { return undefined; }
199

2010
public writeDiagnostic(message: string, ...additionalMessages: string[]) { return undefined; }
@@ -28,6 +18,4 @@ export class MockLogger extends Logger {
2818
public writeError(message: string, ...additionalMessages: string[]) { return undefined; }
2919

3020
public writeAndShowError(message: string, ...additionalMessages: string[]) { return undefined; }
31-
32-
public startNewLog(minimumLogLevel: string = "Normal") { return undefined; }
3321
}

0 commit comments

Comments
 (0)