summaryrefslogtreecommitdiff
path: root/packages/vscode-ide-companion/src/recent-files-manager.ts
blob: 84316363037b79ebd2cc69048b7998e51bb1463f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import * as vscode from 'vscode';

export const MAX_FILES = 10;
export const MAX_FILE_AGE_MINUTES = 5;

interface RecentFile {
  uri: vscode.Uri;
  timestamp: number;
}

/**
 * Keeps track of the 10 most recently-opened files
 * opened less than 5 ago. If a file is closed or deleted,
 * it will be removed. If the length is maxxed out,
 * the now-removed file will not be replaced by an older file.
 */
export class RecentFilesManager {
  private readonly files: RecentFile[] = [];
  private readonly onDidChangeEmitter = new vscode.EventEmitter<void>();
  readonly onDidChange = this.onDidChangeEmitter.event;

  constructor(private readonly context: vscode.ExtensionContext) {
    const editorWatcher = vscode.window.onDidChangeActiveTextEditor(
      (editor) => {
        if (editor) {
          this.add(editor.document.uri);
        }
      },
    );
    const fileWatcher = vscode.workspace.onDidDeleteFiles((event) => {
      for (const uri of event.files) {
        this.remove(uri);
      }
    });
    const closeWatcher = vscode.workspace.onDidCloseTextDocument((document) => {
      this.remove(document.uri);
    });
    const renameWatcher = vscode.workspace.onDidRenameFiles((event) => {
      for (const { oldUri, newUri } of event.files) {
        this.remove(oldUri, false);
        this.add(newUri);
      }
    });
    context.subscriptions.push(
      editorWatcher,
      fileWatcher,
      closeWatcher,
      renameWatcher,
    );
  }

  private remove(uri: vscode.Uri, fireEvent = true) {
    const index = this.files.findIndex(
      (file) => file.uri.fsPath === uri.fsPath,
    );
    if (index !== -1) {
      this.files.splice(index, 1);
      if (fireEvent) {
        this.onDidChangeEmitter.fire();
      }
    }
  }

  add(uri: vscode.Uri) {
    // Remove if it already exists to avoid duplicates and move it to the top.
    this.remove(uri, false);

    this.files.unshift({ uri, timestamp: Date.now() });

    if (this.files.length > MAX_FILES) {
      this.files.pop();
    }
    this.onDidChangeEmitter.fire();
  }

  get recentFiles(): Array<{ filePath: string; timestamp: number }> {
    const now = Date.now();
    const maxAgeInMs = MAX_FILE_AGE_MINUTES * 60 * 1000;
    return this.files
      .filter((file) => now - file.timestamp < maxAgeInMs)
      .map((file) => ({
        filePath: file.uri.fsPath,
        timestamp: file.timestamp,
      }));
  }
}