Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ All notable changes to the "delphi-devkit" extension will be documented in this

Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.

## [Unreleased]

### Changed

- **Run happens inside VS Code**: `Run` / `Run Selected Project` (F9) no longer launches the project's run target detached with its output thrown away. `ddk.projects.runIn` picks where it goes:
- `terminal` (default) runs the executable in its own **DDK Run: \<project\>** terminal — as a task — with the project's usual run parameters and the executable's directory as the working directory. Since a terminal is a real console (ConPTY), everything a console gives a program keeps working: **its own colors** — including a program coloring through the Windows console API, such as DUnitX's test runner — output the view follows, and keyboard input for a program that reads `stdin`. When the program exits the terminal stays open on its finished output, until it is closed or the next run of that project reuses and clears it (each project gets its own). A plain terminal whose process *is* the executable would not do: VS Code disposes it the moment that process ends, taking a short-lived program's output with it. Being a task, a run also appears in the *Run Task* history and repeats with *Rerun Last Task*.
- `output` pipes `stdout`/`stderr` into a new **DDK Run** output channel instead, framed by a header with the exact command line and a footer with the exit code (or terminating signal) and elapsed time — searchable text that survives the run, with concurrent runs labelled by project name. The channel is cleared when a run starts (unless another run is still writing to it) and, for a GUI application that prints nothing, is only revealed once output appears (`ddk.projects.runRevealOutput`). No console window is created either way. Output is decoded with Windows' own default charset for non-Unicode text — the system ANSI codepage (`GetACP`, e.g. 1252, read from the registry) — which is what a program writing to a redirected handle normally produces; `ddk.projects.runOutputEncoding` switches it to `oem` (the console codepage `chcp` reports), `utf8` (what a current Delphi RTL emits while redirected), a fixed codepage (`cp437`/`cp850`/`cp852`, `ibm866`, `windows-125x`, ISO 8859) or `auto`, which takes each line as UTF-8 when it is valid UTF-8 and as the ANSI codepage otherwise.
- `detached` keeps the previous behavior.

Why `terminal` is the default: a program that colors its output through `SetConsoleTextAttribute` writes no color information into `stdout` at all — the color is an attribute of the console screen buffer's cells, set by a side-channel API call that simply fails once the handle is a pipe. So in `output` mode those colors cannot be recovered by any means, and since the Output panel renders no ANSI escape sequences either, escape sequences a program does emit are stripped, together with carriage-return overwrites (a self-rewriting progress line keeps only its final state, as on screen) and other control characters, instead of appearing as `←[32m` litter. `output` mode also cannot forward keystrokes (a program reading input sees end-of-file), and the process is a child of VS Code, so closing VS Code breaks its output pipe.

`ddk run` (CLI/MCP) is unchanged and still launches detached.

## [2.6.0] - 2026-08-03

### Added
Expand Down
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,62 @@ disable it to always use only the saved Start Parameters, ignoring the
dproj's Run Parameters entirely. The CLI/MCP always fuse `Debugger_RunParams`
in, since there is no extension setting for them to consult.

In VS Code, a project started with `Run` / `Run Selected Project` (F9) runs
inside the editor instead of in a console window that closes on exit.
`ddk.projects.runIn` picks where:

* `terminal` (default) runs the executable in its own **DDK Run: \<project\>**
terminal — as a task — with the project's run parameters and the executable's
directory as the working directory. A terminal is a real console (ConPTY), so
everything a console gives a program keeps working: its own colors, output
that the view follows, and keyboard input for a program that reads `stdin`.
When the program exits, the terminal **stays open** on its finished output
until you close it or the next run of that project reuses and clears it (each
project gets its own terminal). Being a task, the run also shows up under
*Terminal > Run Task* history and can be repeated with *Rerun Last Task*.
* `output` pipes `stdout` and `stderr` into the **DDK Run** output channel
instead, framed by a header with the exact command line and a footer with the
exit code and elapsed time — searchable text that survives the run. The
channel is cleared when a run starts (unless another run is still writing to
it) and, for a GUI application that prints nothing, is only revealed once
output actually appears (`ddk.projects.runRevealOutput`).
* `detached` is the old behavior: launched detached, output discarded.

`ddk run` on the CLI is unaffected — it always launches detached.

