Skip to content

Commit 23f1728

Browse files
committed
修复发布问题
1 parent 5dc68f0 commit 23f1728

10 files changed

Lines changed: 285 additions & 140 deletions

File tree

src-tauri/src/rewrite/llm/plain_support.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ fn finalize_singleline_candidate(source_text: &str, candidate_text: &str) -> Str
6161
}
6262

6363
let rewritten = collapse_line_breaks_to_spaces(candidate_text);
64-
let body = strip_redundant_prefix(&rewritten, &prefix);
64+
let body = strip_redundant_prefix(&rewritten, prefix);
6565
if body.trim().is_empty() {
6666
return source_text.to_string();
6767
}

src-tauri/src/rewrite/text.rs

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
pub fn normalize_text(input: &str) -> String {
2-
let normalized = input.replace("\r\n", "\n").replace('\r', "\n");
3-
let mut lines = Vec::new();
4-
let mut blank_streak = 0usize;
2+
let normalized = normalize_line_endings_to_lf(input);
3+
let mut out = String::with_capacity(normalized.len());
4+
let mut blank_streak = 0u32;
55

66
for raw_line in normalized.lines() {
77
let trimmed = raw_line.trim();
88
if trimmed.is_empty() {
9-
blank_streak += 1;
10-
if blank_streak <= 1 {
11-
lines.push(String::new());
9+
if blank_streak == 0 {
10+
out.push('\n');
1211
}
12+
blank_streak = blank_streak.saturating_add(1);
1313
} else {
1414
blank_streak = 0;
15-
lines.push(trimmed.to_string());
15+
out.push_str(trimmed);
16+
out.push('\n');
1617
}
1718
}
1819

19-
lines.join("\n").trim().to_string()
20+
out.truncate(out.trim_end_matches('\n').len());
21+
out
2022
}
2123

2224
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -67,7 +69,22 @@ pub fn detect_line_ending(text: &str) -> LineEnding {
6769
}
6870

6971
pub(super) fn normalize_line_endings_to_lf(text: &str) -> String {
70-
text.replace("\r\n", "\n").replace('\r', "\n")
72+
if !text.contains('\r') {
73+
return text.to_string();
74+
}
75+
let mut out = String::with_capacity(text.len());
76+
let mut chars = text.chars();
77+
while let Some(ch) = chars.next() {
78+
if ch == '\r' {
79+
out.push('\n');
80+
if chars.as_str().starts_with('\n') {
81+
chars.next();
82+
}
83+
} else {
84+
out.push(ch);
85+
}
86+
}
87+
out
7188
}
7289

7390
pub fn convert_line_endings(text: &str, ending: LineEnding) -> String {
@@ -241,14 +258,14 @@ fn detect_line_marker_len(rest: &str) -> usize {
241258
0
242259
}
243260

244-
pub(super) fn split_line_skeleton(line: &str) -> (String, String, String) {
261+
pub(super) fn split_line_skeleton(line: &str) -> (&str, &str, &str) {
245262
// 说明:
246-
// - 该函数用于格式骨架锁定”:尽量把缩进、列表符号、编号、引用前缀等视为不可变格式;
263+
// - 该函数用于格式骨架锁定”:尽量把缩进、列表符号、编号、引用前缀等视为不可变格式;
247264
// - 让模型只改写核心正文,避免空格/缩进/列表结构漂移。
248265
//
249266
// 这里仅把行尾空格/制表符视为 suffix;其他 Unicode 空白(例如 NBSP)更可能是正文的一部分。
250267
let base = trim_ascii_spaces_tabs_end(line);
251-
let suffix = line[base.len()..].to_string();
268+
let suffix = &line[base.len()..];
252269

253270
let bytes = base.as_bytes();
254271
let mut indent_end = 0usize;
@@ -264,8 +281,8 @@ pub(super) fn split_line_skeleton(line: &str) -> (String, String, String) {
264281
prefix_end += 1;
265282
}
266283

267-
let prefix = base[..prefix_end].to_string();
268-
let core = base[prefix_end..].to_string();
284+
let prefix = &base[..prefix_end];
285+
let core = &base[prefix_end..];
269286

270287
(prefix, core, suffix)
271288
}
@@ -297,7 +314,7 @@ pub(super) fn enforce_line_skeleton(source_line: &str, candidate_line: &str) ->
297314
return source_line.to_string();
298315
}
299316

300-
let body = strip_redundant_prefix(candidate_line, &prefix);
317+
let body = strip_redundant_prefix(candidate_line, prefix);
301318
if body.trim().is_empty() {
302319
return source_line.to_string();
303320
}

src-tauri/src/rewrite_unit/build.rs

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -134,25 +134,40 @@ fn unit_char_count(group: &[&WritebackSlot]) -> usize {
134134
}
135135

136136
fn ends_semantic_group(current: &[&WritebackSlot], preset: SegmentationPreset) -> bool {
137-
let text = display_text(current);
138-
let mut chars = text.chars().collect::<Vec<_>>();
139-
while chars.last().is_some_and(|ch| ch.is_whitespace()) {
140-
chars.pop();
141-
}
142-
while chars
143-
.last()
144-
.is_some_and(|ch| CLOSING_PUNCTUATION.contains(ch))
145-
{
146-
chars.pop();
147-
}
148-
let Some(last) = chars.last() else {
149-
return false;
137+
let boundary_set: &[char] = match preset {
138+
SegmentationPreset::Clause => &CLAUSE_BOUNDARIES,
139+
SegmentationPreset::Sentence => &SENTENCE_BOUNDARIES,
140+
SegmentationPreset::Paragraph => return false,
150141
};
151-
match preset {
152-
SegmentationPreset::Clause => CLAUSE_BOUNDARIES.contains(last),
153-
SegmentationPreset::Sentence => SENTENCE_BOUNDARIES.contains(last),
154-
SegmentationPreset::Paragraph => false,
142+
143+
// 从末尾反向扫描 slots,避免重建完整 display_text(消除 String + Vec<char> 分配)
144+
let mut skipping = true; // 先跳过空白,再跳过 CLOSING_PUNCTUATION
145+
146+
for slot in current.iter().rev() {
147+
// separator_after 在拼接字符串中最靠后,先处理
148+
for ch in slot.separator_after.chars().rev() {
149+
if skipping && ch.is_whitespace() {
150+
continue;
151+
}
152+
skipping = false;
153+
if CLOSING_PUNCTUATION.contains(&ch) {
154+
continue;
155+
}
156+
return boundary_set.contains(&ch);
157+
}
158+
for ch in slot.text.chars().rev() {
159+
if skipping && ch.is_whitespace() {
160+
continue;
161+
}
162+
skipping = false;
163+
if CLOSING_PUNCTUATION.contains(&ch) {
164+
continue;
165+
}
166+
return boundary_set.contains(&ch);
167+
}
155168
}
169+
170+
false
156171
}
157172

158173
#[cfg(test)]

src-tauri/src/rewrite_unit/projection.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ pub(crate) fn apply_slot_updates(
3030
Ok(next)
3131
}
3232

33-
fn slot_positions(slots: &[WritebackSlot]) -> HashMap<String, usize> {
33+
fn slot_positions(slots: &[WritebackSlot]) -> HashMap<&str, usize> {
3434
slots
3535
.iter()
3636
.enumerate()
37-
.map(|(index, slot)| (slot.id.clone(), index))
37+
.map(|(index, slot)| (slot.id.as_str(), index))
3838
.collect()
3939
}
4040

src-tauri/src/rewrite_unit/protocol.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,20 @@ pub struct RewriteUnitRequest {
5454

5555
impl RewriteUnitRequest {
5656
pub fn new(rewrite_unit_id: &str, format: &str, slots: Vec<RewriteUnitSlot>) -> Self {
57+
// 预计算容量,避免 collect() 多次重分配和 per-slot format!() 中间分配
58+
let cap: usize = slots
59+
.iter()
60+
.map(|s| s.text.len() + s.separator_after.len())
61+
.sum();
62+
let mut display_text = String::with_capacity(cap);
63+
for slot in &slots {
64+
display_text.push_str(&slot.text);
65+
display_text.push_str(&slot.separator_after);
66+
}
5767
Self {
5868
rewrite_unit_id: rewrite_unit_id.to_string(),
5969
format: format.to_string(),
60-
display_text: slots
61-
.iter()
62-
.map(|slot| format!("{}{}", slot.text, slot.separator_after))
63-
.collect(),
70+
display_text,
6471
slots,
6572
}
6673
}

src/App.tsx

Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,65 @@ export default function App() {
716716
withBusy
717717
});
718718

719+
// 稳定化内联箭头函数:避免每次渲染创建新函数引用导致 memo 子组件失效
720+
const onOpenDocumentStable = useCallback(() => {
721+
void handleOpenDocument();
722+
}, [handleOpenDocument]);
723+
const onExportStable = useCallback(() => {
724+
void handleExport();
725+
}, [handleExport]);
726+
const onStartWindowDragStable = useCallback(() => {
727+
void handleStartWindowDrag();
728+
}, [handleStartWindowDrag]);
729+
const onMinimizeWindowStable = useCallback(() => {
730+
void handleMinimizeWindow();
731+
}, [handleMinimizeWindow]);
732+
const onToggleMaximizeWindowStable = useCallback(() => {
733+
void handleToggleMaximizeWindow();
734+
}, [handleToggleMaximizeWindow]);
735+
const onCloseWindowStable = useCallback(() => {
736+
void handleCloseWindow();
737+
}, [handleCloseWindow]);
738+
const onStartRewriteStable = useCallback(
739+
(mode: AppSettings["rewriteMode"]) => {
740+
void handleStartRewrite(mode);
741+
},
742+
[handleStartRewrite]
743+
);
744+
const onPauseStable = useCallback(() => {
745+
void handlePause();
746+
}, [handlePause]);
747+
const onResumeStable = useCallback(() => {
748+
void handleResume();
749+
}, [handleResume]);
750+
const onCancelRewriteStable = useCallback(() => {
751+
void handleCancelRewrite();
752+
}, [handleCancelRewrite]);
753+
const onFinalizeDocumentStable = useCallback(() => {
754+
void handleFinalizeDocument();
755+
}, [handleFinalizeDocument]);
756+
const onResetSessionStable = useCallback(() => {
757+
void handleResetSession();
758+
}, [handleResetSession]);
759+
const onSaveEditorStable = useCallback(() => {
760+
void handleSaveEditor();
761+
}, [handleSaveEditor]);
762+
const onSaveEditorAndExitStable = useCallback(() => {
763+
void handleSaveEditor({ returnToWorkbench: true });
764+
}, [handleSaveEditor]);
765+
const onRewriteSelectionStable = useCallback(() => {
766+
void handleRewriteSelection();
767+
}, [handleRewriteSelection]);
768+
const onCheckUpdateStable = useCallback(() => {
769+
void handleCheckUpdate();
770+
}, [handleCheckUpdate]);
771+
const onRefreshReleaseVersionsStable = useCallback(() => {
772+
void handleRefreshReleaseVersions();
773+
}, [handleRefreshReleaseVersions]);
774+
const onSwitchSelectedReleaseStable = useCallback(() => {
775+
void handleSwitchSelectedRelease();
776+
}, [handleSwitchSelectedRelease]);
777+
719778
if (booting) {
720779
return <BootScreen />;
721780
}
@@ -739,13 +798,13 @@ export default function App() {
739798
windowMaximized={windowMaximized}
740799
showWindowControls={desktopRuntime}
741800
enableWindowDrag={desktopRuntime}
742-
onOpenDocument={() => void handleOpenDocument()}
801+
onOpenDocument={onOpenDocumentStable}
743802
onOpenSettings={openSettings}
744-
onExport={() => void handleExport()}
745-
onStartWindowDrag={() => void handleStartWindowDrag()}
746-
onMinimizeWindow={() => void handleMinimizeWindow()}
747-
onToggleMaximizeWindow={() => void handleToggleMaximizeWindow()}
748-
onCloseWindow={() => void handleCloseWindow()}
803+
onExport={onExportStable}
804+
onStartWindowDrag={onStartWindowDragStable}
805+
onMinimizeWindow={onMinimizeWindowStable}
806+
onToggleMaximizeWindow={onToggleMaximizeWindowStable}
807+
onCloseWindow={onCloseWindowStable}
749808
/>
750809

751810
<div className="workspace-stage">
@@ -770,12 +829,12 @@ export default function App() {
770829
onOpenDocument={handleOpenDocument}
771830
onSelectRewriteUnit={handleSelectRewriteUnit}
772831
onSelectSuggestion={handleSelectSuggestion}
773-
onStartRewrite={(mode) => void handleStartRewrite(mode)}
774-
onPause={() => void handlePause()}
775-
onResume={() => void handleResume()}
776-
onCancel={() => void handleCancelRewrite()}
777-
onFinalizeDocument={() => void handleFinalizeDocument()}
778-
onResetSession={() => void handleResetSession()}
832+
onStartRewrite={onStartRewriteStable}
833+
onPause={onPauseStable}
834+
onResume={onResumeStable}
835+
onCancel={onCancelRewriteStable}
836+
onFinalizeDocument={onFinalizeDocumentStable}
837+
onResetSession={onResetSessionStable}
779838
onApplySuggestion={handleApplySuggestion}
780839
onDismissSuggestion={handleDismissSuggestion}
781840
onDeleteSuggestion={handleDeleteSuggestion}
@@ -785,13 +844,11 @@ export default function App() {
785844
onChangeEditorText={handleChangeEditorText}
786845
onChangeEditorSlotText={handleChangeEditorSlotText}
787846
onChangeEditorHasSelection={setEditorHasSelection}
788-
onSaveEditor={() => void handleSaveEditor()}
789-
onSaveEditorAndExit={() =>
790-
void handleSaveEditor({ returnToWorkbench: true })
791-
}
847+
onSaveEditor={onSaveEditorStable}
848+
onSaveEditorAndExit={onSaveEditorAndExitStable}
792849
onDiscardEditorChanges={handleDiscardEditorChanges}
793850
onExitEditor={handleExitEditor}
794-
onRewriteSelection={() => void handleRewriteSelection()}
851+
onRewriteSelection={onRewriteSelectionStable}
795852
/>
796853
</div>
797854

@@ -823,10 +880,10 @@ export default function App() {
823880
onConfirm={requestConfirm}
824881
onTestProvider={handleTestProvider}
825882
onSaveSettings={handleSaveSettings}
826-
onCheckUpdate={() => void handleCheckUpdate()}
827-
onRefreshReleaseVersions={() => void handleRefreshReleaseVersions()}
883+
onCheckUpdate={onCheckUpdateStable}
884+
onRefreshReleaseVersions={onRefreshReleaseVersionsStable}
828885
onSelectReleaseTag={handleSelectReleaseTag}
829-
onSwitchSelectedRelease={() => void handleSwitchSelectedRelease()}
886+
onSwitchSelectedRelease={onSwitchSelectedReleaseStable}
830887
/>
831888
</main>
832889
</div>

src/stages/WorkbenchStage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ export const WorkbenchStage = memo(function WorkbenchStage({
154154
const orderedSuggestions = useMemo(() => {
155155
if (!currentSession) return [];
156156
return [...currentSession.suggestions].sort((a, b) => a.sequence - b.sequence);
157-
}, [currentSession]);
157+
}, [currentSession?.suggestions]);
158158

159159
const activeSuggestion = useMemo<RewriteSuggestion | null>(() => {
160160
if (!currentSession || !activeSuggestionId) return null;

0 commit comments

Comments
 (0)