@psnext/slingcli 2.4.20260526-1 → 2.4.20260527-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/sling.js +4 -1
- package/node_modules/@aws-sdk/core/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-env/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-http/dist-cjs/fromHttp/fromHttp.js +12 -6
- package/node_modules/@aws-sdk/credential-provider-http/dist-es/fromHttp/fromHttp.js +12 -6
- package/node_modules/@aws-sdk/credential-provider-http/package.json +3 -2
- package/node_modules/@aws-sdk/credential-provider-ini/package.json +9 -9
- package/node_modules/@aws-sdk/credential-provider-login/package.json +3 -3
- package/node_modules/@aws-sdk/credential-provider-node/package.json +7 -7
- package/node_modules/@aws-sdk/credential-provider-process/package.json +2 -2
- package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/package.json +3 -3
- package/node_modules/@aws-sdk/credential-provider-sso/package.json +4 -4
- package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +3 -3
- package/node_modules/@aws-sdk/middleware-websocket/package.json +2 -2
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/cognito-identity/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/signin/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso-oidc/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sts/index.js +1 -1
- package/node_modules/@aws-sdk/nested-clients/package.json +3 -3
- package/node_modules/@aws-sdk/signature-v4-multi-region/package.json +1 -2
- package/node_modules/@aws-sdk/xml-builder/package.json +2 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +18 -24
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/cli.js +14 -0
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +73 -58
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +8 -22
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +60 -40
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +55 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +45 -69
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +5 -2
- package/node_modules/@earendil-works/pi-ai/package.json +4 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/file-processor.js +2 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +18 -12
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-runtime.js +2 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js +3 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +8 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js +2 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js +18 -24
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/export-html/index.js +8 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/export-html/template.js +6 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js +12 -29
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +5 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/output-guard.js +52 -22
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js +63 -36
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js +6 -20
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js +38 -31
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +9 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +32 -24
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +6 -13
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/skills.js +8 -22
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js +54 -53
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit-diff.js +8 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js +44 -81
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/file-mutation-queue.js +27 -12
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.js +2 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.js +3 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js +5 -5
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/output-accumulator.js +9 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/path-utils.js +39 -21
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +9 -8
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/truncate.js +12 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js +20 -35
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +5 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -5
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +29 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/user-message.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js +37 -28
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.js +64 -7
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +15 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/clipboard-native.js +14 -8
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize-core.js +124 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize-worker.js +31 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize.js +75 -115
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/paths.js +33 -7
- package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +13 -26
- package/node_modules/@earendil-works/pi-coding-agent/package.json +6 -6
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +40 -55
- package/node_modules/@earendil-works/pi-tui/dist/components/input.js +2 -2
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +12 -2
- package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.js +53 -0
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +4 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +38 -16
- package/node_modules/@earendil-works/pi-tui/dist/utils.js +19 -12
- package/node_modules/@earendil-works/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
- package/node_modules/@earendil-works/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
- package/node_modules/@earendil-works/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/node_modules/@earendil-works/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/node_modules/@earendil-works/pi-tui/package.json +3 -5
- package/node_modules/@smithy/node-http-handler/package.json +2 -2
- package/package.json +6 -6
- package/slingshot/index.js +246 -244
- package/node_modules/koffi/CHANGELOG.md +0 -1093
- package/node_modules/koffi/LICENSE.txt +0 -22
- package/node_modules/koffi/README.md +0 -43
- package/node_modules/koffi/build/koffi/darwin_arm64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/darwin_x64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/freebsd_arm64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/freebsd_ia32/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/freebsd_x64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/linux_arm64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/linux_armhf/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/linux_ia32/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/linux_loong64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/linux_riscv64d/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/linux_x64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/musl_arm64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/musl_x64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/openbsd_ia32/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/openbsd_x64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/win32_arm64/koffi.exp +0 -0
- package/node_modules/koffi/build/koffi/win32_arm64/koffi.lib +0 -0
- package/node_modules/koffi/build/koffi/win32_arm64/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/win32_ia32/koffi.exp +0 -0
- package/node_modules/koffi/build/koffi/win32_ia32/koffi.lib +0 -0
- package/node_modules/koffi/build/koffi/win32_ia32/koffi.node +0 -0
- package/node_modules/koffi/build/koffi/win32_x64/koffi.exp +0 -0
- package/node_modules/koffi/build/koffi/win32_x64/koffi.lib +0 -0
- package/node_modules/koffi/build/koffi/win32_x64/koffi.node +0 -0
- package/node_modules/koffi/doc/benchmarks.md +0 -126
- package/node_modules/koffi/doc/callbacks.md +0 -210
- package/node_modules/koffi/doc/contribute.md +0 -148
- package/node_modules/koffi/doc/functions.md +0 -250
- package/node_modules/koffi/doc/index.md +0 -61
- package/node_modules/koffi/doc/input.md +0 -471
- package/node_modules/koffi/doc/migration.md +0 -159
- package/node_modules/koffi/doc/misc.md +0 -180
- package/node_modules/koffi/doc/output.md +0 -305
- package/node_modules/koffi/doc/packaging.md +0 -88
- package/node_modules/koffi/doc/platforms.md +0 -36
- package/node_modules/koffi/doc/pointers.md +0 -328
- package/node_modules/koffi/doc/start.md +0 -118
- package/node_modules/koffi/doc/unions.md +0 -186
- package/node_modules/koffi/doc/variables.md +0 -102
- package/node_modules/koffi/index.d.ts +0 -288
- package/node_modules/koffi/index.js +0 -634
- package/node_modules/koffi/indirect.js +0 -533
- package/node_modules/koffi/lib/native/base/base.cc +0 -11015
- package/node_modules/koffi/lib/native/base/base.hh +0 -6003
- package/node_modules/koffi/lib/native/base/crc.inc +0 -2214
- package/node_modules/koffi/lib/native/base/crc_gen.py +0 -72
- package/node_modules/koffi/lib/native/base/mimetypes.inc +0 -1248
- package/node_modules/koffi/lib/native/base/mimetypes_gen.py +0 -58
- package/node_modules/koffi/lib/native/base/tower.cc +0 -821
- package/node_modules/koffi/lib/native/base/tower.hh +0 -81
- package/node_modules/koffi/lib/native/base/unicode.inc +0 -408
- package/node_modules/koffi/lib/native/base/unicode_gen.py +0 -152
- package/node_modules/koffi/package.json +0 -38
- package/node_modules/koffi/src/cnoke/LICENSE.txt +0 -22
- package/node_modules/koffi/src/cnoke/README.md +0 -99
- package/node_modules/koffi/src/cnoke/assets/FindCNoke.cmake +0 -127
- package/node_modules/koffi/src/cnoke/assets/toolchains.json +0 -126
- package/node_modules/koffi/src/cnoke/assets/win_delay_hook.c +0 -36
- package/node_modules/koffi/src/cnoke/cnoke.js +0 -170
- package/node_modules/koffi/src/cnoke/package.json +0 -24
- package/node_modules/koffi/src/cnoke/src/builder.js +0 -511
- package/node_modules/koffi/src/cnoke/src/index.js +0 -10
- package/node_modules/koffi/src/cnoke/src/tools.js +0 -407
- package/node_modules/koffi/src/koffi/CMakeLists.txt +0 -182
- package/node_modules/koffi/src/koffi/src/abi_arm32.cc +0 -1018
- package/node_modules/koffi/src/koffi/src/abi_arm32_asm.S +0 -169
- package/node_modules/koffi/src/koffi/src/abi_arm64.cc +0 -1295
- package/node_modules/koffi/src/koffi/src/abi_arm64_asm.S +0 -195
- package/node_modules/koffi/src/koffi/src/abi_arm64_asm.asm +0 -174
- package/node_modules/koffi/src/koffi/src/abi_loong64.cc +0 -5
- package/node_modules/koffi/src/koffi/src/abi_loong64_asm.S +0 -204
- package/node_modules/koffi/src/koffi/src/abi_riscv64.cc +0 -915
- package/node_modules/koffi/src/koffi/src/abi_riscv64_asm.S +0 -203
- package/node_modules/koffi/src/koffi/src/abi_x64_sysv.cc +0 -939
- package/node_modules/koffi/src/koffi/src/abi_x64_sysv_asm.S +0 -231
- package/node_modules/koffi/src/koffi/src/abi_x64_win.cc +0 -715
- package/node_modules/koffi/src/koffi/src/abi_x64_win_asm.S +0 -166
- package/node_modules/koffi/src/koffi/src/abi_x64_win_asm.asm +0 -192
- package/node_modules/koffi/src/koffi/src/abi_x86.cc +0 -860
- package/node_modules/koffi/src/koffi/src/abi_x86_asm.S +0 -193
- package/node_modules/koffi/src/koffi/src/abi_x86_asm.asm +0 -177
- package/node_modules/koffi/src/koffi/src/call.cc +0 -1326
- package/node_modules/koffi/src/koffi/src/call.hh +0 -179
- package/node_modules/koffi/src/koffi/src/errno.inc +0 -462
- package/node_modules/koffi/src/koffi/src/ffi.cc +0 -2702
- package/node_modules/koffi/src/koffi/src/ffi.hh +0 -354
- package/node_modules/koffi/src/koffi/src/init.js +0 -105
- package/node_modules/koffi/src/koffi/src/parser.cc +0 -220
- package/node_modules/koffi/src/koffi/src/parser.hh +0 -54
- package/node_modules/koffi/src/koffi/src/util.cc +0 -1807
- package/node_modules/koffi/src/koffi/src/util.hh +0 -221
- package/node_modules/koffi/src/koffi/src/uv.cc +0 -193
- package/node_modules/koffi/src/koffi/src/uv.def +0 -10
- package/node_modules/koffi/src/koffi/src/uv.hh +0 -40
- package/node_modules/koffi/src/koffi/src/win32.cc +0 -198
- package/node_modules/koffi/src/koffi/src/win32.hh +0 -119
- package/node_modules/koffi/src/koffi/tools/write_trampolines.js +0 -77
- package/node_modules/koffi/vendor/node-addon-api/LICENSE.md +0 -9
- package/node_modules/koffi/vendor/node-addon-api/README.md +0 -95
- package/node_modules/koffi/vendor/node-addon-api/napi-inl.deprecated.h +0 -186
- package/node_modules/koffi/vendor/node-addon-api/napi-inl.h +0 -7033
- package/node_modules/koffi/vendor/node-addon-api/napi.h +0 -3309
- package/node_modules/koffi/vendor/node-api-headers/LICENSE +0 -21
- package/node_modules/koffi/vendor/node-api-headers/README.md +0 -95
- package/node_modules/koffi/vendor/node-api-headers/def/js_native_api.def +0 -125
- package/node_modules/koffi/vendor/node-api-headers/def/node_api.def +0 -157
- package/node_modules/koffi/vendor/node-api-headers/include/js_native_api.h +0 -591
- package/node_modules/koffi/vendor/node-api-headers/include/js_native_api_types.h +0 -210
- package/node_modules/koffi/vendor/node-api-headers/include/node_api.h +0 -265
- package/node_modules/koffi/vendor/node-api-headers/include/node_api_types.h +0 -58
- package/node_modules/koffi/vendor/node-api-headers/include/uv/aix.h +0 -32
- package/node_modules/koffi/vendor/node-api-headers/include/uv/bsd.h +0 -34
- package/node_modules/koffi/vendor/node-api-headers/include/uv/darwin.h +0 -61
- package/node_modules/koffi/vendor/node-api-headers/include/uv/errno.h +0 -483
- package/node_modules/koffi/vendor/node-api-headers/include/uv/linux.h +0 -34
- package/node_modules/koffi/vendor/node-api-headers/include/uv/os390.h +0 -33
- package/node_modules/koffi/vendor/node-api-headers/include/uv/posix.h +0 -31
- package/node_modules/koffi/vendor/node-api-headers/include/uv/sunos.h +0 -44
- package/node_modules/koffi/vendor/node-api-headers/include/uv/threadpool.h +0 -37
- package/node_modules/koffi/vendor/node-api-headers/include/uv/tree.h +0 -521
- package/node_modules/koffi/vendor/node-api-headers/include/uv/unix.h +0 -512
- package/node_modules/koffi/vendor/node-api-headers/include/uv/version.h +0 -43
- package/node_modules/koffi/vendor/node-api-headers/include/uv/win.h +0 -698
- package/node_modules/koffi/vendor/node-api-headers/include/uv.h +0 -1990
|
@@ -1,126 +1,86 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
maxHeight: 2000,
|
|
8
|
-
maxBytes: DEFAULT_MAX_BYTES,
|
|
9
|
-
jpegQuality: 80,
|
|
10
|
-
};
|
|
11
|
-
function encodeCandidate(buffer, mimeType) {
|
|
12
|
-
const data = Buffer.from(buffer).toString("base64");
|
|
13
|
-
return {
|
|
14
|
-
data,
|
|
15
|
-
encodedSize: Buffer.byteLength(data, "utf-8"),
|
|
16
|
-
mimeType,
|
|
17
|
-
};
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
import { resizeImageInProcess } from "./image-resize-core.js";
|
|
3
|
+
function toTransferableBytes(input) {
|
|
4
|
+
// Transfer detaches the buffer, so transfer a worker-owned copy and leave the
|
|
5
|
+
// caller's bytes intact.
|
|
6
|
+
return new Uint8Array(input);
|
|
18
7
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* 1. First resize to maxWidth/maxHeight
|
|
28
|
-
* 2. Try both PNG and JPEG formats, pick the smaller one
|
|
29
|
-
* 3. If still too large, try JPEG with decreasing quality
|
|
30
|
-
* 4. If still too large, progressively reduce dimensions until 1x1
|
|
31
|
-
*/
|
|
32
|
-
export async function resizeImage(img, options) {
|
|
33
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
34
|
-
const inputBuffer = Buffer.from(img.data, "base64");
|
|
35
|
-
const inputBase64Size = Buffer.byteLength(img.data, "utf-8");
|
|
36
|
-
const photon = await loadPhoton();
|
|
37
|
-
if (!photon) {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
let image;
|
|
8
|
+
function isResizeImageWorkerResponse(value) {
|
|
9
|
+
return value !== null && typeof value === "object";
|
|
10
|
+
}
|
|
11
|
+
function createResizeWorker(workerSpecifier) {
|
|
12
|
+
return new Worker(workerSpecifier);
|
|
13
|
+
}
|
|
14
|
+
async function resizeImageInWorker(workerSpecifier, inputBytes, mimeType, options) {
|
|
15
|
+
const worker = createResizeWorker(workerSpecifier);
|
|
41
16
|
try {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// Check if already within all limits (dimensions AND encoded size)
|
|
51
|
-
if (originalWidth <= opts.maxWidth && originalHeight <= opts.maxHeight && inputBase64Size < opts.maxBytes) {
|
|
52
|
-
return {
|
|
53
|
-
data: img.data,
|
|
54
|
-
mimeType: img.mimeType ?? `image/${format}`,
|
|
55
|
-
originalWidth,
|
|
56
|
-
originalHeight,
|
|
57
|
-
width: originalWidth,
|
|
58
|
-
height: originalHeight,
|
|
59
|
-
wasResized: false,
|
|
17
|
+
const inputBytesForWorker = toTransferableBytes(inputBytes);
|
|
18
|
+
return await new Promise((resolve, reject) => {
|
|
19
|
+
let settled = false;
|
|
20
|
+
const settle = (result) => {
|
|
21
|
+
if (settled)
|
|
22
|
+
return;
|
|
23
|
+
settled = true;
|
|
24
|
+
resolve(result);
|
|
60
25
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
targetHeight = opts.maxHeight;
|
|
72
|
-
}
|
|
73
|
-
function tryEncodings(width, height, jpegQualities) {
|
|
74
|
-
const resized = photon.resize(image, width, height, photon.SamplingFilter.Lanczos3);
|
|
75
|
-
try {
|
|
76
|
-
const candidates = [encodeCandidate(resized.get_bytes(), "image/png")];
|
|
77
|
-
for (const quality of jpegQualities) {
|
|
78
|
-
candidates.push(encodeCandidate(resized.get_bytes_jpeg(quality), "image/jpeg"));
|
|
26
|
+
const fail = (error) => {
|
|
27
|
+
if (settled)
|
|
28
|
+
return;
|
|
29
|
+
settled = true;
|
|
30
|
+
reject(error);
|
|
31
|
+
};
|
|
32
|
+
worker.once("message", (message) => {
|
|
33
|
+
if (!isResizeImageWorkerResponse(message)) {
|
|
34
|
+
fail(new Error("Invalid image resize worker response"));
|
|
35
|
+
return;
|
|
79
36
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
resized.free();
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
const qualitySteps = Array.from(new Set([opts.jpegQuality, 85, 70, 55, 40]));
|
|
87
|
-
let currentWidth = targetWidth;
|
|
88
|
-
let currentHeight = targetHeight;
|
|
89
|
-
while (true) {
|
|
90
|
-
const candidates = tryEncodings(currentWidth, currentHeight, qualitySteps);
|
|
91
|
-
for (const candidate of candidates) {
|
|
92
|
-
if (candidate.encodedSize < opts.maxBytes) {
|
|
93
|
-
return {
|
|
94
|
-
data: candidate.data,
|
|
95
|
-
mimeType: candidate.mimeType,
|
|
96
|
-
originalWidth,
|
|
97
|
-
originalHeight,
|
|
98
|
-
width: currentWidth,
|
|
99
|
-
height: currentHeight,
|
|
100
|
-
wasResized: true,
|
|
101
|
-
};
|
|
37
|
+
if (message.error) {
|
|
38
|
+
fail(new Error(message.error));
|
|
39
|
+
return;
|
|
102
40
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
catch {
|
|
118
|
-
return null;
|
|
41
|
+
settle(message.result ?? null);
|
|
42
|
+
});
|
|
43
|
+
worker.once("error", fail);
|
|
44
|
+
worker.once("exit", (code) => {
|
|
45
|
+
if (!settled) {
|
|
46
|
+
fail(new Error(`Image resize worker exited with code ${code}`));
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
worker.postMessage({
|
|
50
|
+
inputBytes: inputBytesForWorker,
|
|
51
|
+
mimeType,
|
|
52
|
+
options,
|
|
53
|
+
}, [inputBytesForWorker.buffer]);
|
|
54
|
+
});
|
|
119
55
|
}
|
|
120
56
|
finally {
|
|
121
|
-
|
|
122
|
-
|
|
57
|
+
void worker.terminate().catch(() => undefined);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resize an image to fit within the specified max dimensions and encoded file size.
|
|
62
|
+
* Runs Photon in a worker thread so WASM decoding, resizing, and encoding do not
|
|
63
|
+
* block the TUI event loop. If the worker cannot be loaded (for example in some
|
|
64
|
+
* Bun compiled executable layouts), fall back to in-process resizing so image
|
|
65
|
+
* reads still work.
|
|
66
|
+
*/
|
|
67
|
+
export async function resizeImage(inputBytes, mimeType, options) {
|
|
68
|
+
const isTypeScriptRuntime = import.meta.url.endsWith(".ts");
|
|
69
|
+
const workerUrl = new URL(isTypeScriptRuntime ? "./image-resize-worker.ts" : "./image-resize-worker.js", import.meta.url);
|
|
70
|
+
// Bun compiled executables resolve worker entrypoints by string path, not via
|
|
71
|
+
// new URL(..., import.meta.url). Try the string path first under Bun so the
|
|
72
|
+
// release binary uses the embedded worker instead of falling back in-process.
|
|
73
|
+
if (typeof process.versions.bun === "string") {
|
|
74
|
+
try {
|
|
75
|
+
return await resizeImageInWorker("./src/utils/image-resize-worker.ts", inputBytes, mimeType, options);
|
|
123
76
|
}
|
|
77
|
+
catch { }
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
return await resizeImageInWorker(workerUrl, inputBytes, mimeType, options);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return resizeImageInProcess(inputBytes, mimeType, options);
|
|
124
84
|
}
|
|
125
85
|
}
|
|
126
86
|
/**
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { realpathSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { isAbsolute, join, resolve as nodeResolvePath, relative, sep } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
3
5
|
import { spawnProcessSync } from "./child-process.js";
|
|
6
|
+
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
4
7
|
/**
|
|
5
8
|
* Resolve a path to its canonical (real) form, following symlinks.
|
|
6
9
|
* Falls back to the raw path if resolution fails (e.g. the target does
|
|
@@ -17,12 +20,12 @@ export function canonicalizePath(path) {
|
|
|
17
20
|
}
|
|
18
21
|
/**
|
|
19
22
|
* Returns true if the value is NOT a package source (npm:, git:, etc.)
|
|
20
|
-
* or a URL protocol. Bare names
|
|
23
|
+
* or a remote URL protocol. Bare names, relative paths, and file: URLs
|
|
21
24
|
* are considered local.
|
|
22
25
|
*/
|
|
23
26
|
export function isLocalPath(value) {
|
|
24
27
|
const trimmed = value.trim();
|
|
25
|
-
// Known non-local prefixes
|
|
28
|
+
// Known non-local prefixes. file: URLs are local paths and are intentionally resolved by resolvePath().
|
|
26
29
|
if (trimmed.startsWith("npm:") ||
|
|
27
30
|
trimmed.startsWith("git:") ||
|
|
28
31
|
trimmed.startsWith("github:") ||
|
|
@@ -33,19 +36,42 @@ export function isLocalPath(value) {
|
|
|
33
36
|
}
|
|
34
37
|
return true;
|
|
35
38
|
}
|
|
36
|
-
function
|
|
37
|
-
|
|
39
|
+
export function normalizePath(input, options = {}) {
|
|
40
|
+
let normalized = options.trim ? input.trim() : input;
|
|
41
|
+
if (options.normalizeUnicodeSpaces) {
|
|
42
|
+
normalized = normalized.replace(UNICODE_SPACES, " ");
|
|
43
|
+
}
|
|
44
|
+
if (options.stripAtPrefix && normalized.startsWith("@")) {
|
|
45
|
+
normalized = normalized.slice(1);
|
|
46
|
+
}
|
|
47
|
+
if (options.expandTilde ?? true) {
|
|
48
|
+
const home = options.homeDir ?? homedir();
|
|
49
|
+
if (normalized === "~")
|
|
50
|
+
return home;
|
|
51
|
+
if (normalized.startsWith("~/") || (process.platform === "win32" && normalized.startsWith("~\\"))) {
|
|
52
|
+
return join(home, normalized.slice(2));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (/^file:\/\//.test(normalized)) {
|
|
56
|
+
return fileURLToPath(normalized);
|
|
57
|
+
}
|
|
58
|
+
return normalized;
|
|
59
|
+
}
|
|
60
|
+
export function resolvePath(input, baseDir = process.cwd(), options = {}) {
|
|
61
|
+
const normalized = normalizePath(input, options);
|
|
62
|
+
const normalizedBaseDir = normalizePath(baseDir);
|
|
63
|
+
return isAbsolute(normalized) ? nodeResolvePath(normalized) : nodeResolvePath(normalizedBaseDir, normalized);
|
|
38
64
|
}
|
|
39
65
|
export function getCwdRelativePath(filePath, cwd) {
|
|
40
66
|
const resolvedCwd = resolvePath(cwd);
|
|
41
|
-
const resolvedPath =
|
|
67
|
+
const resolvedPath = resolvePath(filePath, resolvedCwd);
|
|
42
68
|
const relativePath = relative(resolvedCwd, resolvedPath);
|
|
43
69
|
const isInsideCwd = relativePath === "" ||
|
|
44
70
|
(relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath));
|
|
45
71
|
return isInsideCwd ? relativePath || "." : undefined;
|
|
46
72
|
}
|
|
47
73
|
export function formatPathRelativeToCwdOrAbsolute(filePath, cwd) {
|
|
48
|
-
const absolutePath =
|
|
74
|
+
const absolutePath = resolvePath(filePath, cwd);
|
|
49
75
|
return (getCwdRelativePath(absolutePath, cwd) ?? absolutePath).split(sep).join("/");
|
|
50
76
|
}
|
|
51
77
|
export function markPathIgnoredByCloudSync(path) {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@earendil-works/pi-coding-agent",
|
|
3
|
-
"version": "0.75.
|
|
3
|
+
"version": "0.75.5",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@earendil-works/pi-coding-agent",
|
|
9
|
-
"version": "0.75.
|
|
9
|
+
"version": "0.75.5",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@earendil-works/pi-agent-core": "^0.75.
|
|
13
|
-
"@earendil-works/pi-ai": "^0.75.
|
|
14
|
-
"@earendil-works/pi-tui": "^0.75.
|
|
12
|
+
"@earendil-works/pi-agent-core": "^0.75.5",
|
|
13
|
+
"@earendil-works/pi-ai": "^0.75.5",
|
|
14
|
+
"@earendil-works/pi-tui": "^0.75.5",
|
|
15
15
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
16
16
|
"chalk": "5.6.2",
|
|
17
17
|
"cross-spawn": "7.0.6",
|
|
@@ -473,11 +473,11 @@
|
|
|
473
473
|
}
|
|
474
474
|
},
|
|
475
475
|
"node_modules/@earendil-works/pi-agent-core": {
|
|
476
|
-
"version": "0.75.
|
|
477
|
-
"resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.75.
|
|
476
|
+
"version": "0.75.5",
|
|
477
|
+
"resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.75.5.tgz",
|
|
478
478
|
"license": "MIT",
|
|
479
479
|
"dependencies": {
|
|
480
|
-
"@earendil-works/pi-ai": "^0.75.
|
|
480
|
+
"@earendil-works/pi-ai": "^0.75.5",
|
|
481
481
|
"ignore": "7.0.5",
|
|
482
482
|
"typebox": "1.1.38",
|
|
483
483
|
"yaml": "2.9.0"
|
|
@@ -487,12 +487,13 @@
|
|
|
487
487
|
}
|
|
488
488
|
},
|
|
489
489
|
"node_modules/@earendil-works/pi-ai": {
|
|
490
|
-
"version": "0.75.
|
|
491
|
-
"resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.75.
|
|
490
|
+
"version": "0.75.5",
|
|
491
|
+
"resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.75.5.tgz",
|
|
492
492
|
"license": "MIT",
|
|
493
493
|
"dependencies": {
|
|
494
494
|
"@anthropic-ai/sdk": "0.91.1",
|
|
495
495
|
"@aws-sdk/client-bedrock-runtime": "3.1048.0",
|
|
496
|
+
"@smithy/node-http-handler": "4.7.3",
|
|
496
497
|
"@google/genai": "1.52.0",
|
|
497
498
|
"@mistralai/mistralai": "2.2.1",
|
|
498
499
|
"http-proxy-agent": "7.0.2",
|
|
@@ -509,16 +510,13 @@
|
|
|
509
510
|
}
|
|
510
511
|
},
|
|
511
512
|
"node_modules/@earendil-works/pi-tui": {
|
|
512
|
-
"version": "0.75.
|
|
513
|
-
"resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.75.
|
|
513
|
+
"version": "0.75.5",
|
|
514
|
+
"resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.75.5.tgz",
|
|
514
515
|
"license": "MIT",
|
|
515
516
|
"dependencies": {
|
|
516
517
|
"get-east-asian-width": "1.6.0",
|
|
517
518
|
"marked": "15.0.12"
|
|
518
519
|
},
|
|
519
|
-
"optionalDependencies": {
|
|
520
|
-
"koffi": "2.16.2"
|
|
521
|
-
},
|
|
522
520
|
"engines": {
|
|
523
521
|
"node": ">=22.19.0"
|
|
524
522
|
}
|
|
@@ -1369,17 +1367,6 @@
|
|
|
1369
1367
|
"safe-buffer": "^5.0.1"
|
|
1370
1368
|
}
|
|
1371
1369
|
},
|
|
1372
|
-
"node_modules/koffi": {
|
|
1373
|
-
"version": "2.16.2",
|
|
1374
|
-
"resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.2.tgz",
|
|
1375
|
-
"integrity": "sha512-owU0MRwv6xkrVqCd+33uw6BaYppkTRXbO/rVdJNI2dvZG0gzyRhYwW25eWtc5pauwK8TGh3AbkFONSezdykfSA==",
|
|
1376
|
-
"license": "MIT",
|
|
1377
|
-
"optional": true,
|
|
1378
|
-
"hasInstallScript": true,
|
|
1379
|
-
"funding": {
|
|
1380
|
-
"url": "https://liberapay.com/Koromix"
|
|
1381
|
-
}
|
|
1382
|
-
},
|
|
1383
1370
|
"node_modules/long": {
|
|
1384
1371
|
"version": "5.3.2",
|
|
1385
1372
|
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@earendil-works/pi-coding-agent",
|
|
3
|
-
"version": "0.75.
|
|
3
|
+
"version": "0.75.5",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
7
7
|
"configDir": ".sling",
|
|
8
|
-
"name":
|
|
8
|
+
"name":"sling"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
11
|
"pi": "dist/cli.js"
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"scripts": {
|
|
33
33
|
"clean": "shx rm -rf dist",
|
|
34
34
|
"build": "tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js && npm run copy-assets",
|
|
35
|
-
"build:binary": "npm --prefix ../tui run build && npm --prefix ../ai run build && npm --prefix ../agent run build && npm run build && bun build --compile ./dist/bun/cli.js --outfile dist/pi && npm run copy-binary-assets",
|
|
35
|
+
"build:binary": "npm --prefix ../tui run build && npm --prefix ../ai run build && npm --prefix ../agent run build && npm run build && bun build --compile ./dist/bun/cli.js ./src/utils/image-resize-worker.ts --outfile dist/pi && npm run copy-binary-assets",
|
|
36
36
|
"copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/",
|
|
37
37
|
"copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/assets && shx cp src/modes/interactive/assets/*.png dist/assets/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
|
|
38
38
|
"test": "vitest --run",
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@earendil-works/pi-agent-core": "^0.75.
|
|
44
|
-
"@earendil-works/pi-ai": "^0.75.
|
|
45
|
-
"@earendil-works/pi-tui": "^0.75.
|
|
43
|
+
"@earendil-works/pi-agent-core": "^0.75.5",
|
|
44
|
+
"@earendil-works/pi-ai": "^0.75.5",
|
|
45
|
+
"@earendil-works/pi-tui": "^0.75.5",
|
|
46
46
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
47
47
|
"chalk": "5.6.2",
|
|
48
48
|
"cross-spawn": "7.0.6",
|
|
@@ -3,9 +3,10 @@ import { decodePrintableKey, matchesKey } from "../keys.js";
|
|
|
3
3
|
import { KillRing } from "../kill-ring.js";
|
|
4
4
|
import { CURSOR_MARKER } from "../tui.js";
|
|
5
5
|
import { UndoStack } from "../undo-stack.js";
|
|
6
|
-
import {
|
|
6
|
+
import { getGraphemeSegmenter, getWordSegmenter, isWhitespaceChar, truncateToWidth, visibleWidth } from "../utils.js";
|
|
7
7
|
import { SelectList } from "./select-list.js";
|
|
8
|
-
const
|
|
8
|
+
const graphemeSegmenter = getGraphemeSegmenter();
|
|
9
|
+
const wordSegmenter = getWordSegmenter();
|
|
9
10
|
/** Regex matching paste markers like `[paste #1 +123 lines]` or `[paste #2 1234 chars]`. */
|
|
10
11
|
const PASTE_MARKER_REGEX = /\[paste #(\d+)( (\+\d+ lines|\d+ chars))?\]/g;
|
|
11
12
|
/** Non-global version for single-segment testing. */
|
|
@@ -21,7 +22,7 @@ function isPasteMarker(segment) {
|
|
|
21
22
|
*
|
|
22
23
|
* Only markers whose numeric ID exists in `validIds` are merged.
|
|
23
24
|
*/
|
|
24
|
-
function segmentWithMarkers(text, validIds) {
|
|
25
|
+
function segmentWithMarkers(text, baseSegmenter, validIds) {
|
|
25
26
|
// Fast path: no paste markers in the text or no valid IDs.
|
|
26
27
|
if (validIds.size === 0 || !text.includes("[paste #")) {
|
|
27
28
|
return baseSegmenter.segment(text);
|
|
@@ -86,7 +87,7 @@ export function wordWrapLine(line, maxWidth, preSegmented) {
|
|
|
86
87
|
return [{ text: line, startIndex: 0, endIndex: line.length }];
|
|
87
88
|
}
|
|
88
89
|
const chunks = [];
|
|
89
|
-
const segments = preSegmented ?? [...
|
|
90
|
+
const segments = preSegmented ?? [...graphemeSegmenter.segment(line)];
|
|
90
91
|
let currentWidth = 0;
|
|
91
92
|
let chunkStart = 0;
|
|
92
93
|
// Wrap opportunity: the position after the last whitespace before a non-whitespace
|
|
@@ -225,8 +226,8 @@ export class Editor {
|
|
|
225
226
|
return new Set(this.pastes.keys());
|
|
226
227
|
}
|
|
227
228
|
/** Segment text with paste-marker awareness, only merging markers with valid IDs. */
|
|
228
|
-
segment(text) {
|
|
229
|
-
return segmentWithMarkers(text, this.validPasteIds());
|
|
229
|
+
segment(text, mode) {
|
|
230
|
+
return segmentWithMarkers(text, mode === "word" ? wordSegmenter : graphemeSegmenter, this.validPasteIds());
|
|
230
231
|
}
|
|
231
232
|
getPaddingX() {
|
|
232
233
|
return this.paddingX;
|
|
@@ -381,7 +382,7 @@ export class Editor {
|
|
|
381
382
|
if (after.length > 0) {
|
|
382
383
|
// Cursor is on a character (grapheme) - replace it with highlighted version
|
|
383
384
|
// Get the first grapheme from 'after'
|
|
384
|
-
const afterGraphemes = [...this.segment(after)];
|
|
385
|
+
const afterGraphemes = [...this.segment(after, "grapheme")];
|
|
385
386
|
const firstGrapheme = afterGraphemes[0]?.segment || "";
|
|
386
387
|
const restAfter = after.slice(firstGrapheme.length);
|
|
387
388
|
const cursor = `\x1b[7m${firstGrapheme}\x1b[0m`;
|
|
@@ -717,7 +718,7 @@ export class Editor {
|
|
|
717
718
|
}
|
|
718
719
|
else {
|
|
719
720
|
// Line needs wrapping - use word-aware wrapping
|
|
720
|
-
const chunks = wordWrapLine(line, contentWidth, [...this.segment(line)]);
|
|
721
|
+
const chunks = wordWrapLine(line, contentWidth, [...this.segment(line, "grapheme")]);
|
|
721
722
|
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
|
|
722
723
|
const chunk = chunks[chunkIndex];
|
|
723
724
|
if (!chunk)
|
|
@@ -1030,7 +1031,7 @@ export class Editor {
|
|
|
1030
1031
|
const line = this.state.lines[this.state.cursorLine] || "";
|
|
1031
1032
|
const beforeCursor = line.slice(0, this.state.cursorCol);
|
|
1032
1033
|
// Find the last grapheme in the text before cursor
|
|
1033
|
-
const graphemes = [...this.segment(beforeCursor)];
|
|
1034
|
+
const graphemes = [...this.segment(beforeCursor, "grapheme")];
|
|
1034
1035
|
const lastGrapheme = graphemes[graphemes.length - 1];
|
|
1035
1036
|
const graphemeLength = lastGrapheme ? lastGrapheme.segment.length : 1;
|
|
1036
1037
|
const before = line.slice(0, this.state.cursorCol - graphemeLength);
|
|
@@ -1114,7 +1115,7 @@ export class Editor {
|
|
|
1114
1115
|
// Snap cursor to atomic segment boundary (e.g. paste markers)
|
|
1115
1116
|
// so the cursor never lands in the middle of a multi-grapheme unit.
|
|
1116
1117
|
// Single-grapheme segments don't need snapping.
|
|
1117
|
-
const segments = [...this.segment(logicalLine)];
|
|
1118
|
+
const segments = [...this.segment(logicalLine, "grapheme")];
|
|
1118
1119
|
for (const seg of segments) {
|
|
1119
1120
|
if (seg.index > this.state.cursorCol)
|
|
1120
1121
|
break;
|
|
@@ -1334,7 +1335,7 @@ export class Editor {
|
|
|
1334
1335
|
// Delete grapheme at cursor position (handles emojis, combining characters, etc.)
|
|
1335
1336
|
const afterCursor = currentLine.slice(this.state.cursorCol);
|
|
1336
1337
|
// Find the first grapheme at cursor
|
|
1337
|
-
const graphemes = [...this.segment(afterCursor)];
|
|
1338
|
+
const graphemes = [...this.segment(afterCursor, "grapheme")];
|
|
1338
1339
|
const firstGrapheme = graphemes[0];
|
|
1339
1340
|
const graphemeLength = firstGrapheme ? firstGrapheme.segment.length : 1;
|
|
1340
1341
|
const before = currentLine.slice(0, this.state.cursorCol);
|
|
@@ -1389,7 +1390,7 @@ export class Editor {
|
|
|
1389
1390
|
}
|
|
1390
1391
|
else {
|
|
1391
1392
|
// Line needs wrapping - use word-aware wrapping
|
|
1392
|
-
const chunks = wordWrapLine(line, width, [...this.segment(line)]);
|
|
1393
|
+
const chunks = wordWrapLine(line, width, [...this.segment(line, "grapheme")]);
|
|
1393
1394
|
for (const chunk of chunks) {
|
|
1394
1395
|
visualLines.push({
|
|
1395
1396
|
logicalLine: i,
|
|
@@ -1441,7 +1442,7 @@ export class Editor {
|
|
|
1441
1442
|
// Moving right - move by one grapheme (handles emojis, combining characters, etc.)
|
|
1442
1443
|
if (this.state.cursorCol < currentLine.length) {
|
|
1443
1444
|
const afterCursor = currentLine.slice(this.state.cursorCol);
|
|
1444
|
-
const graphemes = [...this.segment(afterCursor)];
|
|
1445
|
+
const graphemes = [...this.segment(afterCursor, "grapheme")];
|
|
1445
1446
|
const firstGrapheme = graphemes[0];
|
|
1446
1447
|
this.setCursorCol(this.state.cursorCol + (firstGrapheme ? firstGrapheme.segment.length : 1));
|
|
1447
1448
|
}
|
|
@@ -1462,7 +1463,7 @@ export class Editor {
|
|
|
1462
1463
|
// Moving left - move by one grapheme (handles emojis, combining characters, etc.)
|
|
1463
1464
|
if (this.state.cursorCol > 0) {
|
|
1464
1465
|
const beforeCursor = currentLine.slice(0, this.state.cursorCol);
|
|
1465
|
-
const graphemes = [...this.segment(beforeCursor)];
|
|
1466
|
+
const graphemes = [...this.segment(beforeCursor, "grapheme")];
|
|
1466
1467
|
const lastGrapheme = graphemes[graphemes.length - 1];
|
|
1467
1468
|
this.setCursorCol(this.state.cursorCol - (lastGrapheme ? lastGrapheme.segment.length : 1));
|
|
1468
1469
|
}
|
|
@@ -1501,35 +1502,27 @@ export class Editor {
|
|
|
1501
1502
|
return;
|
|
1502
1503
|
}
|
|
1503
1504
|
const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
|
|
1504
|
-
const
|
|
1505
|
+
const segments = [...this.segment(textBeforeCursor, "word")];
|
|
1505
1506
|
let newCol = this.state.cursorCol;
|
|
1506
1507
|
// Skip trailing whitespace
|
|
1507
|
-
while (
|
|
1508
|
-
!isPasteMarker(
|
|
1509
|
-
isWhitespaceChar(
|
|
1510
|
-
newCol -=
|
|
1511
|
-
}
|
|
1512
|
-
if (
|
|
1513
|
-
const
|
|
1514
|
-
if (isPasteMarker(
|
|
1515
|
-
//
|
|
1516
|
-
newCol -=
|
|
1517
|
-
}
|
|
1518
|
-
else if (isPunctuationChar(lastGrapheme)) {
|
|
1519
|
-
// Skip punctuation run
|
|
1520
|
-
while (graphemes.length > 0 &&
|
|
1521
|
-
isPunctuationChar(graphemes[graphemes.length - 1]?.segment || "") &&
|
|
1522
|
-
!isPasteMarker(graphemes[graphemes.length - 1]?.segment || "")) {
|
|
1523
|
-
newCol -= graphemes.pop()?.segment.length || 0;
|
|
1524
|
-
}
|
|
1508
|
+
while (segments.length > 0 &&
|
|
1509
|
+
!isPasteMarker(segments[segments.length - 1]?.segment || "") &&
|
|
1510
|
+
isWhitespaceChar(segments[segments.length - 1]?.segment || "")) {
|
|
1511
|
+
newCol -= segments.pop()?.segment.length || 0;
|
|
1512
|
+
}
|
|
1513
|
+
if (segments.length > 0) {
|
|
1514
|
+
const last = segments[segments.length - 1];
|
|
1515
|
+
if (isPasteMarker(last.segment) || last.isWordLike) {
|
|
1516
|
+
// Skip one word-like segment (or paste marker)
|
|
1517
|
+
newCol -= segments.pop()?.segment.length || 0;
|
|
1525
1518
|
}
|
|
1526
1519
|
else {
|
|
1527
|
-
// Skip word run
|
|
1528
|
-
while (
|
|
1529
|
-
!
|
|
1530
|
-
!
|
|
1531
|
-
!
|
|
1532
|
-
newCol -=
|
|
1520
|
+
// Skip non-word non-whitespace run (punctuation)
|
|
1521
|
+
while (segments.length > 0 &&
|
|
1522
|
+
!isPasteMarker(segments[segments.length - 1]?.segment || "") &&
|
|
1523
|
+
!segments[segments.length - 1]?.isWordLike &&
|
|
1524
|
+
!isWhitespaceChar(segments[segments.length - 1]?.segment || "")) {
|
|
1525
|
+
newCol -= segments.pop()?.segment.length || 0;
|
|
1533
1526
|
}
|
|
1534
1527
|
}
|
|
1535
1528
|
}
|
|
@@ -1691,7 +1684,7 @@ export class Editor {
|
|
|
1691
1684
|
return;
|
|
1692
1685
|
}
|
|
1693
1686
|
const textAfterCursor = currentLine.slice(this.state.cursorCol);
|
|
1694
|
-
const segments = this.segment(textAfterCursor);
|
|
1687
|
+
const segments = this.segment(textAfterCursor, "word");
|
|
1695
1688
|
const iterator = segments[Symbol.iterator]();
|
|
1696
1689
|
let next = iterator.next();
|
|
1697
1690
|
let newCol = this.state.cursorCol;
|
|
@@ -1701,24 +1694,16 @@ export class Editor {
|
|
|
1701
1694
|
next = iterator.next();
|
|
1702
1695
|
}
|
|
1703
1696
|
if (!next.done) {
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
newCol += firstGrapheme.length;
|
|
1708
|
-
}
|
|
1709
|
-
else if (isPunctuationChar(firstGrapheme)) {
|
|
1710
|
-
// Skip punctuation run
|
|
1711
|
-
while (!next.done && isPunctuationChar(next.value.segment) && !isPasteMarker(next.value.segment)) {
|
|
1712
|
-
newCol += next.value.segment.length;
|
|
1713
|
-
next = iterator.next();
|
|
1714
|
-
}
|
|
1697
|
+
if (isPasteMarker(next.value.segment) || next.value.isWordLike) {
|
|
1698
|
+
// Skip one word-like segment (or paste marker)
|
|
1699
|
+
newCol += next.value.segment.length;
|
|
1715
1700
|
}
|
|
1716
1701
|
else {
|
|
1717
|
-
// Skip word run
|
|
1702
|
+
// Skip non-word non-whitespace run (punctuation)
|
|
1718
1703
|
while (!next.done &&
|
|
1719
|
-
!
|
|
1720
|
-
!
|
|
1721
|
-
!
|
|
1704
|
+
!isPasteMarker(next.value.segment) &&
|
|
1705
|
+
!next.value.isWordLike &&
|
|
1706
|
+
!isWhitespaceChar(next.value.segment)) {
|
|
1722
1707
|
newCol += next.value.segment.length;
|
|
1723
1708
|
next = iterator.next();
|
|
1724
1709
|
}
|
|
@@ -3,8 +3,8 @@ import { decodeKittyPrintable } from "../keys.js";
|
|
|
3
3
|
import { KillRing } from "../kill-ring.js";
|
|
4
4
|
import { CURSOR_MARKER } from "../tui.js";
|
|
5
5
|
import { UndoStack } from "../undo-stack.js";
|
|
6
|
-
import {
|
|
7
|
-
const segmenter =
|
|
6
|
+
import { getGraphemeSegmenter, isPunctuationChar, isWhitespaceChar, sliceByColumn, visibleWidth } from "../utils.js";
|
|
7
|
+
const segmenter = getGraphemeSegmenter();
|
|
8
8
|
/**
|
|
9
9
|
* Input component - single-line text input with horizontal scrolling
|
|
10
10
|
*/
|