Skip to content

Commit f0305ad

Browse files
authored
Merge pull request #162 from lambdalisue/fix-bom
Fix BOM handling in GinEdit
2 parents 7c4b980 + 6bea403 commit f0305ad

File tree

8 files changed

+86
-29
lines changed

8 files changed

+86
-29
lines changed

.github/workflows/test.yml

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ on:
1313
workflow_dispatch:
1414
inputs:
1515
denops_branch:
16-
description: "Denops revision to test"
16+
description: "Denops branch to test"
1717
required: false
1818
default: "main"
1919

20+
# Use 'bash' as default shell even on Windows
2021
defaults:
2122
run:
2223
shell: bash --noprofile --norc -eo pipefail {0}
@@ -31,13 +32,13 @@ jobs:
3132
runner:
3233
- ubuntu-latest
3334
deno_version:
34-
- "1.x"
35+
- "2.x"
3536
runs-on: ${{ matrix.runner }}
3637
steps:
3738
- run: git config --global core.autocrlf false
3839
if: runner.os == 'Windows'
3940
- uses: actions/checkout@v4
40-
- uses: denoland/setup-deno@v1.1.4
41+
- uses: denoland/setup-deno@v2
4142
with:
4243
deno-version: "${{ matrix.deno_version }}"
4344
- uses: actions/cache@v4
@@ -61,27 +62,22 @@ jobs:
6162
- macos-latest
6263
- ubuntu-latest
6364
deno_version:
64-
- "1.45.0"
65-
- "1.x"
65+
- "2.x"
6666
host_version:
6767
- vim: "v9.1.0448"
6868
nvim: "v0.10.0"
69+
6970
runs-on: ${{ matrix.runner }}
70-
timeout-minutes: 15
71+
7172
steps:
7273
- run: git config --global core.autocrlf false
7374
if: runner.os == 'Windows'
7475

75-
- run: |
76-
git config --global user.email "[email protected]"
77-
git config --global user.name "GitHub Action"
78-
git version
79-
8076
- uses: actions/checkout@v4
8177

82-
- uses: denoland/setup-deno@v1.1.4
78+
- uses: denoland/setup-deno@v1
8379
with:
84-
deno-version: "${{ matrix.deno_version }}"
80+
deno-version: ${{ matrix.deno_version }}
8581

8682
- name: Get denops
8783
run: |
@@ -96,13 +92,13 @@ jobs:
9692
- uses: rhysd/action-setup-vim@v1
9793
id: vim
9894
with:
99-
version: "${{ matrix.host_version.vim }}"
95+
version: ${{ matrix.host_version.vim }}
10096

10197
- uses: rhysd/action-setup-vim@v1
10298
id: nvim
10399
with:
104100
neovim: true
105-
version: "${{ matrix.host_version.nvim }}"
101+
version: ${{ matrix.host_version.nvim }}
106102

107103
- name: Export executables
108104
run: |
@@ -117,7 +113,8 @@ jobs:
117113
118114
- name: Perform pre-cache
119115
run: |
120-
deno cache ${DENOPS_TEST_DENOPS_PATH}/denops/@denops-private/mod.ts ./denops/gin/main.ts
116+
deno cache --config ${DENOPS_TEST_DENOPS_PATH}/denops/@denops-private/deno.jsonc ${DENOPS_TEST_DENOPS_PATH}/denops/@denops-private/mod.ts
117+
deno cache ./denops/gin/main.ts
121118
122119
- name: Test
123120
run: deno task test:coverage

deno.jsonc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,12 @@
1212
"update": "deno run --allow-env --allow-read --allow-write --allow-run=git,deno --allow-net=jsr.io,registry.npmjs.org jsr:@molt/cli **/*.ts",
1313
"update:write": "deno task -q update --write",
1414
"update:commit": "deno task -q update --commit --prefix :package: --pre-commit=fmt,lint"
15+
},
16+
"lint": {
17+
"rules": {
18+
"exclude": [
19+
"no-import-prefix"
20+
]
21+
}
1522
}
1623
}

denops/gin/command/edit/edit.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import * as vars from "jsr:@denops/std@^7.0.0/variable";
99
import { parseOpts, validateOpts } from "jsr:@denops/std@^7.0.0/argument";
1010
import { parse as parseBufname } from "jsr:@denops/std@^7.0.0/bufname";
1111
import { execute } from "../../git/executor.ts";
12+
import { hasBom } from "../../util/bom.ts";
1213
import { formatTreeish } from "./util.ts";
1314

