@akiojin/gwt 9.6.0 → 9.8.0
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/.husky/pre-push +3 -1
- package/README.ja.md +40 -6
- package/README.md +41 -8
- package/assets/icons/icon.icns +0 -0
- package/assets/icons/icon.ico +0 -0
- package/assets/icons/icon.png +0 -0
- package/assets/release-assets.json +18 -0
- package/bin/gwt.cjs +7 -4
- package/docker-compose.yml +2 -2
- package/package.json +6 -3
- package/scripts/check-coverage-threshold.mjs +65 -0
- package/scripts/check-release-flow.sh +17 -0
- package/scripts/postinstall.cjs +4 -5
- package/scripts/release-assets.cjs +39 -15
- package/scripts/test-all.sh +15 -1
- package/scripts/test_release_assets.cjs +102 -9
- package/scripts/verify-husky-hooks.sh +3 -3
- package/vendor/vt100/LICENSE +21 -0
- package/vendor/vt100/src/attrs.rs +144 -0
- package/vendor/vt100/src/callbacks.rs +69 -0
- package/vendor/vt100/src/cell.rs +179 -0
- package/vendor/vt100/src/grid.rs +742 -0
- package/vendor/vt100/src/lib.rs +64 -0
- package/vendor/vt100/src/parser.rs +96 -0
- package/vendor/vt100/src/perform.rs +277 -0
- package/vendor/vt100/src/row.rs +488 -0
- package/vendor/vt100/src/screen.rs +1354 -0
- package/vendor/vt100/src/term.rs +551 -0
- package/wix/main.wxs +42 -4
|
@@ -6,7 +6,9 @@ const { execFileSync } = require("child_process");
|
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
8
|
binaryNameForPlatform,
|
|
9
|
-
|
|
9
|
+
bundleBinaryNamesForPlatform,
|
|
10
|
+
installBundleFromArchive,
|
|
11
|
+
primaryReleaseAssetName,
|
|
10
12
|
releaseAssetName,
|
|
11
13
|
} = require("./release-assets.cjs");
|
|
12
14
|
const postinstall = require("./postinstall.cjs");
|
|
@@ -33,63 +35,154 @@ run("release asset names match the public portable contract", () => {
|
|
|
33
35
|
assert.equal(releaseAssetName("win32", "x64"), "gwt-windows-x86_64.zip");
|
|
34
36
|
});
|
|
35
37
|
|
|
38
|
+
run("primary release asset names match the GUI-first install contract", () => {
|
|
39
|
+
assert.equal(primaryReleaseAssetName("darwin", "arm64"), "gwt-macos-universal.dmg");
|
|
40
|
+
assert.equal(primaryReleaseAssetName("darwin", "x64"), "gwt-macos-universal.dmg");
|
|
41
|
+
assert.equal(primaryReleaseAssetName("linux", "arm64"), "gwt-linux-aarch64.tar.gz");
|
|
42
|
+
assert.equal(primaryReleaseAssetName("linux", "x64"), "gwt-linux-x86_64.tar.gz");
|
|
43
|
+
assert.equal(primaryReleaseAssetName("win32", "x64"), "gwt-windows-x86_64.msi");
|
|
44
|
+
});
|
|
45
|
+
|
|
36
46
|
run("release helper keeps platform binary names stable", () => {
|
|
37
47
|
assert.equal(binaryNameForPlatform("win32"), "gwt.exe");
|
|
38
48
|
assert.equal(binaryNameForPlatform("linux"), "gwt");
|
|
39
49
|
assert.equal(binaryNameForPlatform("darwin"), "gwt");
|
|
40
50
|
});
|
|
41
51
|
|
|
52
|
+
run("release helper keeps bundle binary names stable", () => {
|
|
53
|
+
assert.deepEqual(bundleBinaryNamesForPlatform("win32"), ["gwt.exe", "gwtd.exe"]);
|
|
54
|
+
assert.deepEqual(bundleBinaryNamesForPlatform("linux"), ["gwt", "gwtd"]);
|
|
55
|
+
assert.deepEqual(bundleBinaryNamesForPlatform("darwin"), ["gwt", "gwtd"]);
|
|
56
|
+
});
|
|
57
|
+
|
|
42
58
|
run("installer entrypoints are loadable under package type module", () => {
|
|
43
59
|
assert.equal(typeof postinstall.main, "function");
|
|
44
60
|
assert.equal(typeof launcher.main, "function");
|
|
45
61
|
});
|
|
46
62
|
|
|
47
|
-
run("
|
|
63
|
+
run("windows installer definition includes the gwtd companion binary", () => {
|
|
64
|
+
const wix = fs.readFileSync(path.join(__dirname, "..", "wix", "main.wxs"), "utf8");
|
|
65
|
+
assert.match(wix, /gwtd\.exe/);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
run("release workflow packages gwtd alongside gwt", () => {
|
|
69
|
+
const workflow = fs.readFileSync(
|
|
70
|
+
path.join(__dirname, "..", ".github", "workflows", "release.yml"),
|
|
71
|
+
"utf8"
|
|
72
|
+
);
|
|
73
|
+
assert.match(workflow, /--bin gwt --bin gwtd/);
|
|
74
|
+
assert.match(workflow, /Compress-Archive -Path @\("dist\/gwt\.exe", "dist\/gwtd\.exe"\)/);
|
|
75
|
+
assert.match(workflow, /tar -czf \$\{\{ matrix\.archive_name \}\} gwt gwtd/);
|
|
76
|
+
assert.match(workflow, /Contents\/MacOS\/gwtd/);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
run("package scripts keep the GUI front door and release contract explicit", () => {
|
|
80
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
|
|
81
|
+
assert.equal(pkg.scripts["test:release-assets"], "node scripts/test_release_assets.cjs");
|
|
82
|
+
assert.equal(
|
|
83
|
+
pkg.scripts["test:frontend-bundle"],
|
|
84
|
+
"node --check crates/gwt/web/app.js"
|
|
85
|
+
);
|
|
86
|
+
assert.equal(pkg.scripts["test:release-flow"], "bash scripts/check-release-flow.sh");
|
|
87
|
+
assert.equal(pkg.scripts.dev, "cargo run -p gwt --bin gwt");
|
|
88
|
+
assert.equal(pkg.scripts.build, "cargo build --release -p gwt --bin gwt --bin gwtd");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
run("test all script keeps rust tests plus frontend release checks", () => {
|
|
92
|
+
const testAll = fs.readFileSync(path.join(__dirname, "test-all.sh"), "utf8");
|
|
93
|
+
assert.match(testAll, /test -p gwt-core -p gwt --all-features/);
|
|
94
|
+
assert.match(testAll, /cargo\.exe/);
|
|
95
|
+
assert.match(testAll, /bash scripts\/check-release-flow\.sh/);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
run("release flow helper checks the shared frontend bundle and release assets", () => {
|
|
99
|
+
const releaseFlow = fs.readFileSync(path.join(__dirname, "check-release-flow.sh"), "utf8");
|
|
100
|
+
assert.match(releaseFlow, /scripts\/test_release_assets\.cjs/);
|
|
101
|
+
assert.match(releaseFlow, /node --check \"\$APP_JS\"/);
|
|
102
|
+
assert.match(releaseFlow, /script type=\"module\" src=\"\/app\.js\"/);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
run("CI workflows call the named frontend and release verification scripts", () => {
|
|
106
|
+
const lintWorkflow = fs.readFileSync(
|
|
107
|
+
path.join(__dirname, "..", ".github", "workflows", "lint.yml"),
|
|
108
|
+
"utf8"
|
|
109
|
+
);
|
|
110
|
+
const testWorkflow = fs.readFileSync(
|
|
111
|
+
path.join(__dirname, "..", ".github", "workflows", "test.yml"),
|
|
112
|
+
"utf8"
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
assert.match(lintWorkflow, /test:frontend-bundle/);
|
|
116
|
+
assert.match(lintWorkflow, /test:release-flow/);
|
|
117
|
+
assert.match(testWorkflow, /test:release-assets/);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
run("README install guidance points to GUI-first release assets", () => {
|
|
121
|
+
const readme = fs.readFileSync(path.join(__dirname, "..", "README.md"), "utf8");
|
|
122
|
+
const readmeJa = fs.readFileSync(path.join(__dirname, "..", "README.ja.md"), "utf8");
|
|
123
|
+
|
|
124
|
+
for (const doc of [readme, readmeJa]) {
|
|
125
|
+
assert.match(doc, /gwt-macos-universal\.dmg/);
|
|
126
|
+
assert.match(doc, /gwt-windows-x86_64\.msi/);
|
|
127
|
+
assert.match(doc, /gwt-linux-x86_64\.tar\.gz/);
|
|
128
|
+
assert.match(doc, /test:frontend-bundle|node --check crates\/gwt\/web\/app\.js/);
|
|
129
|
+
assert.match(doc, /test:release-flow|bash scripts\/check-release-flow\.sh/);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
run("portable tarball extraction installs the unix bundle", () => {
|
|
48
134
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "gwt-release-test-"));
|
|
49
135
|
const sourceDir = path.join(root, "source");
|
|
50
136
|
const binDir = path.join(root, "bin");
|
|
51
137
|
const archivePath = path.join(root, "gwt-linux-x86_64.tar.gz");
|
|
52
138
|
fs.mkdirSync(sourceDir, { recursive: true });
|
|
53
139
|
fs.writeFileSync(path.join(sourceDir, "gwt"), "unix-binary");
|
|
140
|
+
fs.writeFileSync(path.join(sourceDir, "gwtd"), "unix-daemon");
|
|
54
141
|
|
|
55
|
-
execFileSync("tar", ["-czf", archivePath, "-C", sourceDir, "gwt"]);
|
|
142
|
+
execFileSync("tar", ["-czf", archivePath, "-C", sourceDir, "gwt", "gwtd"]);
|
|
56
143
|
|
|
57
|
-
|
|
144
|
+
installBundleFromArchive({
|
|
58
145
|
archivePath,
|
|
59
146
|
asset: path.basename(archivePath),
|
|
60
147
|
binDir,
|
|
61
|
-
binaryName: "gwt",
|
|
62
148
|
platform: "linux",
|
|
63
149
|
});
|
|
64
150
|
|
|
65
151
|
assert.equal(fs.readFileSync(path.join(binDir, "gwt"), "utf8"), "unix-binary");
|
|
152
|
+
assert.equal(fs.readFileSync(path.join(binDir, "gwtd"), "utf8"), "unix-daemon");
|
|
66
153
|
});
|
|
67
154
|
|
|
68
|
-
run("portable zip extraction installs the windows
|
|
155
|
+
run("portable zip extraction installs the windows bundle", () => {
|
|
156
|
+
if (process.platform !== "win32") {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
69
160
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "gwt-release-test-"));
|
|
70
161
|
const sourceDir = path.join(root, "source");
|
|
71
162
|
const binDir = path.join(root, "bin");
|
|
72
163
|
const archivePath = path.join(root, "gwt-windows-x86_64.zip");
|
|
73
164
|
const sourceBinary = path.join(sourceDir, "gwt.exe");
|
|
165
|
+
const sourceDaemon = path.join(sourceDir, "gwtd.exe");
|
|
74
166
|
fs.mkdirSync(sourceDir, { recursive: true });
|
|
75
167
|
fs.writeFileSync(sourceBinary, "windows-binary");
|
|
168
|
+
fs.writeFileSync(sourceDaemon, "windows-daemon");
|
|
76
169
|
|
|
77
170
|
execFileSync("powershell.exe", [
|
|
78
171
|
"-NoProfile",
|
|
79
172
|
"-NonInteractive",
|
|
80
173
|
"-Command",
|
|
81
|
-
`Compress-Archive -LiteralPath '${sourceBinary.replace(/'/g, "''")}' -DestinationPath '${archivePath.replace(/'/g, "''")}' -Force`,
|
|
174
|
+
`Compress-Archive -LiteralPath @('${sourceBinary.replace(/'/g, "''")}','${sourceDaemon.replace(/'/g, "''")}') -DestinationPath '${archivePath.replace(/'/g, "''")}' -Force`,
|
|
82
175
|
]);
|
|
83
176
|
|
|
84
|
-
|
|
177
|
+
installBundleFromArchive({
|
|
85
178
|
archivePath,
|
|
86
179
|
asset: path.basename(archivePath),
|
|
87
180
|
binDir,
|
|
88
|
-
binaryName: "gwt.exe",
|
|
89
181
|
platform: "win32",
|
|
90
182
|
});
|
|
91
183
|
|
|
92
184
|
assert.equal(fs.readFileSync(path.join(binDir, "gwt.exe"), "utf8"), "windows-binary");
|
|
185
|
+
assert.equal(fs.readFileSync(path.join(binDir, "gwtd.exe"), "utf8"), "windows-daemon");
|
|
93
186
|
});
|
|
94
187
|
|
|
95
188
|
if (failed) {
|
|
@@ -43,7 +43,9 @@ require_contains "$PACKAGE_JSON" "\"lint:husky\": \"bash scripts/verify-husky-ho
|
|
|
43
43
|
require_file "$PRE_PUSH"
|
|
44
44
|
require_contains "$PRE_PUSH" "cargo clippy --all-targets --all-features -- -D warnings"
|
|
45
45
|
require_contains "$PRE_PUSH" "cargo fmt --all -- --check"
|
|
46
|
-
require_contains "$PRE_PUSH" "
|
|
46
|
+
require_contains "$PRE_PUSH" "cargo llvm-cov -p gwt-core -p gwt --all-features --json --summary-only --output-path target/coverage-summary.json"
|
|
47
|
+
require_contains "$PRE_PUSH" "node scripts/check-coverage-threshold.mjs target/coverage-summary.json 90"
|
|
48
|
+
require_contains "$PRE_PUSH" "bunx --bun markdownlint-cli . --config .markdownlint.json --ignore target --ignore CHANGELOG.md --ignore tasks/todo.md"
|
|
47
49
|
require_contains "$PRE_PUSH" "pnpm lint:skills"
|
|
48
50
|
|
|
49
51
|
require_file "$COMMIT_MSG"
|
|
@@ -52,8 +54,6 @@ require_contains "$COMMIT_MSG" 'bunx --package @commitlint/cli commitlint --edit
|
|
|
52
54
|
if [ -f "$PRE_COMMIT" ]; then
|
|
53
55
|
require_contains "$PRE_COMMIT" "pnpm lint:skills"
|
|
54
56
|
require_contains "$PRE_COMMIT" "bash scripts/run-local-backend-tests-on-commit.sh"
|
|
55
|
-
require_contains "$PRE_COMMIT" "bash scripts/run-local-e2e-on-commit.sh"
|
|
56
|
-
require_contains "$PRE_COMMIT" "bash scripts/run-local-e2e-coverage-on-commit.sh"
|
|
57
57
|
require_not_contains "$PRE_COMMIT" "cargo clippy --all-targets --all-features -- -D warnings"
|
|
58
58
|
require_not_contains "$PRE_COMMIT" "cargo fmt --all -- --check"
|
|
59
59
|
require_not_contains "$PRE_COMMIT" "markdownlint-cli"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016 Jesse Luehrs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
9
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
10
|
+
so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
use crate::term::BufWrite as _;
|
|
2
|
+
|
|
3
|
+
/// Represents a foreground or background color for cells.
|
|
4
|
+
#[derive(Eq, PartialEq, Debug, Copy, Clone, Default)]
|
|
5
|
+
pub enum Color {
|
|
6
|
+
/// The default terminal color.
|
|
7
|
+
#[default]
|
|
8
|
+
Default,
|
|
9
|
+
|
|
10
|
+
/// An indexed terminal color.
|
|
11
|
+
Idx(u8),
|
|
12
|
+
|
|
13
|
+
/// An RGB terminal color. The parameters are (red, green, blue).
|
|
14
|
+
Rgb(u8, u8, u8),
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const TEXT_MODE_INTENSITY: u8 = 0b0000_0011;
|
|
18
|
+
const TEXT_MODE_BOLD: u8 = 0b0000_0001;
|
|
19
|
+
const TEXT_MODE_DIM: u8 = 0b0000_0010;
|
|
20
|
+
const TEXT_MODE_ITALIC: u8 = 0b0000_0100;
|
|
21
|
+
const TEXT_MODE_UNDERLINE: u8 = 0b0000_1000;
|
|
22
|
+
const TEXT_MODE_INVERSE: u8 = 0b0001_0000;
|
|
23
|
+
|
|
24
|
+
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
|
|
25
|
+
pub struct Attrs {
|
|
26
|
+
pub fgcolor: Color,
|
|
27
|
+
pub bgcolor: Color,
|
|
28
|
+
pub mode: u8,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
impl Attrs {
|
|
32
|
+
pub fn bold(&self) -> bool {
|
|
33
|
+
self.mode & TEXT_MODE_BOLD != 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub fn dim(&self) -> bool {
|
|
37
|
+
self.mode & TEXT_MODE_DIM != 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fn intensity(&self) -> u8 {
|
|
41
|
+
self.mode & TEXT_MODE_INTENSITY
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pub fn set_bold(&mut self) {
|
|
45
|
+
self.mode &= !TEXT_MODE_INTENSITY;
|
|
46
|
+
self.mode |= TEXT_MODE_BOLD;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub fn set_dim(&mut self) {
|
|
50
|
+
self.mode &= !TEXT_MODE_INTENSITY;
|
|
51
|
+
self.mode |= TEXT_MODE_DIM;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
pub fn set_normal_intensity(&mut self) {
|
|
55
|
+
self.mode &= !TEXT_MODE_INTENSITY;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
pub fn italic(&self) -> bool {
|
|
59
|
+
self.mode & TEXT_MODE_ITALIC != 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
pub fn set_italic(&mut self, italic: bool) {
|
|
63
|
+
if italic {
|
|
64
|
+
self.mode |= TEXT_MODE_ITALIC;
|
|
65
|
+
} else {
|
|
66
|
+
self.mode &= !TEXT_MODE_ITALIC;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fn underline(&self) -> bool {
|
|
71
|
+
self.mode & TEXT_MODE_UNDERLINE != 0
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
pub fn set_underline(&mut self, underline: bool) {
|
|
75
|
+
if underline {
|
|
76
|
+
self.mode |= TEXT_MODE_UNDERLINE;
|
|
77
|
+
} else {
|
|
78
|
+
self.mode &= !TEXT_MODE_UNDERLINE;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
pub fn inverse(&self) -> bool {
|
|
83
|
+
self.mode & TEXT_MODE_INVERSE != 0
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
pub fn set_inverse(&mut self, inverse: bool) {
|
|
87
|
+
if inverse {
|
|
88
|
+
self.mode |= TEXT_MODE_INVERSE;
|
|
89
|
+
} else {
|
|
90
|
+
self.mode &= !TEXT_MODE_INVERSE;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
pub fn write_escape_code_diff(
|
|
95
|
+
&self,
|
|
96
|
+
contents: &mut Vec<u8>,
|
|
97
|
+
other: &Self,
|
|
98
|
+
) {
|
|
99
|
+
if self != other && self == &Self::default() {
|
|
100
|
+
crate::term::ClearAttrs.write_buf(contents);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let attrs = crate::term::Attrs::default();
|
|
105
|
+
|
|
106
|
+
let attrs = if self.fgcolor == other.fgcolor {
|
|
107
|
+
attrs
|
|
108
|
+
} else {
|
|
109
|
+
attrs.fgcolor(self.fgcolor)
|
|
110
|
+
};
|
|
111
|
+
let attrs = if self.bgcolor == other.bgcolor {
|
|
112
|
+
attrs
|
|
113
|
+
} else {
|
|
114
|
+
attrs.bgcolor(self.bgcolor)
|
|
115
|
+
};
|
|
116
|
+
let attrs = if self.intensity() == other.intensity() {
|
|
117
|
+
attrs
|
|
118
|
+
} else {
|
|
119
|
+
attrs.intensity(match self.intensity() {
|
|
120
|
+
0 => crate::term::Intensity::Normal,
|
|
121
|
+
TEXT_MODE_BOLD => crate::term::Intensity::Bold,
|
|
122
|
+
TEXT_MODE_DIM => crate::term::Intensity::Dim,
|
|
123
|
+
_ => unreachable!(),
|
|
124
|
+
})
|
|
125
|
+
};
|
|
126
|
+
let attrs = if self.italic() == other.italic() {
|
|
127
|
+
attrs
|
|
128
|
+
} else {
|
|
129
|
+
attrs.italic(self.italic())
|
|
130
|
+
};
|
|
131
|
+
let attrs = if self.underline() == other.underline() {
|
|
132
|
+
attrs
|
|
133
|
+
} else {
|
|
134
|
+
attrs.underline(self.underline())
|
|
135
|
+
};
|
|
136
|
+
let attrs = if self.inverse() == other.inverse() {
|
|
137
|
+
attrs
|
|
138
|
+
} else {
|
|
139
|
+
attrs.inverse(self.inverse())
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
attrs.write_buf(contents);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/// This trait is used by the parser to handle extra escape sequences that
|
|
2
|
+
/// don't have an impact on the terminal screen directly.
|
|
3
|
+
pub trait Callbacks {
|
|
4
|
+
/// This callback is called when the terminal requests an audible bell
|
|
5
|
+
/// (typically with `^G`).
|
|
6
|
+
fn audible_bell(&mut self, _: &mut crate::Screen) {}
|
|
7
|
+
/// This callback is called when the terminal requests a visual bell
|
|
8
|
+
/// (typically with `\eg`).
|
|
9
|
+
fn visual_bell(&mut self, _: &mut crate::Screen) {}
|
|
10
|
+
/// This callback is called when the terminal requests a resize
|
|
11
|
+
/// (typically with `\e[8;<rows>;<cols>t`).
|
|
12
|
+
fn resize(&mut self, _: &mut crate::Screen, _request: (u16, u16)) {}
|
|
13
|
+
/// This callback is called when the terminal requests the window title
|
|
14
|
+
/// to be set (typically with `\e]1;<icon_name>\a`)
|
|
15
|
+
fn set_window_icon_name(
|
|
16
|
+
&mut self,
|
|
17
|
+
_: &mut crate::Screen,
|
|
18
|
+
_icon_name: &[u8],
|
|
19
|
+
) {
|
|
20
|
+
}
|
|
21
|
+
/// This callback is called when the terminal requests the window title
|
|
22
|
+
/// to be set (typically with `\e]2;<title>\a`)
|
|
23
|
+
fn set_window_title(&mut self, _: &mut crate::Screen, _title: &[u8]) {}
|
|
24
|
+
/// This callback is called when the terminal requests data to be copied
|
|
25
|
+
/// to the system clipboard (typically with `\e]52;<ty>;<data>\a`). Note
|
|
26
|
+
/// that `data` will be encoded as base64.
|
|
27
|
+
fn copy_to_clipboard(
|
|
28
|
+
&mut self,
|
|
29
|
+
_: &mut crate::Screen,
|
|
30
|
+
_ty: &[u8],
|
|
31
|
+
_data: &[u8],
|
|
32
|
+
) {
|
|
33
|
+
}
|
|
34
|
+
/// This callback is called when the terminal requests data to be pasted
|
|
35
|
+
/// from the system clipboard (typically with `\e]52;<ty>;?\a`).
|
|
36
|
+
fn paste_from_clipboard(&mut self, _: &mut crate::Screen, _ty: &[u8]) {}
|
|
37
|
+
/// This callback is called when the terminal receives an escape sequence
|
|
38
|
+
/// which is otherwise not implemented.
|
|
39
|
+
fn unhandled_char(&mut self, _: &mut crate::Screen, _c: char) {}
|
|
40
|
+
/// This callback is called when the terminal receives a control
|
|
41
|
+
/// character which is otherwise not implemented.
|
|
42
|
+
fn unhandled_control(&mut self, _: &mut crate::Screen, _b: u8) {}
|
|
43
|
+
/// This callback is called when the terminal receives an escape sequence
|
|
44
|
+
/// which is otherwise not implemented.
|
|
45
|
+
fn unhandled_escape(
|
|
46
|
+
&mut self,
|
|
47
|
+
_: &mut crate::Screen,
|
|
48
|
+
_i1: Option<u8>,
|
|
49
|
+
_i2: Option<u8>,
|
|
50
|
+
_b: u8,
|
|
51
|
+
) {
|
|
52
|
+
}
|
|
53
|
+
/// This callback is called when the terminal receives a CSI sequence
|
|
54
|
+
/// (`\e[`) which is otherwise not implemented.
|
|
55
|
+
fn unhandled_csi(
|
|
56
|
+
&mut self,
|
|
57
|
+
_: &mut crate::Screen,
|
|
58
|
+
_i1: Option<u8>,
|
|
59
|
+
_i2: Option<u8>,
|
|
60
|
+
_params: &[&[u16]],
|
|
61
|
+
_c: char,
|
|
62
|
+
) {
|
|
63
|
+
}
|
|
64
|
+
/// This callback is called when the terminal receives a OSC sequence
|
|
65
|
+
/// (`\e]`) which is otherwise not implemented.
|
|
66
|
+
fn unhandled_osc(&mut self, _: &mut crate::Screen, _params: &[&[u8]]) {}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
impl Callbacks for () {}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
use unicode_width::UnicodeWidthChar as _;
|
|
2
|
+
|
|
3
|
+
// chosen to make the size of the cell struct 32 bytes
|
|
4
|
+
const CONTENT_BYTES: usize = 22;
|
|
5
|
+
|
|
6
|
+
const IS_WIDE: u8 = 0b1000_0000;
|
|
7
|
+
const IS_WIDE_CONTINUATION: u8 = 0b0100_0000;
|
|
8
|
+
const LEN_BITS: u8 = 0b0001_1111;
|
|
9
|
+
|
|
10
|
+
/// Represents a single terminal cell.
|
|
11
|
+
#[derive(Clone, Debug, Eq)]
|
|
12
|
+
pub struct Cell {
|
|
13
|
+
contents: [u8; CONTENT_BYTES],
|
|
14
|
+
len: u8,
|
|
15
|
+
attrs: crate::attrs::Attrs,
|
|
16
|
+
}
|
|
17
|
+
const _: () = assert!(std::mem::size_of::<Cell>() == 32);
|
|
18
|
+
|
|
19
|
+
impl PartialEq<Self> for Cell {
|
|
20
|
+
fn eq(&self, other: &Self) -> bool {
|
|
21
|
+
if self.len != other.len {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if self.attrs != other.attrs {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
let len = self.len();
|
|
28
|
+
self.contents[..len] == other.contents[..len]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl Cell {
|
|
33
|
+
pub(crate) fn new() -> Self {
|
|
34
|
+
Self {
|
|
35
|
+
contents: Default::default(),
|
|
36
|
+
len: 0,
|
|
37
|
+
attrs: crate::attrs::Attrs::default(),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fn len(&self) -> usize {
|
|
42
|
+
usize::from(self.len & LEN_BITS)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
pub(crate) fn set(&mut self, c: char, a: crate::attrs::Attrs) {
|
|
46
|
+
self.len = 0;
|
|
47
|
+
self.append_char(0, c);
|
|
48
|
+
// strings in this context should always be an arbitrary character
|
|
49
|
+
// followed by zero or more zero-width characters, so we should only
|
|
50
|
+
// have to look at the first character
|
|
51
|
+
self.set_wide(c.width().unwrap_or(1) > 1);
|
|
52
|
+
self.attrs = a;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
pub(crate) fn append(&mut self, c: char) {
|
|
56
|
+
let len = self.len();
|
|
57
|
+
if len >= CONTENT_BYTES - 4 {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if len == 0 {
|
|
61
|
+
self.contents[0] = b' ';
|
|
62
|
+
self.len += 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// we already checked that we have space for another codepoint
|
|
66
|
+
self.append_char(self.len(), c);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Writes bytes representing c at start
|
|
70
|
+
// Requires caller to verify start <= CODEPOINTS_IN_CELL * 4
|
|
71
|
+
fn append_char(&mut self, start: usize, c: char) {
|
|
72
|
+
c.encode_utf8(&mut self.contents[start..]);
|
|
73
|
+
self.len += u8::try_from(c.len_utf8()).unwrap();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pub(crate) fn clear(&mut self, attrs: crate::attrs::Attrs) {
|
|
77
|
+
self.len = 0;
|
|
78
|
+
self.attrs = attrs;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Returns the text contents of the cell.
|
|
82
|
+
///
|
|
83
|
+
/// Can include multiple unicode characters if combining characters are
|
|
84
|
+
/// used, but will contain at most one character with a non-zero character
|
|
85
|
+
/// width.
|
|
86
|
+
// Since contents has been constructed by appending chars encoded as UTF-8 it will be valid UTF-8
|
|
87
|
+
#[allow(clippy::missing_panics_doc)]
|
|
88
|
+
#[must_use]
|
|
89
|
+
pub fn contents(&self) -> &str {
|
|
90
|
+
std::str::from_utf8(&self.contents[..self.len()]).unwrap()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Returns whether the cell contains any text data.
|
|
94
|
+
#[must_use]
|
|
95
|
+
pub fn has_contents(&self) -> bool {
|
|
96
|
+
self.len() > 0
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Returns whether the text data in the cell represents a wide character.
|
|
100
|
+
#[must_use]
|
|
101
|
+
pub fn is_wide(&self) -> bool {
|
|
102
|
+
self.len & IS_WIDE != 0
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Returns whether the cell contains the second half of a wide character
|
|
106
|
+
/// (in other words, whether the previous cell in the row contains a wide
|
|
107
|
+
/// character)
|
|
108
|
+
#[must_use]
|
|
109
|
+
pub fn is_wide_continuation(&self) -> bool {
|
|
110
|
+
self.len & IS_WIDE_CONTINUATION != 0
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fn set_wide(&mut self, wide: bool) {
|
|
114
|
+
if wide {
|
|
115
|
+
self.len |= IS_WIDE;
|
|
116
|
+
} else {
|
|
117
|
+
self.len &= !IS_WIDE;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pub(crate) fn set_wide_continuation(&mut self, wide: bool) {
|
|
122
|
+
if wide {
|
|
123
|
+
self.len |= IS_WIDE_CONTINUATION;
|
|
124
|
+
} else {
|
|
125
|
+
self.len &= !IS_WIDE_CONTINUATION;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
pub(crate) fn attrs(&self) -> &crate::attrs::Attrs {
|
|
130
|
+
&self.attrs
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// Returns the foreground color of the cell.
|
|
134
|
+
#[must_use]
|
|
135
|
+
pub fn fgcolor(&self) -> crate::Color {
|
|
136
|
+
self.attrs.fgcolor
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// Returns the background color of the cell.
|
|
140
|
+
#[must_use]
|
|
141
|
+
pub fn bgcolor(&self) -> crate::Color {
|
|
142
|
+
self.attrs.bgcolor
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// Returns whether the cell should be rendered with the bold text
|
|
146
|
+
/// attribute.
|
|
147
|
+
#[must_use]
|
|
148
|
+
pub fn bold(&self) -> bool {
|
|
149
|
+
self.attrs.bold()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// Returns whether the cell should be rendered with the dim text
|
|
153
|
+
/// attribute.
|
|
154
|
+
#[must_use]
|
|
155
|
+
pub fn dim(&self) -> bool {
|
|
156
|
+
self.attrs.dim()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// Returns whether the cell should be rendered with the italic text
|
|
160
|
+
/// attribute.
|
|
161
|
+
#[must_use]
|
|
162
|
+
pub fn italic(&self) -> bool {
|
|
163
|
+
self.attrs.italic()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// Returns whether the cell should be rendered with the underlined text
|
|
167
|
+
/// attribute.
|
|
168
|
+
#[must_use]
|
|
169
|
+
pub fn underline(&self) -> bool {
|
|
170
|
+
self.attrs.underline()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// Returns whether the cell should be rendered with the inverse text
|
|
174
|
+
/// attribute.
|
|
175
|
+
#[must_use]
|
|
176
|
+
pub fn inverse(&self) -> bool {
|
|
177
|
+
self.attrs.inverse()
|
|
178
|
+
}
|
|
179
|
+
}
|