editor.ts 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. import { spawn } from 'child_process';
  2. import path from 'path';
  3. /**
  4. * Opens the file in the user's preferred editor and waits for them to close it.
  5. * Checks $VISUAL, $EDITOR, then falls back to platform defaults.
  6. */
  7. export async function editInExternalEditor(filePath: string): Promise<void> {
  8. const { cmd, args } = getEditor();
  9. const absolutePath = path.resolve(filePath);
  10. return new Promise((resolve, reject) => {
  11. const child = spawn(cmd, [...args, absolutePath], {
  12. stdio: 'inherit',
  13. shell: true,
  14. });
  15. child.on('error', (err) => {
  16. reject(new Error(`Failed to open editor: ${err.message}`));
  17. });
  18. child.on('close', (code) => {
  19. if (code === 0) {
  20. resolve();
  21. } else {
  22. reject(new Error(`Editor exited with code ${code}`));
  23. }
  24. });
  25. });
  26. }
  27. /**
  28. * Get the editor/opener command to use.
  29. * Priority: $VISUAL > $EDITOR > platform default (opens with associated app)
  30. */
  31. function getEditor(): { cmd: string; args: string[] } {
  32. if (process.env.VISUAL) return { cmd: process.env.VISUAL, args: [] };
  33. if (process.env.EDITOR) return { cmd: process.env.EDITOR, args: [] };
  34. // Platform defaults - open with associated application
  35. if (process.platform === 'win32') {
  36. // 'start' opens with default app, '' is window title, /wait makes it blocking
  37. return { cmd: 'start', args: ['""', '/wait'] };
  38. }
  39. if (process.platform === 'darwin') {
  40. // macOS: open -W waits for app to close
  41. return { cmd: 'open', args: ['-W'] };
  42. }
  43. // Linux: xdg-open (doesn't wait, but best we can do)
  44. return { cmd: 'xdg-open', args: [] };
  45. }