1415
export async function edit(
@@ -73,6 +74,7 @@ export async function exec(
7374
await denops.cmd("filetype detect");
7475
await option.swapfile.setLocal(denops, false);
7576
await option.bufhidden.setLocal(denops, "unload");
77+
await option.bomb.setLocal(denops, hasBom(stdout));
7678
if (options.commitish) {
7779
await option.buftype.setLocal(denops, "nowrite");
7880
await option.modifiable.setLocal(denops, false);

denops/gin/command/edit/write.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import * as batch from "jsr:@denops/std@^7.0.0/batch";
55
import * as fn from "jsr:@denops/std@^7.0.0/function";
66
import * as option from "jsr:@denops/std@^7.0.0/option";
77
import { parse as parseBufname } from "jsr:@denops/std@^7.0.0/bufname";
8+
import Encoding from "npm:[email protected]";
89
import { findWorktreeFromDenops } from "../../git/worktree.ts";
10+
import { addBom } from "../../util/bom.ts";
911
import { exec as execBare } from "../../command/bare/command.ts";
1012

1113
export async function write(
@@ -32,15 +34,17 @@ export async function exec(
3234
relpath: string,
3335
options: ExecOptions,
3436
): Promise<void> {
35-
const [verbose, fileencoding, fileformat, content] = await batch.collect(
36-
denops,
37-
(denops) => [
38-
option.verbose.get(denops),
39-
option.fileencoding.getBuffer(denops, bufnr),
40-
option.fileformat.getBuffer(denops, bufnr),
41-
fn.getbufline(denops, bufnr, 1, "$"),
42-
],
43-
);
37+
const [verbose, fileencoding, fileformat, bomb, content] = await batch
38+
.collect(
39+
denops,
40+
(denops) => [
41+
option.verbose.get(denops),
42+
option.fileencoding.getBuffer(denops, bufnr),
43+
option.fileformat.getBuffer(denops, bufnr),
44+
option.bomb.getBuffer(denops, bufnr),
45+
fn.getbufline(denops, bufnr, 1, "$"),
46+
],
47+
);
4448

4549
const worktree = await findWorktreeFromDenops(denops, {
4650
worktree: options.worktree,
@@ -64,7 +68,8 @@ export async function exec(
6468
}
6569
try {
6670
await fs.copy(f, original);
67-
await Deno.writeTextFile(original, `${content.join("\n")}\n`);
71+
const data = encode(`${content.join("\n")}\n`, fileencoding);
72+
await Deno.writeFile(original, bomb ? addBom(data) : data);
6873
await fn.setbufvar(denops, bufnr, "&modified", 0);
6974
await execBare(denops, [
7075
"add",
@@ -80,3 +85,12 @@ export async function exec(
8085
await restore();
8186
}
8287
}
88+
89+
function encode(str: string, encoding: string): Uint8Array {
90+
const utf8Encoder = new TextEncoder();
91+
const utf8Bytes = utf8Encoder.encode(str);
92+
return Uint8Array.from(Encoding.convert(utf8Bytes, {
93+
to: encoding,
94+
from: "UTF8",
95+
}));
96+
}

denops/gin/git/executor.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,10 @@ async function postProcessor(
124124
env,
125125
});
126126
const proc = command.spawn();
127-
const reader = new Blob([stdout]);
127+
// Deno's Type definition doesn't allow ArrayBufferLike so we need to copy
128+
// stdout (Uint8Array<ArrayBufferLike>) to safeStdout (Uint8Array<ArrayBuffer>)
129+
const safeStdout = new Uint8Array(stdout);
130+
const reader = new Blob([safeStdout.buffer]);
128131
const [_, result] = await Promise.all([
129132
reader.stream().pipeTo(proc.stdin),
130133
proc.output(),

denops/gin/git/finder_test.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,15 @@ Deno.test({
141141
async function prepare(): ReturnType<typeof sandbox> {
142142
const sbox = await sandbox();
143143
await $`git init`;
144+
// Configure git user for tests to avoid commit failures
145+
await $`git config user.email "[email protected]"`;
146+
await $`git config user.name "Test User"`;
144147
await $`git commit --allow-empty -m 'Initial commit' --no-gpg-sign`;
145-
await $`git switch -c main`;
148+
// Check if we're already on main branch, if not switch to it
149+
const currentBranch = await $`git branch --show-current`.text();
150+
if (currentBranch.trim() !== "main") {
151+
await $`git switch -c main`;
152+
}
146153
await Deno.mkdir(join("a", "b", "c"), { recursive: true });
147154
return sbox;
148155
}

denops/gin/util/bom.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export const BOM = Uint8Array.from([0xef, 0xbb, 0xbf]);
2+
3+
export function addBom(data: Uint8Array): Uint8Array {
4+
if (hasBom(data)) {
5+
return data;
6+
}
7+
const withBom = new Uint8Array(data.length + BOM.length);
8+
withBom.set(BOM, 0);
9+
withBom.set(data, BOM.length);
10+
return withBom;
11+
}
12+
13+
export function removeBom(data: Uint8Array): Uint8Array {
14+
if (!hasBom(data)) {
15+
return data;
16+
}
17+
return data.subarray(BOM.length);
18+
}
19+
20+
export function hasBom(data: Uint8Array): boolean {
21+
return (
22+
data.length >= BOM.length &&
23+
data[0] === BOM[0] &&
24+
data[1] === BOM[1] &&
25+
data[2] === BOM[2]
26+
);
27+
}

denops/gin/util/text.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export function encodeUtf8(input: string): Uint8Array {
1111
/**
1212
* Decode an UTF8 Uint8Array into a string
1313
*/
14-
export function decodeUtf8(input: BufferSource): string {
14+
export function decodeUtf8(input: AllowSharedBufferSource): string {
1515
return textDecoder.decode(input);
1616
}
1717

0 commit comments

Comments
 (0)