vizhub-core / vzcode

Mob Programming Code Editor
MIT License
71 stars 14 forks source link

Watch File System #846

Open curran opened 1 month ago

curran commented 1 month ago

As a person using VZCode locally on my own machine, I want to be able to open and edit the same files in VSCode or other editors, and have the changes propagate automatically from the file system to VZCode, so that I can use multiple editors at once.

Current behavior: Changes only go from VZCode to the file system, not the other way.

Desired behavior: Changes made to files external to VZCode should be tracked by VZCode.

curran commented 1 month ago

Some ideas from ChatGPT:

To achieve automatic synchronization between external file changes and VZCode, you can use NPM packages that allow for file system watching. Here are a few popular tools for this purpose:

  1. chokidar: This is one of the most widely used packages for watching the file system in Node.js. It efficiently detects changes in files and directories and can trigger callbacks on events like file creation, modification, or deletion.

    • Install: npm install chokidar
    • Example usage:

      const chokidar = require('chokidar');
      
      // Initialize watcher
      const watcher = chokidar.watch('/path/to/watch', {
      ignored: /(^|[\/\\])\../, // ignore dotfiles
      persistent: true
      });
      
      // Add event listeners
      watcher
      .on('change', path => console.log(`File ${path} has been changed`))
      .on('unlink', path => console.log(`File ${path} has been removed`));
  2. fs.watch (built into Node.js): If you want a minimal solution, you can use Node.js's built-in fs.watch method. However, it may have some limitations on certain platforms (e.g., fewer features on macOS).

    • Example usage:

      const fs = require('fs');
      
      fs.watch('/path/to/watch', (eventType, filename) => {
      if (filename) {
       console.log(`${filename} file Changed`);
      }
      });
  3. node-watch: Another lightweight alternative that offers a simpler API to monitor file changes in the file system.

    • Install: npm install node-watch
    • Example usage:

      const watch = require('node-watch');
      
      watch('/path/to/watch', { recursive: true }, function(evt, name) {
      console.log('%s changed.', name);
      });
  4. nsfw (Node's simple file watcher): This is an efficient file system watcher that scales well to large file trees and is particularly known for performance.

    • Install: npm install nsfw
    • Example usage:

      const nsfw = require('nsfw');
      
      nsfw('/path/to/watch', events => {
      events.forEach(event => {
       console.log(`File ${event.action} detected in ${event.directory}`);
      });
      }).then(watcher => watcher.start());

Any of these tools should help track external file changes and synchronize them with VZCode, allowing the desired multi-editor setup. For a balance of simplicity and power, chokidar is often the go-to choice.

curran commented 1 month ago

This is a relevant snippet from VizHub that deals with figuring out FileIDs. We would need to consider how to get the fresh FS updates into the VZCode data structure, where the file at the same path should get the same FileID:

import { Content, Files, FileV2, FilesV2 } from 'entities';
import { v4 as uuid } from 'uuid';

// A file ID is an 8 character uuid (random characters).
let generateFileId = () => uuid().substring(0, 8);

export const setPredictableGenerateFileId = () => {
  let i = 0;
  generateFileId = () => {
    i++;
    return i.toString();
  };
};

export const computeV3Files = (
  goodFiles: FilesV2,
  oldContentV3?: Content,
): Files => {
  let getFileId: (file?: FileV2) => string = generateFileId;

  // If we fork from an existing V3 viz,
  // optimize the file diffs.
  // Not relevant for the primordial viz.
  if (oldContentV3) {
    const keys = Object.keys(oldContentV3.files);

    const fileIdByName = new Map<string, string>(
      keys.map((fileId) => [
        oldContentV3.files[fileId].name,
        fileId,
      ]),
    );
    const fileIdByText = new Map<string, string>(
      keys.map((fileId) => [
        oldContentV3.files[fileId].text,
        fileId,
      ]),
    );

    // Try to match on the name, in case text was changed (most common case).
    // Try to match on the text, in case name was changed (file renamed).
    // If no match, consider it a new file and mint a new file id.
    getFileId = (file) =>
      (file && fileIdByName.get(file.name)) ||
      (file && fileIdByText.get(file.text)) ||
      generateFileId();
  }

  const filesOutOfOrder = goodFiles.reduce(
    (accumulator, file) => ({
      ...accumulator,
      [getFileId(file)]: file,
    }),
    {},
  );

  const files = {};

  // Ensure ordering of keys matches that of the files
  // in the forked from viz, to minimize JSON1 OT Diff.
  if (oldContentV3) {
    Object.keys(oldContentV3.files).forEach((fileId) => {
      if (fileId in filesOutOfOrder) {
        files[fileId] = filesOutOfOrder[fileId];
      }
    });
  }

  // Get the rest, whatever wasn't in forkedFrom viz
  Object.keys(filesOutOfOrder).forEach((fileId) => {
    files[fileId] = filesOutOfOrder[fileId];
  });

  return files;
};
curran commented 1 month ago

This is the sort of thing that we could have really solid unit tests for!

Ultimately we'd need to write a function like

updateChangedFiles(content: VZCodeContent, filesChanged: string[])