Skip to content

Commit 579769b

Browse files
committed
drop some escapes; fix \t render
1 parent 2804b9a commit 579769b

File tree

6 files changed

+40
-43
lines changed

6 files changed

+40
-43
lines changed

include/webview_candidate_window.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ class WebviewCandidateWindow {
119119
void update_input_panel(const formatted<std::string> &preedit, int caret,
120120
const formatted<std::string> &auxUp,
121121
const formatted<std::string> &auxDown);
122-
void set_candidates(const std::vector<Candidate> &candidates,
123-
int highlighted, scroll_state_t scroll_state,
124-
bool scroll_start, bool scroll_end);
122+
void set_candidates(std::vector<Candidate> candidates, int highlighted,
123+
scroll_state_t scroll_state, bool scroll_start,
124+
bool scroll_end);
125125
void set_layout(layout_t layout) { layout_ = layout; }
126126
void set_writing_mode(writing_mode_t mode) { writing_mode_ = mode; }
127127

page/generic.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
line-height: 1em; /* align label and candidates */
102102
position: relative; /* for absolute position of mark */
103103
box-sizing: content-box; /* f5j: vitepress sets border-box for * which makes candidate shorter */
104+
white-space: pre-wrap; /* Don't collapse spaces and newlines of candidates. */
104105
}
105106

