Skip to content

Commit 7fc7dcc

Browse files
authored
Merge pull request #91 from stuartleeks/open-sub-folder
Add support for opening subfolders in VS Code devcontainer commands
2 parents 42d3dd6 + 2dc8baa commit 7fc7dcc

File tree

4 files changed

+84
-7
lines changed

4 files changed

+84
-7
lines changed

cmd/devcontainerx/openincode.go

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,78 @@ package main
33
import (
44
"fmt"
55
"os/exec"
6+
"path/filepath"
7+
"strings"
68

79
"github.com/spf13/cobra"
810
"github.com/stuartleeks/devcontainer-cli/internal/pkg/devcontainers"
911
"github.com/stuartleeks/devcontainer-cli/internal/pkg/wsl"
1012
)
1113

1214
func createOpenInCodeCommand() *cobra.Command {
15+
var subFolder string
1316
cmd := &cobra.Command{
1417
Use: "open-in-code <path>",
1518
Short: "open the specified path devcontainer project in VS Code",
1619
Long: "Open the specified path (containing a .devcontainer folder in VS Code",
1720
RunE: func(cmd *cobra.Command, args []string) error {
18-
return launchDevContainer(cmd, "code", args)
21+
return launchDevContainer(cmd, "code", args, subFolder)
1922
},
2023
}
24+
cmd.Flags().StringVarP(&subFolder, "sub-folder", "s", "", "sub-folder within the devcontainer to open")
2125
return cmd
2226
}
2327
func createOpenInCodeInsidersCommand() *cobra.Command {
28+
var subFolder string
2429
cmd := &cobra.Command{
2530
Use: "open-in-code-insiders <path>",
2631
Short: "open the specified path devcontainer project in VS Code Insiders",
2732
Long: "Open the specified path (containing a .devcontainer folder in VS Code Insiders",
2833
RunE: func(cmd *cobra.Command, args []string) error {
29-
return launchDevContainer(cmd, "code-insiders", args)
34+
return launchDevContainer(cmd, "code-insiders", args, subFolder)
3035
},
3136
}
37+
cmd.Flags().StringVarP(&subFolder, "sub-folder", "s", "", "sub-folder within the devcontainer to open")
3238
return cmd
3339
}
3440

