Pijul source control integration extension for Visual Studio Code
import * as path from 'path';
import { Event, TreeDataProvider, TreeItem, TreeItemCollapsibleState, Uri, workspace } from 'vscode';
import { PijulChange, PijulFileChange, Repository } from '../pijul';

/**
 * The Changelog provider class is a Treedata provider which provides
 * TreeData for the `pijul.views.changelog` TreeView
 */
export class ChangelogViewProvider implements TreeDataProvider<PijulChange | PijulFileChange> {
  /**
     * Create a new ChangelogViewProvider instance
     * @param repository
     */
  constructor (
    private readonly repository: Repository,
    public readonly onDidChangeTreeData?: Event<void>
  ) {}

  /**
   * Convert a PijulChange into a TreeItem
   * @param element
   */
  getTreeItem (element: PijulChange | PijulFileChange): TreeItem | Thenable<TreeItem> {
    if (element instanceof PijulChange) {
      const resourceUri = Uri.parse('pijul-change:' + element.hash);

      return {
        label: element.message,
        description: `${element.author.name}, ${element.date.toISOString()}`,
        collapsibleState: TreeItemCollapsibleState.Collapsed,
        contextValue: 'pijulChange',
        tooltip: `${element.hash}\nRecorded by ${element.author.fullName ?? element.author.name} at ${element.date.toISOString()}\n\n\t${element.message}\n`,
        command: {
          command: 'vscode.open',
          title: 'Open Change TOML',
          arguments: [resourceUri]
        }
      };
    } else {
      // It's a PijulFileChange
      // TODO: Customize appearance depending on file action (Edit, Replacement, Deletion, etc.)
      const relativePath = path.relative(workspace.workspaceFolders?.[0]?.uri.path ?? '/', element.path.fsPath);
      return {
        label: path.basename(relativePath),
        description: path.dirname(relativePath),
        collapsibleState: TreeItemCollapsibleState.None,
        contextValue: 'pijulChangedFile',
        tooltip: `${element.operations.join(', ')} in ${relativePath}`,
        command: {
          command: 'vscode.open',
          title: 'Open Change TOML',
          arguments: [element.path]
        }
      };
    }
  }

  /**
   * Get the children of the element or the root if no element is passed
   * @param element An optional element to get the children of
   */
  async getChildren (element?: PijulChange | PijulFileChange): Promise<Array<PijulChange | PijulFileChange> | null | undefined> {
    if (element) {
      if (element instanceof PijulChange) {
        // Show the paths of the files altered by the parent change
        return await this.repository.getChangeFiles(element);
      } else {
        // Paths have no children
        return null;
      }
    } else {
      let children;
      // Retry if the pristine is locked. TODO: More robust solution
      while (!children) {
        try {
          children = await this.repository.getLog();
        } catch (err) {
          if (!(err instanceof Error) || !err.message.includes('Pristine locked')) {
            throw err;
          }
        }
      }
      return children;
    }
  }
}