Skip to content

Commit 6a04ad7

Browse files
authored
Add configurable working directory for Shiny app launch (#102)
1 parent 047a8ee commit 6a04ad7

File tree

7 files changed

+396
-23
lines changed

7 files changed

+396
-23
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
- In Positron, the Viewer pane now shows an interrupt button when running Shiny apps, allowing you to stop the app directly from the Viewer. ([#103](https://github.com/posit-dev/shiny-vscode/pull/103))
88

9+
New `shiny.runFrom` option can be used to run Shiny apps from the `"projectRoot"` (default) or the `"appDirectory"`. This can also be overridden at the app-level with a workspace `shiny.runFromOverride` setting. Use the new "Shiny: Run this app from..." command on an open Shiny app file to configure this setting for a specific app. (#102)
10+
911
## 1.1.0
1012

1113
- In Positron, the extension now uses the selected R runtime for Shiny for R apps. In VS Code, the extension also now consults the `r.rpath.mac`, `r.rpath.windows` or `r.rpath.linux` settings to find the R executable, before falling back to system settings. These settings are part of the [R Debugger extension](https://marketplace.visualstudio.com/items?itemName=RDebugger.r-debugger) ([#64](https://github.com/posit-dev/shiny-vscode/pull/64))

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,34 @@ Note that there is no setting for Python executable path or virtual environment.
5454
- `shiny.includeHeader`: Include the "Shiny" header in the Shinylive link when opening in app mode?
5555
- `shiny.shinylive.openAction`: What action should be taken when opening a Shinylive link? Options are `"open"` to open the link in an external browser, `"copy"` the link to the clipboard, or `"ask"`. The default is `"ask"`, which prompts you each time you create a Shinylive link.
5656
- `shiny.shinylive.host`: The Shinylive host used when creating a Shinylive link. The default is `"https://shinylive.io"`, which uses the latest released version of Shiny in Python or R. Or `"https://posit-dev.github.io/shinylive"`, which uses the latest development version of Shiny in Python or R.
57+
58+
### Working Directory Configuration
59+
60+
By default, Shiny apps run from the workspace root directory. This works well for most projects, but you may need apps to run from a different directory—for example, when using renv in a subfolder or organizing apps in a monorepo structure.
61+
62+
The extension provides two settings to control the working directory:
63+
64+
- `shiny.runFrom`: Global default for all apps in the workspace
65+
- `"projectRoot"` (default): Run apps from the workspace root
66+
- `"appDirectory"`: Run apps from the directory containing the app file
67+
68+
- `shiny.runFromOverrides`: Per-app overrides for specific files
69+
- For specific app paths (relative to the project root), choose which root-relative path the app is run from.
70+
- Empty string `""` means project root
71+
- This is a workspace-only setting
72+
73+
**Example configuration** for a project with apps in subfolders using renv:
74+
75+
```json
76+
{
77+
"shiny.runFrom": "appDirectory",
78+
"shiny.runFromOverrides": {
79+
"src/apps/main/app.py": "",
80+
"src/r-apps/dashboard/app.R": "src/r-apps"
81+
}
82+
}
83+
```
84+
85+
**Setting overrides via Command Palette:**
86+
87+
Instead of editing `settings.json` manually, you can use the **"Shiny: Run this app from..."** command to set the run from override for an open app file.

package.json

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@
9090
"command": "shiny.stopApp",
9191
"title": "Stop Shiny App",
9292
"icon": "$(debug-stop)"
93+
},
94+
{
95+
"category": "Shiny",
96+
"command": "shiny.setRunFromOverride",
97+
"title": "Run this app from...",
98+
"enablement": "(editorLangId == python || editorLangId == r) && workspaceFolderCount > 0"
9399
}
94100
],
95101
"menus": {
@@ -154,6 +160,7 @@
154160
"title": "Shiny",
155161
"properties": {
156162
"shiny.previewType": {
163+
"order": 1,
157164
"scope": "window",
158165
"type": "string",
159166
"description": "Where should the Shiny app preview open?",
@@ -172,36 +179,40 @@
172179
]
173180
},
174181
"shiny.timeoutOpenBrowser": {
182+
"order": 2,
175183
"type": "integer",
176184
"default": 10,
177185
"description": "Maximum wait time (in seconds) for the Shiny app to be ready before opening the browser."
178186
},
179-
"shiny.python.port": {
180-
"type": "integer",
181-
"default": 0,
182-
"description": "The port number Shiny should listen on when running a Shiny for Python app. (Use 0 to choose a random port.)"
183-
},
184-
"shiny.python.autoreloadPort": {
185-
"type": "integer",
186-
"default": 0,
187-
"description": "The port number Shiny should use for a supplemental WebSocket channel it uses to support reload-on-save. (Use 0 to choose a random port.)"
188-
},
189-
"shiny.python.debugJustMyCode": {
190-
"type": "boolean",
191-
"default": true,
192-
"description": "When running the \"Debug Shiny App\" command, only step through user-written code. Disable this to allow stepping through library code."
193-
},
194-
"shiny.r.port": {
195-
"type": "integer",
196-
"default": 0,
197-
"description": "The port number Shiny should listen on when running a Shiny for R app. (Use 0 to choose a random port.)"
187+
"shiny.runFrom": {
188+
"order": 3,
189+
"scope": "window",
190+
"type": "string",
191+
"description": "Default working directory for running Shiny apps",
192+
"enum": [
193+
"projectRoot",
194+
"appDirectory"
195+
],
196+
"default": "projectRoot",
197+
"enumDescriptions": [
198+
"Run apps from the workspace root directory",
199+
"Run apps from the directory containing the app file"
200+
]
198201
},
199-
"shiny.r.devmode": {
200-
"type": "boolean",
201-
"default": true,
202-
"markdownDescription": "Enable dev mode when running a Shiny for R app by running `shiny::devmode()` before launching the app."
202+
"shiny.runFromOverrides": {
203+
"order": 4,
204+
"scope": "resource",
205+
"type": "object",
206+
"description": "Override working directory for specific app files. This setting maps workspace-relative file paths (using forward slashes) to workspace-relative directory paths. You can use an empty string (`\"\"`) to run an app from the project root. Note that you can use the \"Run this app from...\" command from an active app file to configure this setting for that app.",
207+
"default": {},
208+
"patternProperties": {
209+
"^.*\\.(py|R)$": {
210+
"type": "string"
211+
}
212+
}
203213
},
204214
"shiny.shinylive.appMode": {
215+
"order": 5,
205216
"type": "string",
206217
"default": "ask",
207218
"description": "Which Shinylive mode to use when creating a Shinylive app.",
@@ -217,6 +228,7 @@
217228
]
218229
},
219230
"shiny.shinylive.openAction": {
231+
"order": 6,
220232
"type": "string",
221233
"default": "ask",
222234
"description": "Choose the default action upon creating a Shinylive link.",
@@ -232,11 +244,13 @@
232244
]
233245
},
234246
"shiny.shinylive.includeHeader": {
247+
"order": 7,
235248
"type": "boolean",
236249
"default": true,
237250
"description": "Include the Shiny header when creating Shinylive app links. Only relevant for app mode Shinylive links."
238251
},
239252
"shiny.shinylive.host": {
253+
"order": 8,
240254
"type": "string",
241255
"default": "https://shinylive.io",
242256
"description": "The default Shinylive host to use when creating Shinylive app links.",
@@ -248,6 +262,36 @@
248262
"Uses the latest released version of Shiny in Python or R.",
249263
"Uses the latest development version of Shiny in Python or R."
250264
]
265+
},
266+
"shiny.python.port": {
267+
"order": 9,
268+
"type": "integer",
269+
"default": 0,
270+
"description": "The port number Shiny should listen on when running a Shiny for Python app. (Use 0 to choose a random port.)"
271+
},
272+
"shiny.python.autoreloadPort": {
273+
"order": 10,
274+
"type": "integer",
275+
"default": 0,
276+
"description": "The port number Shiny should use for a supplemental WebSocket channel it uses to support reload-on-save. (Use 0 to choose a random port.)"
277+
},
278+
"shiny.python.debugJustMyCode": {
279+
"order": 11,
280+
"type": "boolean",
281+
"default": true,
282+
"description": "When running the \"Debug Shiny App\" command, only step through user-written code. Disable this to allow stepping through library code."
283+
},
284+
"shiny.r.port": {
285+
"order": 12,
286+
"type": "integer",
287+
"default": 0,
288+
"description": "The port number Shiny should listen on when running a Shiny for R app. (Use 0 to choose a random port.)"
289+
},
290+
"shiny.r.devmode": {
291+
"order": 13,
292+
"type": "boolean",
293+
"default": true,
294+
"markdownDescription": "Enable dev mode when running a Shiny for R app by running `shiny::devmode()` before launching the app."
251295
}
252296
}
253297
},

src/extension.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
setAppRunningStateChangeCallback,
1212
stopApp,
1313
} from "./run";
14+
import { setRunFromOverride } from "./set-run-from-override-command";
1415
import {
1516
shinyliveCreateFromActiveEditor,
1617
shinyliveCreateFromExplorer,
@@ -24,6 +25,10 @@ export async function activate(context: vscode.ExtensionContext) {
2425
vscode.commands.registerCommand("shiny.python.debugApp", pyDebugApp),
2526
vscode.commands.registerCommand("shiny.r.runApp", rRunApp),
2627
vscode.commands.registerCommand("shiny.stopApp", stopApp),
28+
vscode.commands.registerCommand(
29+
"shiny.setRunFromOverride",
30+
setRunFromOverride
31+
),
2732
vscode.commands.registerCommand(
2833
"shiny.shinylive.createFromActiveEditor",
2934
shinyliveCreateFromActiveEditor

src/run.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
envVarsForShell as envVarsForTerminal,
1616
escapeCommandForTerminal,
1717
} from "./shell-utils";
18+
import { resolveWorkingDirectory } from "./working-directory";
1819

1920
const DEBUG_NAME = "Debug Shiny app";
2021

@@ -98,8 +99,11 @@ export async function pyRunApp(): Promise<void> {
9899
const port = await getAppPort("run", "python");
99100
const autoreloadPort = await getAutoreloadPort("run");
100101

102+
const cwd = await resolveWorkingDirectory(path);
103+
101104
const terminal = await createTerminalAndCloseOthersWithSameName({
102105
name: "Shiny",
106+
cwd: cwd,
103107
env: {
104108
// We store the Python path here so we know whether the terminal can be
105109
// reused by us in the future (yes if the selected Python interpreter has
@@ -200,12 +204,15 @@ export async function pyDebugApp(): Promise<void> {
200204
.getConfiguration("shiny.python")
201205
.get("debugJustMyCode", true);
202206

207+
const cwd = await resolveWorkingDirectory(path);
208+
203209
await vscode.debug.startDebugging(undefined, {
204210
type: "python",
205211
name: DEBUG_NAME,
206212
request: "launch",
207213
module: "shiny",
208214
args: ["run", "--port", port.toString(), path],
215+
cwd: cwd,
209216
jinja: true,
210217
justMyCode,
211218
stopOnEntry: false,
@@ -231,8 +238,11 @@ export async function rRunApp(): Promise<void> {
231238
// TODO: Is this needed for Shiny for R too?
232239
// const autoreloadPort = await getAutoreloadPort("run");
233240

241+
const cwd = await resolveWorkingDirectory(pathFile);
242+
234243
const terminal = await createTerminalAndCloseOthersWithSameName({
235244
name: "Shiny",
245+
cwd: cwd,
236246
env: {
237247
// We save this here so escapeCommandForTerminal knows what shell
238248
// semantics to use when escaping arguments. A bit magical, but oh well.

0 commit comments

Comments
 (0)