35-
func launchDevContainer(cmd *cobra.Command, appBase string, args []string) error {
41+
func launchDevContainer(cmd *cobra.Command, appBase string, args []string, subFolder string) error {
3642
if len(args) > 1 {
3743
return cmd.Usage()
3844
}
3945
path := "." // default to current directory
40-
if len(args) == 1 {
46+
if len(args) >= 1 {
4147
path = args[0]
4248
}
4349

44-
launchURI, err := devcontainers.GetDevContainerURI(path)
50+
// allow the command to be invoked from a subfolder of the folder containing the devcontainer.json by searching ancestor paths for a devcontainer.json
51+
devcontainerPath, err := devcontainers.FindDevContainerInAncestorPaths(path)
52+
if err != nil {
53+
return fmt.Errorf("error finding devcontainer.json: %s", err)
54+
}
55+
fmt.Printf("Found devcontainer.json at %s\n", devcontainerPath)
56+
57+
// If subFolder is not empty then use that as the path to open in VS Code (note it should be relative to the folder containing the devcontainer.json/.devcontainer folder)
58+
// If subFolder is empty and the current folder is contained within the folder containing the devcontainer.json, then open the current folder in VS Code, otherwise open the folder containing the devcontainer.json in VS Code
59+
if subFolder == "" {
60+
absDevContainerPath, err := filepath.Abs(devcontainerPath)
61+
if err != nil {
62+
return fmt.Errorf("error getting absolute path: %s", err)
63+
}
64+
absCurrentPath, err := filepath.Abs(path)
65+
if err != nil {
66+
return fmt.Errorf("error getting absolute path: %s", err)
67+
}
68+
if absCurrentPath != absDevContainerPath && strings.HasPrefix(absCurrentPath, absDevContainerPath) {
69+
subFolder, err = filepath.Rel(absDevContainerPath, absCurrentPath)
70+
if err != nil {
71+
return fmt.Errorf("error getting relative path: %s", err)
72+
}
73+
fmt.Printf("Opening subfolder %s within devcontainer\n", subFolder)
74+
}
75+
}
76+
77+
launchURI, err := devcontainers.GetDevContainerURI(devcontainerPath, subFolder)
4578
if err != nil {
4679
return err
4780
}

docs/open-in-code.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
The `devcontainer open-in-code` command opens the current folder in VS Code as a dev container, i.e. it skips the normal step of opening in VS Code and then clicking # on the "Re-open in container" prompt to reload the window as a dev container. With `devcontainer open-in-code` you get straight to the dev container!
44

5-
You can also use `devcontainer open-in-code <path>` to open a different folder as a devcontainer.
5+
You can also use `devcontainer open-in-code <path>` to open a the devcontainer from a different folder.
6+
7+
The command also supports the `--sub-folder` option. This allows you to specify a sub-folder within the folder that contains the devcontainer configuration (i.e. with the `.devcontainer` folder or `.devcontainer.json` file).
8+
9+
The `open-in-code` command will walk up the tree from the current/specified folder until it finds a folder with a devcontainer configuration, and then open that folder in VS Code as a dev container.
10+
If `--sub-folder` is not specified and the devcontainer configuration is found in a parent folder, relative path to the current folder is used as the sub-folder.
611

712
If you want to use the VS Code Insiders release, you can use `devcontainer open-in-code-insiders`.

internal/pkg/devcontainers/devcontainer.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,38 @@ func getDevContainerJsonPath(folderPath string) (string, error) {
2020

2121
return "", fmt.Errorf("devcontainer.json not found. Looked for %s", strings.Join(pathsToTest, ","))
2222
}
23+
24+
func FindDevContainerInAncestorPaths(folderPath string) (string, error) {
25+
currentPath, err := filepath.Abs(folderPath)
26+
if err != nil {
27+
return "", fmt.Errorf("error getting absolute path: %w", err)
28+
}
29+
30+
for {
31+
// Check if devcontainer.json exists in current path
32+
_, err := getDevContainerJsonPath(currentPath)
33+
if err == nil {
34+
return currentPath, nil
35+
}
36+
37+
// Check if this is a git repository root
38+
gitPath := filepath.Join(currentPath, ".git")
39+
gitInfo, gitErr := os.Stat(gitPath)
40+
isGitRoot := gitErr == nil && gitInfo.IsDir()
41+
42+
// If we're at a git root, stop searching (we already checked this folder)
43+
if isGitRoot {
44+
return "", fmt.Errorf("devcontainer.json not found in ancestor paths")
45+
}
46+
47+
// Move to parent directory
48+
parentPath := filepath.Dir(currentPath)
49+
50+
// Check if we've reached the root (parent is same as current)
51+
if parentPath == currentPath {
52+
return "", fmt.Errorf("devcontainer.json not found in ancestor paths")
53+
}
54+
55+
currentPath = parentPath
56+
}
57+
}

internal/pkg/devcontainers/remoteuri.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import (
1212
)
1313

1414
// GetDevContainerURI gets the devcontainer URI for a folder to launch using the VS Code --folder-uri switch
15-
func GetDevContainerURI(folderPath string) (string, error) {
15+
// If subFolder is specified, it is appended to the workspaceMountPath
16+
func GetDevContainerURI(folderPath string, subFolder string) (string, error) {
1617

1718
absPath, err := filepath.Abs(folderPath)
1819
if err != nil {
@@ -33,6 +34,9 @@ func GetDevContainerURI(folderPath string) (string, error) {
3334
if err != nil {
3435
return "", err
3536
}
37+
if subFolder != "" {
38+
workspaceMountPath = filepath.Join(workspaceMountPath, subFolder)
39+
}
3640
uri := fmt.Sprintf("vscode-remote://dev-container+%s%s", launchPathHex, workspaceMountPath)
3741

3842
return uri, nil

0 commit comments

Comments
 (0)