Skip to content

Commit

Permalink
Clear Status When Document Closes (#30)
Browse files Browse the repository at this point in the history
When a diagnostic is cancelled, we were leaving behind the status
entry to dangle. This adds a cancellation event to stop the status,
thus removing it from the status bar when cancelled. We were also
using the Uri as a key in many different places, but this led to bugs
when they were not identical objects (even though identical Uris).
For convenience, we've wrapped these places with a class that
gets the string from the Uri as the key.
  • Loading branch information
ObliviousHarmony authored May 24, 2021
1 parent fb55621 commit 148d27f
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 18 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Fixed
- Maps and Sets should not be keyed by the Uri instance directly.
- Linter status should be cleared when a diagnostic is cancelled.

## [1.2.0] - 2021-05-21
### Added
Expand Down
14 changes: 12 additions & 2 deletions src/__mocks__/vscode.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
const Uri: any = jest.fn().mockImplementation(() => {
return {
const created = {
scheme: 'file',
authority: '',
path: 'test/file/path.php',
query: '',
fragment: '',
fsPath: 'test/file/path.php',
toString: jest.fn(),
toString: (): string => {
let str = created.scheme + '://' + created.path;
if (created.query) {
str += '?' + created.query;
}
if (created.fragment) {
str += '#' + created.fragment;
}
return str;
},
};
return created;
});
Uri.joinPath = jest.fn();

Expand Down
46 changes: 46 additions & 0 deletions src/common/uri-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Uri } from 'vscode';

/**
* A map for storing data keyed by a Uri.
*/
export class UriMap<V> extends Map<string, V> {
public has(key: Uri): boolean;
public has(key: string): boolean;
public has(key: Uri | string): boolean {
if (key instanceof Uri) {
key = key.toString();
}

return super.has(key);
}

public set(key: Uri, value: V): this;
public set(key: string, value: V): this;
public set(key: Uri | string, value: V): this {
if (key instanceof Uri) {
key = key.toString();
}

return super.set(key, value);
}

public get(key: Uri): V | undefined;
public get(key: string): V | undefined;
public get(key: Uri | string): V | undefined {
if (key instanceof Uri) {
key = key.toString();
}

return super.get(key);
}

public delete(key: Uri): boolean;
public delete(key: string): boolean;
public delete(key: Uri | string): boolean {
if (key instanceof Uri) {
key = key.toString();
}

return super.delete(key);
}
}
36 changes: 36 additions & 0 deletions src/common/uri-set.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Uri } from 'vscode';

/**
* A set for storing Uris.
*/
export class UriSet extends Set<string> {
public has(key: Uri): boolean;
public has(key: string): boolean;
public has(key: Uri | string): boolean {
if (key instanceof Uri) {
key = key.toString();
}

return super.has(key);
}

public add(key: Uri): this;
public add(key: string): this;
public add(key: Uri | string): this {
if (key instanceof Uri) {
key = key.toString();
}

return super.add(key);
}

public delete(key: Uri): boolean;
public delete(key: string): boolean;
public delete(key: Uri | string): boolean {
if (key instanceof Uri) {
key = key.toString();
}

return super.delete(key);
}
}
10 changes: 6 additions & 4 deletions src/listeners/workspace-listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { Configuration } from '../services/configuration';
import { CodeActionEditResolver } from '../services/code-action-edit-resolver';
import { DiagnosticUpdater } from '../services/diagnostic-updater';
import { DocumentFormatter } from '../services/document-formatter';
import { UriMap } from '../common/uri-map';
import { UriSet } from '../common/uri-set';

/**
* A class for listening to the workspace and responding to events that occur.
Expand Down Expand Up @@ -37,12 +39,12 @@ export class WorkspaceListener implements Disposable {
/**
* A set containing the Uris of all the documents that we're tracking.
*/
private readonly trackedDocuments: Set<Uri>;
private readonly trackedDocuments: UriSet;

/**
* A map for applying a debounce to document updates.
*/
private readonly updateDebounceMap: Map<Uri, NodeJS.Timeout>;
private readonly updateDebounceMap: UriMap<NodeJS.Timeout>;

