execIntoTmuxWorktree method

Future<({String? error, bool handled})> execIntoTmuxWorktree(
  1. List<String> args
)

Fast-path handler for --worktree --tmux. Creates the worktree and execs into tmux running Neomage inside.

Implementation

Future<({bool handled, String? error})> execIntoTmuxWorktree(
  List<String> args,
) async {
  // Check platform
  if (Platform.isWindows) {
    return (
      handled: false,
      error: 'Error: --tmux is not supported on Windows',
    );
  }

  // Check tmux
  final tmuxAvailable = await isTmuxAvailable();
  if (!tmuxAvailable) {
    return (
      handled: false,
      error: 'Error: tmux is not installed. ${getTmuxInstallInstructions()}',
    );
  }

  // Parse worktree name from args
  String? worktreeName;
  bool forceClassicTmux = false;

  for (int i = 0; i < args.length; i++) {
    final arg = args[i];
    if (arg == '-w' || arg == '--worktree') {
      if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
        worktreeName = args[i + 1];
      }
    } else if (arg.startsWith('--worktree=')) {
      worktreeName = arg.substring('--worktree='.length);
    } else if (arg == '--tmux=classic') {
      forceClassicTmux = true;
    }
  }

  // Check for PR reference
  int? prNumber;
  if (worktreeName != null) {
    prNumber = parsePRReference(worktreeName);
    if (prNumber != null) {
      worktreeName = 'pr-$prNumber';
    }
  }

  // Generate a slug if no name provided
  if (worktreeName == null) {
    const adjectives = ['swift', 'bright', 'calm', 'keen', 'bold'];
    const nouns = ['fox', 'owl', 'elm', 'oak', 'ray'];
    final rng = Random();
    final adj = adjectives[rng.nextInt(adjectives.length)];
    final noun = nouns[rng.nextInt(nouns.length)];
    final suffix = rng.nextInt(0xFFFF).toRadixString(36).padLeft(4, '0');
    worktreeName = '$adj-$noun-$suffix';
  }

  try {
    validateWorktreeSlug(worktreeName);
  } catch (e) {
    return (handled: false, error: 'Error: $e');
  }

  // Create or resume worktree
  final gitRoot = _findCanonicalGitRoot(_getCwd());
  if (gitRoot == null && !_hasWorktreeCreateHook()) {
    return (
      handled: false,
      error: 'Error: --worktree requires a git repository',
    );
  }

  String worktreeDir;
  String repoName;

  if (_hasWorktreeCreateHook() && onExecuteWorktreeCreateHook != null) {
    try {
      final hookResult = await onExecuteWorktreeCreateHook!(worktreeName);
      worktreeDir = hookResult.worktreePath;
    } catch (e) {
      return (handled: false, error: 'Error: $e');
    }
    repoName = (gitRoot ?? _getCwd()).split('/').last;
  } else {
    repoName = gitRoot!.split('/').last;
    worktreeDir = _worktreePathFor(gitRoot, worktreeName);

    try {
      final result = await _getOrCreateWorktree(
        gitRoot,
        worktreeName,
        options: prNumber != null
            ? WorktreeCreateOptions(prNumber: prNumber)
            : null,
      );
      if (result is WorktreeCreated) {
        await _performPostCreationSetup(gitRoot, worktreeDir);
      }
    } catch (e) {
      return (handled: false, error: 'Error: $e');
    }
  }

  // Build tmux session name
  final tmuxSessionNameFinal =
      '${repoName}_${worktreeBranchName(worktreeName)}'.replaceAll(
        RegExp(r'[/.]'),
        '_',
      );

  // Build new args without --tmux and --worktree
  final newArgs = <String>[];
  for (int i = 0; i < args.length; i++) {
    final arg = args[i];
    if (arg == '--tmux' || arg == '--tmux=classic') continue;
    if (arg == '-w' || arg == '--worktree') {
      if (i + 1 < args.length && !args[i + 1].startsWith('-')) i++;
      continue;
    }
    if (arg.startsWith('--worktree=')) continue;
    newArgs.add(arg);
  }

  // Create tmux session
  final tmuxArgs = [
    'new-session',
    '-A',
    '-s',
    tmuxSessionNameFinal,
    '-c',
    worktreeDir,
    '--',
    Platform.resolvedExecutable,
    ...newArgs,
  ];

  final tmuxResult = Process.runSync(
    'tmux',
    tmuxArgs,
    workingDirectory: worktreeDir,
  );

  return (handled: true, error: null);
}