@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.
@@ -6,7 +6,9 @@ const { execFileSync } = require("child_process");
6
6
 
7
7
  const {
8
8
  binaryNameForPlatform,
9
- installBinaryFromArchive,
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("portable tarball extraction installs the unix binary", () => {
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
- installBinaryFromArchive({
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 binary", () => {
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
- installBinaryFromArchive({
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" "bunx --bun markdownlint-cli . --config .markdownlint.json --ignore target --ignore CHANGELOG.md"
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
+ }