diff --git a/src/ccAnnotateController.ts b/src/ccAnnotateController.ts index a7dab39..db6feb2 100755 --- a/src/ccAnnotateController.ts +++ b/src/ccAnnotateController.ts @@ -1,7 +1,6 @@ import { DecorationOptions, DecorationRenderOptions, - ExtensionContext, Range, TextEditor, TextEditorDecorationType, @@ -9,16 +8,17 @@ import { } from "vscode"; import { CCConfigHandler } from "./ccConfigHandler"; import { CCConfiguration } from "./ccConfiguration"; +import { IDisposable } from "./model"; -export class CCAnnotationController { +export class CCAnnotationController implements IDisposable { private mDecorationType: TextEditorDecorationType; - private mIsActive: boolean; + private mIsActive = false; private mConfiguration: CCConfiguration; + private mDisposables: IDisposable[] = []; - constructor(private editor: TextEditor, private context: ExtensionContext, private configHandler: CCConfigHandler) { - this.mIsActive = false; - window.onDidChangeActiveTextEditor((editor) => this.onActiveEditorChange(editor), this, this.context.subscriptions); - this.configHandler.onDidChangeConfiguration(() => this.onConfigurationChanged()); + constructor(private editor: TextEditor, private configHandler: CCConfigHandler) { + this.mDisposables.push(window.onDidChangeActiveTextEditor((editor) => this.onActiveEditorChange(editor))); + this.mDisposables.push(this.configHandler.onDidChangeConfiguration(() => this.onConfigurationChanged())); const ro: DecorationRenderOptions = { isWholeLine: false, before: { diff --git a/src/ccAnnotateLensProvider.ts b/src/ccAnnotateLensProvider.ts index f4f1f2b..7bb5c2b 100755 --- a/src/ccAnnotateLensProvider.ts +++ b/src/ccAnnotateLensProvider.ts @@ -1,4 +1,4 @@ -import { CodeLens, CodeLensProvider, ExtensionContext, Range, TextDocument } from "vscode"; +import { CancellationToken, CodeLens, CodeLensProvider, ProviderResult, Range, TextDocument } from "vscode"; import { CCAnnotateLens } from "./ccAnnotateLens"; import { CCConfigHandler } from "./ccConfigHandler"; import { CCScmProvider } from "./ccScmProvider"; @@ -9,35 +9,43 @@ export class CCCodeLensProvider implements CodeLensProvider { }; constructor( - private mContext: ExtensionContext, private mCfg: CCConfigHandler, private mProvider: CCScmProvider ) { } - provideCodeLenses(document: TextDocument): Thenable | CodeLens[] { + provideCodeLenses(document: TextDocument, token: CancellationToken): ProviderResult { if (!this.mCfg.configuration.showAnnotationCodeLens.value) { return []; } - return new Promise((resolve) => { - this.mProvider.clearCase?.isClearcaseObject(document.uri).then((is: boolean) => { - const lLenses: CodeLens[] = []; - if (document !== undefined && is === true) { - lLenses.push(new CCAnnotateLens(document, new Range(0, 0, 0, 1))); - } - resolve(lLenses); - }); - }); + return this.getCodeLenses(document, token); } - resolveCodeLens(codeLens: CodeLens): Thenable { + private async getCodeLenses(document: TextDocument, token: CancellationToken): Promise { + if (token.isCancellationRequested === true) { + return []; + } + + const isClearcaseObject = (await this.mProvider.clearCase?.isClearcaseObject(document.uri)) ?? false; + + if (document !== undefined && isClearcaseObject) { + return [new CCAnnotateLens(document, new Range(0, 0, 0, 1))]; + } + return []; + } + + resolveCodeLens?(codeLens: CodeLens, token: CancellationToken): ProviderResult { + if (token.isCancellationRequested === true) { + return codeLens; + } + if (codeLens instanceof CCAnnotateLens) { codeLens.command = { title: "Toggle annotations", command: "extension.ccAnnotate", arguments: [codeLens.document.uri], }; - return Promise.resolve(codeLens); + return codeLens; } return Promise.reject(); diff --git a/src/ccConfigHandler.ts b/src/ccConfigHandler.ts index d788b82..7a4dbc1 100755 --- a/src/ccConfigHandler.ts +++ b/src/ccConfigHandler.ts @@ -1,23 +1,23 @@ "use strict"; -import { Disposable, Event, EventEmitter, ExtensionContext, workspace, WorkspaceConfiguration } from "vscode"; +import { Event, EventEmitter, workspace, WorkspaceConfiguration } from "vscode"; import { CCConfiguration, ConfigurationProperty } from "./ccConfiguration"; +import { IDisposable } from "./model"; -export class CCConfigHandler { - private mConfigChanged: EventEmitter; - private mConfiguration: CCConfiguration; - private mChangeIdents: string[]; - - constructor(private context: ExtensionContext, private disposables: Disposable[]) { - this.mChangeIdents = []; - this.mConfigChanged = new EventEmitter(); - this.mConfiguration = new CCConfiguration(); +export class CCConfigHandler implements IDisposable { + private mConfigChanged = new EventEmitter(); + private mConfiguration = new CCConfiguration(); + private mChangeIdents: string[] = []; + private mDisposables: IDisposable[] = []; + constructor() { this.loadConfig(); - this.disposables.push( - workspace.onDidChangeConfiguration(() => this.handleChangedConfig(), this, this.context.subscriptions) - ); + this.mDisposables.push(workspace.onDidChangeConfiguration(() => this.handleChangedConfig())); + } + + dispose(): void { + this.mDisposables.forEach((d) => d.dispose()); } get onDidChangeConfiguration(): Event { @@ -76,7 +76,7 @@ export class CCConfigHandler { } return false; } - + private handleChangedConfig(): void { if (this.loadConfig()) { this.mConfigChanged.fire(this.mChangeIdents); diff --git a/src/ccContentProvider.ts b/src/ccContentProvider.ts index c9a6920..502d5aa 100755 --- a/src/ccContentProvider.ts +++ b/src/ccContentProvider.ts @@ -1,16 +1,30 @@ -import { workspace, Uri, Disposable, TextDocumentContentProvider, QuickDiffProvider, CancellationToken } from "vscode"; +import { + workspace, + Uri, + TextDocumentContentProvider, + QuickDiffProvider, + CancellationToken, + ProviderResult, +} from "vscode"; import { ClearCase } from "./clearcase"; +import { IDisposable } from "./model"; import { toCcUri, fromCcUri } from "./uri"; -export class CCContentProvider implements TextDocumentContentProvider, QuickDiffProvider, Disposable { - constructor(private mCcHandler: ClearCase | null, private mDisposals: Disposable[]) { +export class CCContentProvider implements TextDocumentContentProvider, QuickDiffProvider, IDisposable { + private mDisposals: IDisposable[] = []; + + constructor(private mCcHandler: ClearCase | null) { if (this.mCcHandler !== null) { this.mDisposals.push(workspace.registerTextDocumentContentProvider("cc", this)); this.mDisposals.push(workspace.registerTextDocumentContentProvider("cc-orig", this)); } } - async provideTextDocumentContent(uri: Uri, token: CancellationToken): Promise { + provideTextDocumentContent(uri: Uri, token: CancellationToken): ProviderResult { + return this.getTextDocumentContent(uri, token); + } + + private async getTextDocumentContent(uri: Uri, token: CancellationToken): Promise { if (token.isCancellationRequested === true) { return "canceled"; } @@ -30,12 +44,20 @@ export class CCContentProvider implements TextDocumentContentProvider, QuickDiff return ""; } - async provideOriginalResource(uri: Uri): Promise { + provideOriginalResource(uri: Uri, token: CancellationToken): ProviderResult { + return this.getOriginalResource(uri, token); + } + + async getOriginalResource(uri: Uri, token?: CancellationToken): Promise { + if (token?.isCancellationRequested === true) { + return undefined; + } + if (uri.scheme !== "file") { return; } - const currentVersion = this.mCcHandler ? await this.mCcHandler.getVersionInformation(uri, false) : ""; + const currentVersion = (await this.mCcHandler?.getVersionInformation(uri, false)) ?? ""; if (currentVersion !== "") { const isCheckedOut = currentVersion.match("\\b(CHECKEDOUT)\\b$"); diff --git a/src/ccIgnoreHandler.ts b/src/ccIgnoreHandler.ts index 541f332..17a48e2 100755 --- a/src/ccIgnoreHandler.ts +++ b/src/ccIgnoreHandler.ts @@ -1,25 +1,14 @@ -import { workspace, WorkspaceFolder, Uri, EventEmitter } from "vscode"; +import { workspace, WorkspaceFolder, Uri, EventEmitter, Event } from "vscode"; import { existsSync, readFileSync, statSync } from "fs"; import { join, dirname, sep } from "path"; import ignore, { Ignore } from "ignore"; import { ModelHandler } from "./model"; export class IgnoreHandler { - private fileIgnores: FileIgnore[]; - private mOnFilterRefreshed: EventEmitter; + private fileIgnores: FileIgnore[] = []; + private mOnFilterRefreshed = new EventEmitter(); constructor(private mFsWatch: ModelHandler) { - this.mOnFilterRefreshed = new EventEmitter(); - this.fileIgnores = []; - this.init(); - } - - get onFilterRefreshed(): EventEmitter { - return this.mOnFilterRefreshed; - } - - private init(): void { - this.fileIgnores = []; workspace.workspaceFolders?.forEach((folder: WorkspaceFolder) => { const lM = this.mFsWatch.addWatcher(join(folder.uri.fsPath, ".ccignore")); lM.onWorkspaceChanged((fileObj) => this.refreshFilter(fileObj)); @@ -30,6 +19,10 @@ export class IgnoreHandler { }); } + get onFilterRefreshed(): Event { + return this.mOnFilterRefreshed.event; + } + getFolderIgnore(path: Uri | string): FileIgnore | null { const t = this.appendSeparator(typeof path === "string" ? path : path.fsPath); for (const ignore of this.fileIgnores) { diff --git a/src/ccScmProvider.ts b/src/ccScmProvider.ts index af7cece..f9c838b 100755 --- a/src/ccScmProvider.ts +++ b/src/ccScmProvider.ts @@ -3,7 +3,6 @@ import { scm, SourceControlResourceGroup, Uri, - Disposable, OutputChannel, commands, workspace, @@ -25,7 +24,7 @@ import { CCScmResource, ResourceGroupType } from "./ccScmResource"; import { CCScmStatus } from "./ccScmStatus"; import { ClearCase, ViewType } from "./clearcase"; import { LocalizeFunc, loadMessageBundle } from "vscode-nls"; -import { ModelHandler } from "./model"; +import { IDisposable, ModelHandler } from "./model"; import { CCConfigHandler } from "./ccConfigHandler"; import { CCAnnotationController } from "./ccAnnotateController"; import { CCCodeLensProvider } from "./ccAnnotateLensProvider"; @@ -40,7 +39,7 @@ import { getErrorMessage } from "./errormessage"; const localize: LocalizeFunc = loadMessageBundle(); -export class CCScmProvider { +export class CCScmProvider implements IDisposable { private mCCContentProvider: CCContentProvider | null = null; private mCCHandler: ClearCase | null = null; private mIgnoreFileEv: ModelHandler | null = null; @@ -50,6 +49,7 @@ export class CCScmProvider { private mIsUpdatingUntracked: boolean | null = null; private mListLock: Lock | null = null; private mIgnores: IgnoreHandler | null = null; + private mDisposables: IDisposable[] = []; private mWindowChangedEvent: EventEmitter = new EventEmitter(); @@ -62,14 +62,13 @@ export class CCScmProvider { constructor( private mContext: ExtensionContext, - private mDisposables: Disposable[], private outputChannel: OutputChannel, private configHandler: CCConfigHandler ) { } async init(): Promise { this.mListLock = new Lock(1); - this.mCCHandler = new ClearCase(this.mContext, this.configHandler, this.outputChannel); + this.mCCHandler = new ClearCase(this.configHandler, this.outputChannel); if (this.configHandler.configuration.useRemoteClient.value === true) { if (this.configHandler.configuration.webserverPassword.value !== "") { if (this.clearCase) { @@ -110,9 +109,9 @@ export class CCScmProvider { } private async startExtension(): Promise { - let isView: boolean | undefined = false; + let isView = false; try { - isView = await this.mCCHandler?.checkIsView(undefined); + isView = (await this.mCCHandler?.checkIsView(undefined)) ?? false; } catch (error) { isView = false; } @@ -129,9 +128,10 @@ export class CCScmProvider { this.mCCUntrackedGrp = this.mCCScm.createResourceGroup("cc_untracked", "View private"); this.mCCCheckedoutGrp.hideWhenEmpty = true; this.mCCUntrackedGrp.hideWhenEmpty = true; - this.mCCContentProvider = new CCContentProvider(this.mCCHandler, this.mDisposables); + this.mCCContentProvider = new CCContentProvider(this.mCCHandler); - this.mContext.subscriptions.push(this.mCCScm); + this.mDisposables.push(this.mCCScm); + this.mDisposables.push(this.mCCContentProvider); this.mCCScm.inputBox.placeholder = "Message (press Ctrl+Enter to checkin all files)"; this.mCCScm.acceptInputCommand = { @@ -143,11 +143,8 @@ export class CCScmProvider { } this.mIgnoreFileEv = new ModelHandler(); - this.mIgnoreFileEv.init(); this.mIgnores = new IgnoreHandler(this.mIgnoreFileEv); - this.mIgnores.onFilterRefreshed.event(() => { - this.filterUntrackedList(); - }, this); + this.mIgnores.onFilterRefreshed(() => this.filterUntrackedList()); this.clearCase?.onCommandExecuted((evArgs: Uri) => { this.handleChangeFiles(evArgs); @@ -169,13 +166,8 @@ export class CCScmProvider { return this.mCCHandler; } - updateIsView(): Promise { - return new Promise((resolve, reject) => { - this.clearCase - ?.checkIsView(window.activeTextEditor) - .then(() => resolve(this.clearCase?.isView ?? false)) - .catch(() => reject(false)); - }); + async updateIsView(): Promise { + return this.clearCase?.checkIsView(window.activeTextEditor) ?? false; } private async handleChangeFiles(fileObj: Uri) { @@ -502,9 +494,9 @@ export class CCScmProvider { ); if (window.activeTextEditor !== undefined) { - const annoCtrl = new CCAnnotationController(window.activeTextEditor, this.mContext, this.configHandler); + const annoCtrl = new CCAnnotationController(window.activeTextEditor, this.configHandler); + this.mDisposables.push(annoCtrl); - this.mContext.subscriptions.push(annoCtrl); this.mDisposables.push( commands.registerCommand( "extension.ccAnnotate", @@ -518,10 +510,10 @@ export class CCScmProvider { ); } - this.mContext.subscriptions.push( + this.mDisposables.push( languages.registerCodeLensProvider( CCCodeLensProvider.selector, - new CCCodeLensProvider(this.mContext, this.configHandler, this) + new CCCodeLensProvider(this.configHandler, this) ) ); } @@ -689,7 +681,7 @@ export class CCScmProvider { preview: true, }; - const prevUri = await this.mCCContentProvider?.provideOriginalResource(fileObj); + const prevUri = await this.mCCContentProvider?.getOriginalResource(fileObj); if (prevUri !== undefined) { const fn = path.basename(fileObj.fsPath); const { version } = fromCcUri(prevUri); diff --git a/src/clearcase.ts b/src/clearcase.ts index d442b99..1557a9e 100755 --- a/src/clearcase.ts +++ b/src/clearcase.ts @@ -7,7 +7,6 @@ import * as tmp from "tmp"; import { Event, EventEmitter, - ExtensionContext, OutputChannel, QuickPickItem, TextDocument, @@ -116,24 +115,18 @@ export class ClearCase { private readonly rxViewAttr = new RegExp("(view attributes\\:)([\\,\\t \\w\\d]*)(webview)", "i"); private mIsCCView = false; - private mViewType: ViewType; - private mUpdateEvent: EventEmitter; + private mViewType: ViewType = ViewType.unknown; + private mUpdateEvent = new EventEmitter(); - private mUntrackedList: MappedList; + private mUntrackedList = new MappedList(); private mExecCmd: CleartoolIf; private mWebviewPassword = ""; constructor( - private mContext: ExtensionContext, private configHandler: CCConfigHandler, private outputChannel: OutputChannel ) { - this.mUpdateEvent = new EventEmitter(); - this.mViewType = ViewType.unknown; - this.isView = false; - this.mUntrackedList = new MappedList(); - if (this.configHandler.configuration.useRemoteClient.value === true) { this.mExecCmd = new Cleartool( this.configHandler.configuration.webserverUsername.value, @@ -167,10 +160,6 @@ export class ClearCase { return this.mIsCCView; } - set isView(v: boolean) { - this.mIsCCView = v; - } - get viewType(): ViewType { return this.mViewType; } @@ -220,7 +209,7 @@ export class ClearCase { this.mViewType = await this.detectViewType(); } - this.isView = isView; + this.mIsCCView = isView; return isView; } diff --git a/src/extension.ts b/src/extension.ts index fd012d1..0c2c7ea 100755 --- a/src/extension.ts +++ b/src/extension.ts @@ -18,20 +18,22 @@ async function _activate(context: ExtensionContext, disposables: Disposable[]) { // The commandId parameter must match the command field in package.json const outputChannel: OutputChannel = window.createOutputChannel("Clearcase SCM"); - const configHandler = new CCConfigHandler(context, disposables); + const configHandler = new CCConfigHandler(); + disposables.push(configHandler); - const provider = new CCScmProvider(context, disposables, outputChannel, configHandler); + const provider = new CCScmProvider(context, outputChannel, configHandler); + disposables.push(provider); try { if (true === (await provider.init())) { provider.bindEvents(); provider.bindCommands(); - provider.updateIsView().then((is: boolean) => { - const d = provider.clearCase ? provider.clearCase.viewType === ViewType.dynamic : false; + provider.updateIsView().then((isView: boolean) => { + const viewType = provider.clearCase ? provider.clearCase.viewType === ViewType.dynamic : false; const files = provider.getCheckedoutObjects(); - commands.executeCommand("setContext", "vscode-clearcase:enabled", is); - commands.executeCommand("setContext", "vscode-clearcase:DynView", d); + commands.executeCommand("setContext", "vscode-clearcase:enabled", isView); + commands.executeCommand("setContext", "vscode-clearcase:DynView", viewType); commands.executeCommand("setContext", "vscode-clearcase:CheckedoutObjects", files); }); @@ -43,14 +45,8 @@ async function _activate(context: ExtensionContext, disposables: Disposable[]) { commands.executeCommand("setContext", "vscode-clearcase:CheckedoutObjects", files); }, provider); - const uiInfo = new UIInformation(context, disposables, configHandler, window.activeTextEditor, provider.clearCase); - uiInfo.createStatusbarItem(); - uiInfo.bindEvents(); - uiInfo.initialQuery(); - - provider.clearCase?.onCommandExecuted(() => { - uiInfo.initialQuery(); - }, uiInfo); + const uiInfo = new UIInformation(configHandler, window.activeTextEditor, provider.clearCase); + disposables.push(uiInfo); console.log("[vscode-clearcase] started!"); } } catch { diff --git a/src/mappedlist.ts b/src/mappedlist.ts index 6099102..1263d9e 100644 --- a/src/mappedlist.ts +++ b/src/mappedlist.ts @@ -2,14 +2,13 @@ import { accessSync } from "fs"; import { workspace } from "vscode"; class FileType { - constructor(public found: boolean, public name: string) {} + constructor(public found: boolean, public name: string) { } } export class MappedList { - private mUntrackedList: Map | null; + private mUntrackedList: Map | null = null; constructor() { - this.mUntrackedList = null; if (workspace.workspaceFolders !== undefined && workspace.workspaceFolders.length > 0) { this.mUntrackedList = new Map(); workspace.workspaceFolders.forEach((val) => { diff --git a/src/model.ts b/src/model.ts index 30742d7..af9501f 100755 --- a/src/model.ts +++ b/src/model.ts @@ -1,4 +1,4 @@ -import { Event, Disposable, Uri, workspace } from "vscode"; +import { Event, Uri, workspace } from "vscode"; export interface IDisposable { dispose(): void; @@ -10,11 +10,7 @@ export function dispose(disposables: T[]): T[] { } export class ModelHandler { - private mModels: Model[] | undefined; - - init(): void { - this.mModels = []; - } + private mModels: Model[] = []; addWatcher(filter = "**"): Model { const lM = new Model(); @@ -24,8 +20,8 @@ export class ModelHandler { } } -export class Model implements Disposable { - private disposables: Disposable[] = []; +export class Model implements IDisposable { + private disposables: IDisposable[] = []; private _onWorkspaceCreated!: Event; private _onWorkspaceChanged!: Event; private _onWorkspaceDeleted!: Event; @@ -33,7 +29,7 @@ export class Model implements Disposable { get onWorkspaceCreated(): Event { return this._onWorkspaceCreated; } - + get onWorkspaceChanged(): Event { return this._onWorkspaceChanged; } diff --git a/src/uiinformation.ts b/src/uiinformation.ts index 1948511..0580ca6 100755 --- a/src/uiinformation.ts +++ b/src/uiinformation.ts @@ -2,8 +2,6 @@ import { CCConfigHandler } from "./ccConfigHandler"; import { ClearCase } from "./clearcase"; import { existsSync } from "fs"; import { - Disposable, - ExtensionContext, StatusBarAlignment, StatusBarItem, TextDocument, @@ -13,28 +11,20 @@ import { window, workspace, } from "vscode"; +import { IDisposable } from "./model"; -export class UIInformation { - private mStatusbar: StatusBarItem | null; - private mIsActive: boolean; +export class UIInformation implements IDisposable { + private mStatusbar: StatusBarItem = window.createStatusBarItem(StatusBarAlignment.Left); + private mIsActive = true; + private mDisposables: IDisposable[] = []; constructor( - private mContext: ExtensionContext, - private mDisposables: Disposable[], private mConfigHandler: CCConfigHandler, private mEditor: TextEditor | undefined, private mClearcase: ClearCase | null ) { - this.mIsActive = true; this.handleConfigState(); - this.mStatusbar = null; - } - - createStatusbarItem(): void { - this.mStatusbar = window.createStatusBarItem(StatusBarAlignment.Left); - } - bindEvents(): void { // configuration change event this.mConfigHandler.onDidChangeConfiguration(() => this.handleConfigState()); @@ -42,6 +32,11 @@ export class UIInformation { this.mDisposables.push(workspace.onDidSaveTextDocument((document) => this.receiveDocument(document))); this.mDisposables.push(window.onDidChangeActiveTextEditor((editor) => this.receiveEditor(editor))); this.mDisposables.push(window.onDidChangeTextEditorViewColumn((event) => this.receiveEditorColumn(event))); + if (this.mClearcase) { + this.mDisposables.push(this.mClearcase.onCommandExecuted(() => { this.initialQuery(); })); + } + + this.initialQuery(); } private receiveEditorColumn(event: TextEditorViewColumnChangeEvent) {