What a pipe cannot carry, and why `terminal` is the default: a program coloring
its output through the Windows console API (`SetConsoleTextAttribute` — what
DUnitX's test runner uses) writes **no** color information into `stdout` at all.
The color is an attribute of the console screen buffer's cells, set by a
side-channel API call; with the handle redirected to a pipe that call simply
fails, so nothing about it reaches DDK, and no reader could recover it. Only a
real console produces those colors — which is exactly what ConPTY gives the
process, and it re-encodes them into escape sequences the terminal renders.
In `output` mode the text is therefore plain: the Output panel renders no ANSI
escape sequences either, so any that a program does emit are stripped, along
with carriage-return overwrites (a self-rewriting progress line keeps only its
final state) and other control characters, rather than showing up as `←[32m`
litter. Keyboard input is impossible there as well (a program reading `stdin`
sees end-of-file), the process is a child of VS Code — closing VS Code breaks
its output pipe — and VS Code's own "smart scroll" stops the panel from
following new output once the cursor sits off the last line
(`output.smartScroll.enabled: false`, or **View: Toggle Locked Scrolling**,
releases it).

Output in `output` mode is decoded with Windows' own default charset for
non-Unicode text — the system ANSI codepage (`GetACP`, e.g. 1252) — which is
what a program writing to a redirected handle normally produces;
`ddk.projects.runOutputEncoding` switches it to `oem` (the console codepage such
as CP850), `utf8` (what a current Delphi RTL writes while redirected), a fixed
codepage, or `auto`, which takes each line as UTF-8 when it is valid UTF-8 and
as the ANSI codepage otherwise. A terminal decodes its own output, so the
setting does not apply there.

Because the output channel cannot forward keystrokes, a captured program's
`stdin` is not connected: one that reads input sees end-of-file instead of
hanging invisibly. Run such a program from a terminal, or disable
`ddk.projects.runInOutputChannel`.

## Demos

### Add a Workspace and drag in a Project
Expand Down Expand Up @@ -261,6 +317,9 @@ in, since there is no extension setting for them to consult.

* `ddk.compiler.encoding`: Character encoding used to decode MSBuild output (`oem` by default, use `utf8` if your paths contain non-ASCII characters).
* `ddk.projects.useDebuggerRunParams`: When running a project, fuse the `.dproj`'s own `Debugger_RunParams` with the saved Start Parameters, dproj first (`true` by default). Disable to always use only the saved Start Parameters.
* `ddk.projects.runIn`: Where a run sends its output: `terminal` (default, a real console — the program's own colors, following output and keyboard input all work), `output` (piped into the **DDK Run** output channel: searchable text, no colors, no input) or `detached` (output discarded, as before).
* `ddk.projects.runOutputEncoding`: Encoding used to decode a running project's output in `output` mode (`ansi` by default: Windows' system ANSI codepage, e.g. 1252). Further choices: `auto` (per line UTF-8, falling back to ANSI), `utf8`, `oem` (console codepage), `cp437`/`cp850`/`cp852`, `ibm866`, `windows-1250`/`windows-1252` and the ISO 8859 variants.
* `ddk.projects.runRevealOutput`: When to reveal the **DDK Run** channel in `output` mode: `onOutput` (default, as soon as the program prints something), `always` or `never`.

## Compiler Configurations

Expand Down
79 changes: 79 additions & 0 deletions vscode_extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,71 @@
"default": true,
"markdownDescription": "When running a project, fuse the `.dproj`'s own `Debugger_RunParams` (Project > Options > Run in the Delphi IDE) with the saved `Set Start Parameters` value — dproj first, saved value appended after. Disable to always use only the saved Start Parameters, ignoring the dproj's Run Parameters entirely."
},
"ddk.projects.runIn": {
"type": "string",
"default": "terminal",
"enum": [
"terminal",
"output",
"detached"
],
"enumDescriptions": [
"A dedicated 'DDK Run: <project>' terminal — a real console, so the program's own colors, its full output and keyboard input all work",
"The DDK Run output channel — captured through a pipe: searchable text, but no colors and no keyboard input",
"Detached, with its output discarded (the behavior before output was captured at all)"
],
"markdownDescription": "Where `Run` / `Run Selected Project` sends a project's output.\n\n- `terminal` (default): the executable runs in its own **DDK Run: \\<project\\>** terminal, as a task. Because a terminal is a real console (ConPTY), a program coloring its output through the Windows console API — DUnitX's test runner, for example — keeps its colors, the view follows new output, and a program that reads input can be typed into. The terminal stays open on the finished output when the program exits, until you close it or the next run of that project reuses (and clears) it.\n- `output`: `stdout`/`stderr` are piped into the **DDK Run** output channel. The text is searchable and survives the run, but Windows console colors do not exist in a pipe and the Output panel renders no ANSI, so the output is plain text; keyboard input is impossible, and the process is a child of VS Code, so closing VS Code breaks its output pipe. `#ddk.projects.runOutputEncoding#` and `#ddk.projects.runRevealOutput#` apply to this mode.\n- `detached`: launched fully detached with its output discarded."
},
"ddk.projects.runOutputEncoding": {
"type": "string",
"default": "ansi",
"enum": [
"ansi",
"auto",
"utf8",
"oem",
"cp437",
"cp850",
"cp852",
"ibm866",
"windows-1250",
"windows-1252",
"iso-8859-1",
"iso-8859-2",
"iso-8859-15"
],
"enumDescriptions": [
"Windows' own default charset for non-Unicode text (the system ANSI codepage, e.g. 1252 on a Western European install)",
"Decode each line as UTF-8 when it is valid UTF-8, otherwise as the system ANSI codepage",
"UTF-8",
"The console codepage (what `chcp` reports, e.g. 850) — for a program that writes console/OEM bytes",
"CP437 (DOS US)",
"CP850 (DOS Western European)",
"CP852 (DOS Central European)",
"CP866 (DOS Cyrillic)",
"Windows-1250 (Central European)",
"Windows-1252 (Western European)",
"ISO 8859-1 (Latin-1)",
"ISO 8859-2 (Latin-2)",
"ISO 8859-15 (Latin-9)"
],
"markdownDescription": "Encoding used to decode the output of a running project shown in the **DDK Run** output channel. The default `ansi` is Windows' own default charset for non-Unicode text (the system ANSI codepage from `HKLM\\SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage\\ACP`, what `GetACP` returns — e.g. 1252), which is what a program writing to a redirected handle normally produces. Pick `oem` for a program that writes console codepage bytes (e.g. CP850), `utf8` for one that writes UTF-8 — which a current Delphi RTL does while its output is redirected — or `auto` to take each line as UTF-8 when it is valid UTF-8 and as the ANSI codepage otherwise, covering both kinds of program in one run. Only applies to `#ddk.projects.runIn#` = `output`; a terminal decodes its own output."
},
"ddk.projects.runRevealOutput": {
"type": "string",
"default": "onOutput",
"enum": [
"onOutput",
"always",
"never"
],
"enumDescriptions": [
"Reveal the DDK Run channel as soon as the program writes its first output",
"Reveal the DDK Run channel whenever a project is run",
"Never reveal the DDK Run channel by itself"
],
"markdownDescription": "When to bring the **DDK Run** output channel into view (without stealing focus from the editor). `onOutput` keeps the panel untouched for a GUI application that prints nothing. Only applies to `#ddk.projects.runIn#` = `output`."
},
"ddk.compiler.resultTimeout": {
"type": "number",
"default": 5000,
Expand Down Expand Up @@ -801,6 +866,20 @@
}
}
},
"taskDefinitions": [
{
"type": "ddk.run",
"required": [
"project"
],
"properties": {
"project": {
"type": "string",
"description": "Name of the DDK project whose executable this task runs."
}
}
}
],
"grammars": [
{
"language": "ddk.compiler",
Expand Down
10 changes: 10 additions & 0 deletions vscode_extension/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export namespace PROJECTS {
export const SortProjects = 'sortProjects';
export const CONFIG_PLATFORM_DISPLAY = 'configPlatformDisplay';
export const USE_DEBUGGER_RUN_PARAMS = 'useDebuggerRunParams';
export const RUN_IN = 'runIn';
export const RUN_OUTPUT_ENCODING = 'runOutputEncoding';
export const RUN_REVEAL_OUTPUT = 'runRevealOutput';
export namespace COMPILER {
export const NS = 'compiler';
export const CONFIGURATIONS = `${NS}.configurations`;
Expand Down Expand Up @@ -66,6 +69,13 @@ export namespace PROJECTS {
export const GENERATE_DELPHILSP_CONFIG = `${PROJECTS.CONFIG.KEY}.generateDelphiLspConfig`;
}

export namespace TASK {
/** Task type of a project run; contributed in package.json under `taskDefinitions`. */
export const RUN = 'ddk.run';
/** Task source — VS Code titles the terminal `<source>: <task name>`. */
export const RUN_SOURCE = 'DDK Run';
}

export namespace CONTEXT {
export const IS_GROUP_PROJECT_OPENED = 'ddk:isGroupProjectOpened';
export const IS_PROJECT_SELECTED = 'ddk:isProjectSelected';
Expand Down
20 changes: 6 additions & 14 deletions vscode_extension/src/projects/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { Coroutine, DelphiProjectTreeItemType } from '../types';
import { Entities } from './entities';
import { BaseFileItem } from './trees/items/baseFile';
import { ProjectItem } from './trees/items/project';
import { assertError, basenameNoExt, launchExecutable } from '../utils';
import { assertError, basenameNoExt } from '../utils';
import { ProjectRunner } from './runner';
import { WorkspaceItem } from './trees/items/workspaceItem';
import { Change } from '../client';
import { Option } from '../types';
Expand Down Expand Up @@ -74,12 +75,7 @@ export namespace ProjectsCommands {
window.showWarningMessage('Selected project has no associated executable or Host Application to run.');
return;
}
try {
launchExecutable(target, resolveEffectiveStartParameters(project));
window.showInformationMessage(`Running: ${target}`);
} catch (error) {
window.showErrorMessage(`Failed to launch executable: ${error}`);
}
ProjectRunner.run(target, resolveEffectiveStartParameters(project), project.name);
});
}
}
Expand Down Expand Up @@ -132,17 +128,13 @@ export namespace ProjectsCommands {
}

private static async runExecutable(item: BaseFileItem): Promise<void> {
const target = resolveRunTarget(item.project.entity);
const entity = item.project.entity;
const target = resolveRunTarget(entity);
if (!target) {
window.showWarningMessage(`No executable or Host Application found for: ${item.label}`);
return;
}
try {
launchExecutable(target, resolveEffectiveStartParameters(item.project.entity));
window.showInformationMessage(`Running: ${target}`);
} catch (error) {
window.showErrorMessage(`Failed to launch executable: ${error}`);
}
ProjectRunner.run(target, resolveEffectiveStartParameters(entity), entity.name);
}

private static async setStartParameters(item: BaseFileItem): Promise<void> {
Expand Down
Loading