/**
* The subscriptions we have to VS Code events.
Expand All @@ -67,8 +69,8 @@ export class WorkspaceListener implements Disposable {
this.diagnosticUpdater = diagnosticUpdater;
this.codeActionEditResolver = codeActionEditResolver;
this.documentFormatter = documentFormatter;
this.trackedDocuments = new Set();
this.updateDebounceMap = new Map();
this.trackedDocuments = new UriSet();
this.updateDebounceMap = new UriMap();
this.subscriptions = [];
}

Expand Down
5 changes: 3 additions & 2 deletions src/services/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Uri,
workspace as vsCodeWorkspace,
} from 'vscode';
import { UriMap } from '../common/uri-map';

/**
* An enum describing the values in the `phpcsCodeSniffer.standard` configuration.
Expand Down Expand Up @@ -83,7 +84,7 @@ export class Configuration {
/**
* A cache containing all of the configurations we've loaded.
*/
private readonly cache: Map<Uri, DocumentConfiguration>;
private readonly cache: UriMap<DocumentConfiguration>;

/**
* The decoder for parsing file content into strings for consumption.
Expand All @@ -97,7 +98,7 @@ export class Configuration {
*/
public constructor(workspace: typeof vsCodeWorkspace) {
this.workspace = workspace;
this.cache = new Map();
this.cache = new UriMap();
this.textDecoder = new TextDecoder();
}

Expand Down
5 changes: 5 additions & 0 deletions src/services/diagnostic-updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ export class DiagnosticUpdater extends WorkerService {
// Record that we're going to start linting a document.
this.linterStatus.start(document.uri);

// Make sure we stop linting the document the update is cancelled.
cancellationToken.onCancellationRequested(() => {
this.linterStatus.stop(document.uri);
});

this.workerPool
.waitForAvailable(
'diagnostic:' + document.fileName,
Expand Down
5 changes: 3 additions & 2 deletions src/services/linter-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Uri,
window as vsCodeWindow,
} from 'vscode';
import { UriSet } from '../common/uri-set';

/**
* A class for managing the linter's status.
Expand All @@ -18,14 +19,14 @@ export class LinterStatus implements Disposable {
/**
* The documents that are actively being linted.
*/
private readonly activeDocuments: Set<Uri>;
private readonly activeDocuments: UriSet;

/**
* Constructor.
*/
public constructor(window: typeof vsCodeWindow) {
this.statusBar = window.createStatusBarItem(StatusBarAlignment.Left);
this.activeDocuments = new Set();
this.activeDocuments = new UriSet();
}

/**
Expand Down
9 changes: 3 additions & 6 deletions src/services/worker-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import {
CancellationTokenSource,
Disposable,
TextDocument,
Uri,
} from 'vscode';
import { Logger } from './logger';
import { Configuration } from './configuration';
import { WorkerPool } from '../phpcs-report/worker-pool';
import { UriMap } from '../common/uri-map';

/**
* A base class for all of the updates that interact with PHPCS.
Expand All @@ -31,10 +31,7 @@ export abstract class WorkerService implements Disposable {
/**
* A map containing all of the cancellation token sources to prevent overlapping execution.
*/
private readonly cancellationTokenSourceMap: Map<
Uri,
CancellationTokenSource
>;
private readonly cancellationTokenSourceMap: UriMap<CancellationTokenSource>;

/**
* Constructor.
Expand All @@ -51,7 +48,7 @@ export abstract class WorkerService implements Disposable {
this.logger = logger;
this.configuration = configuration;
this.workerPool = workerPool;
this.cancellationTokenSourceMap = new Map();
this.cancellationTokenSourceMap = new UriMap();
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CodeAction as BaseCodeAction, TextDocument, Uri } from 'vscode';
import { CodeAction as BaseCodeAction, TextDocument } from 'vscode';
import { UriMap } from './common/uri-map';

/**
* A custom code action class that adds a Uri for associating it with a document.
Expand All @@ -13,4 +14,4 @@ export class CodeAction extends BaseCodeAction {
/**
* A collection of CodeAction instances keyed by the Uri.
*/
export class CodeActionCollection extends Map<Uri, CodeAction[]> {}
export class CodeActionCollection extends UriMap<CodeAction[]> {}

0 comments on commit 148d27f

Please sign in to comment.