106107
.fcitx-label {

page/panel.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,6 @@ function isSingleEmoji(text: string) {
1313
return Array.from(segmenter.segment(text)).length === 1 && regex.test(text)
1414
}
1515

16-
function escapeWS(s: string) {
17-
// XXX: &emsp; is broken in Safari
18-
return s.replaceAll(' ', '&nbsp;').replaceAll('\n', '<br>').replaceAll('\t', '&emsp;')
19-
}
20-
2116
function divider(paging: boolean = false) {
2217
const e = div('fcitx-divider')
2318
// Is this divider between candidates and paging buttons?
@@ -113,19 +108,19 @@ export function setCandidates(cands: Candidate[], highlighted: number, markText:
113108
mark.classList.add('fcitx-no-text')
114109
}
115110
else {
116-
mark.innerHTML = markText
111+
mark.textContent = markText
117112
}
118113
candidateInner.append(mark)
119114
}
120115

121116
if (cands[i].label || scrollState === SCROLLING) {
122117
const label = div('fcitx-label')
123-
label.innerHTML = escapeWS(cands[i].label || label0)
118+
label.textContent = cands[i].label || label0
124119
candidateInner.append(label)
125120
}
126121

127122
const text = div('fcitx-text')
128-
text.innerHTML = escapeWS(cands[i].text)
123+
text.textContent = cands[i].text
129124
if (isSingleEmoji(cands[i].text)) {
130125
// Hack: for vertical-lr writing mode, 🙅‍♂️ is rotated on Safari and split to 🙅 and ♂ on Chrome.
131126
// Can't find a way that works for text that contains not only emoji (e.g. for preedit) but
@@ -136,7 +131,7 @@ export function setCandidates(cands: Candidate[], highlighted: number, markText:
136131

137132
if (cands[i].comment) {
138133
const comment = div('fcitx-comment')
139-
comment.innerHTML = escapeWS(cands[i].comment)
134+
comment.textContent = cands[i].comment
140135
candidateInner.append(comment)
141136
}
142137

page/ux.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ export function showContextmenu(x: number, y: number, index: number, actions: Ca
189189
contextmenu.innerHTML = ''
190190
for (const action of actions) {
191191
const item = div('fcitx-menu-item')
192-
item.innerHTML = action.text
192+
item.textContent = action.text
193193
item.addEventListener('click', () => {
194194
window.fcitx._action(index, action.id)
195195
hideContextmenu()

src/webview_candidate_window.cpp

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,6 @@ void to_json(nlohmann::json &j, const Candidate &c) {
2121
{"actions", c.actions}};
2222
}
2323

24-
CandidateAction escape_action(const CandidateAction &a) {
25-
return CandidateAction{a.id, escape_html(a.text)};
26-
}
27-
28-
Candidate escape_candidate(const Candidate &c) {
29-
std::vector<CandidateAction> escaped_actions;
30-
escaped_actions.reserve(c.actions.size());
31-
std::transform(c.actions.begin(), c.actions.end(),
32-
std::back_inserter(escaped_actions), escape_action);
33-
return Candidate{escape_html(c.text), escape_html(c.label),
34-
escape_html(c.comment), std::move(escaped_actions)};
35-
}
36-
3724
WebviewCandidateWindow::WebviewCandidateWindow()
3825
#ifndef __EMSCRIPTEN__
3926
: main_thread_id_(std::this_thread::get_id()),
@@ -115,14 +102,12 @@ void WebviewCandidateWindow::set_accent_color() const {
115102
}
116103
}
117104

118-
void WebviewCandidateWindow::set_candidates(
119-
const std::vector<Candidate> &candidates, int highlighted,
120-
scroll_state_t scroll_state, bool scroll_start, bool scroll_end) {
121-
candidates_.clear();
122-
candidates_.reserve(candidates.size());
123-
std::transform(candidates.begin(), candidates.end(),
124-
std::back_inserter(candidates_), escape_candidate);
125-
candidates_.shrink_to_fit();
105+
void WebviewCandidateWindow::set_candidates(std::vector<Candidate> candidates,
106+
int highlighted,
107+
scroll_state_t scroll_state,
108+
bool scroll_start,
109+
bool scroll_end) {
110+
candidates_ = std::move(candidates);
126111
highlighted_ = highlighted;
127112
scroll_state_ = scroll_state;
128113
scroll_start_ = scroll_start;
@@ -136,11 +121,7 @@ void WebviewCandidateWindow::scroll_key_action(
136121

137122
void WebviewCandidateWindow::answer_actions(
138123
const std::vector<CandidateAction> &actions) const {
139-
std::vector<CandidateAction> escaped_actions;
140-
escaped_actions.reserve(actions.size());
141-
std::transform(actions.begin(), actions.end(),
142-
std::back_inserter(escaped_actions), escape_action);
143-
invoke_js("answerActions", escaped_actions);
124+
invoke_js("answerActions", actions);
144125
}
145126

146127
void WebviewCandidateWindow::set_theme(theme_t theme) const {
@@ -166,9 +147,9 @@ void WebviewCandidateWindow::show(double x, double y, double height) const {
166147
invoke_js("setLayout", layout_);
167148
invoke_js("setWritingMode", writing_mode_);
168149
invoke_js("updateInputPanel", preedit_, auxUp_, auxDown_);
169-
invoke_js("setCandidates", candidates_, highlighted_,
170-
escape_html(highlight_mark_text_), pageable_, has_prev_,
171-
has_next_, scroll_state_, scroll_start_, scroll_end_);
150+
invoke_js("setCandidates", candidates_, highlighted_, highlight_mark_text_,
151+
pageable_, has_prev_, has_next_, scroll_state_, scroll_start_,
152+
scroll_end_);
172153
invoke_js("resize", epoch, 0., 0., false);
173154
}
174155

tests/test-corner-case.spec.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Page } from '@playwright/test'
22
import test, { expect } from '@playwright/test'
3-
import { candidate, followHostTheme, getBox, hover, init, panel, scrollExpand, setCandidates, setStyle, transparent, updateInputPanel } from './util'
3+
import { candidate, followHostTheme, getBox, getTextBox, hover, init, panel, scrollExpand, setCandidates, setStyle, transparent, updateInputPanel } from './util'
44

55
test('Horizontal multi-line candidate', async ({ page }) => {
66
await init(page)
@@ -25,6 +25,26 @@ test('Horizontal multi-line candidate', async ({ page }) => {
2525
expect((commentBox.y + commentBox.height / 2), 'Comment is centralized vertically').toEqual(middleY)
2626
})
2727

28+
test('Multi-space and tab', async ({ page }) => {
29+
await init(page)
30+
31+
await setCandidates(page, [{ text: '基准 单空格 双空格\t制表符' }], 0)
32+
const text = panel(page).locator('.fcitx-text')
33+
const rects: Record<number, DOMRect> = {}
34+
for (const i of [0, 1, 3, 5, 8, 11]) {
35+
rects[i] = await getTextBox(text, i)
36+
}
37+
const base = rects[1].left - rects[0].right
38+
const single = rects[3].left - rects[1].right
39+
const double = rects[8].left - rects[5].right
40+
const tab = rects[11].width
41+
42+
expect(base).toBe(0)
43+
expect(single).toBe(4)
44+
expect(double).toBe(8)
45+
expect(tab).toBeGreaterThan(double)
46+
})
47+
2848
test.describe('Ghost stripe default positive margin', () => {
2949
const cases = [
3050
{ name: 'System (macOS 26)', preset: (page: Page) => followHostTheme(page, 'macOS', 26) },

0 commit comments

Comments
 (0)