Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/release-notes/v1.7.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[English](#v1-7-3-english) · [简体中文](#v1-7-3-zh)

<a id="v1-7-3-english"></a>
### English

> This maintenance release makes internal editor navigation reliable for very long single-line files and makes selections easier to see, including SVG source locating.

#### Important fixes

- Text search in any file opened by the internal text editor now scrolls horizontally to matches inside very long single lines instead of only moving to the line.
- Editor selections are easier to see in both light and dark themes, and SVG preview-to-source navigation selects and clearly marks the complete target tag.

#### Known issue

- macOS packages are currently not code-signed or notarized, so Gatekeeper may report that Terma is damaged or cannot be opened. See [Issue #36](https://github.com/zmide/Terma/issues/36) for a temporary workaround.

#### Changes in this release

- Fix long single-line navigation and release v1.7.3 ([#37](https://github.com/zmide/Terma/pull/37), [@JunXiaoRuo](https://github.com/JunXiaoRuo))

**Full Changelog**: [v1.7.2...v1.7.3](https://github.com/zmide/Terma/compare/v1.7.2...v1.7.3)

<a id="v1-7-3-zh"></a>
### 简体中文

> 本维护版本改进超长单行文件的编辑器定位,并让选中内容更容易看清,包括 SVG 源码定位。

#### 重要修复

- 所有通过内部文本编辑器打开的文件,在超长单行中查找内容时都会横向滚动到实际匹配位置,不再只定位到同一行。
- 编辑器选区在浅色和深色主题下都更容易看清;从 SVG 预览定位源码时,会选中并醒目标记完整的目标标签。

#### 已知问题

- macOS 发布包目前尚未完成代码签名和公证,Gatekeeper 可能提示 Terma 已损坏或无法打开。临时处理方法见 [Issue #36](https://github.com/zmide/Terma/issues/36)。

#### 本次变更

- 修复超长单行定位并发布 v1.7.3([#37](https://github.com/zmide/Terma/pull/37),[@JunXiaoRuo](https://github.com/JunXiaoRuo))

**完整变更**:[v1.7.2...v1.7.3](https://github.com/zmide/Terma/compare/v1.7.2...v1.7.3)
48 changes: 46 additions & 2 deletions docs/update.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<a id="english"></a>
### English

> The next release will focus on recoverable terminal sessions, broader terminal AI interoperability, and cross-platform remote desktop acceptance.
> The next release will continue recoverable terminal, interoperability, and cross-platform remote desktop work.

#### Planned

Expand All @@ -18,14 +18,58 @@
<a id="简体中文"></a>
### 简体中文

> 下一版本将重点推进可恢复终端、更多终端 AI 互操作场景和跨平台远程桌面验收。
> 下一版本继续推进可恢复终端、互操作场景和跨平台远程桌面验收。

#### 计划

- 完成 Linux、macOS、Windows 降级和浏览器模式下的真实断网、重载与会话恢复验收。
- 使用更多 OpenAI 兼容网关扩展终端 AI 互操作测试,同时保持命令执行和 MCP 确认边界不变。
- 完成 TigerVNC、noVNC、系统客户端在三种桌面平台上的文本、中文和图片剪贴板验收。

## v1.7.3

[English](#v1-7-3-english) · [简体中文](#v1-7-3-zh)

<a id="v1-7-3-english"></a>
### English

> This maintenance release makes internal editor navigation reliable for very long single-line files and makes selections easier to see, including SVG source locating.

#### Important fixes

- Text search in any file opened by the internal text editor now scrolls horizontally to matches inside very long single lines instead of only moving to the line.
- Editor selections are easier to see in both light and dark themes, and SVG preview-to-source navigation selects and clearly marks the complete target tag.

#### Known issue

- macOS packages are currently not code-signed or notarized, so Gatekeeper may report that Terma is damaged or cannot be opened. See [Issue #36](https://github.com/zmide/Terma/issues/36) for a temporary workaround.

#### Changes in this release

- Fix long single-line navigation and release v1.7.3 ([#37](https://github.com/zmide/Terma/pull/37), [@JunXiaoRuo](https://github.com/JunXiaoRuo))

**Full Changelog**: [v1.7.2...v1.7.3](https://github.com/zmide/Terma/compare/v1.7.2...v1.7.3)

<a id="v1-7-3-zh"></a>
### 简体中文

> 本维护版本改进超长单行文件的编辑器定位,并让选中内容更容易看清,包括 SVG 源码定位。

#### 重要修复

- 所有通过内部文本编辑器打开的文件,在超长单行中查找内容时都会横向滚动到实际匹配位置,不再只定位到同一行。
- 编辑器选区在浅色和深色主题下都更容易看清;从 SVG 预览定位源码时,会选中并醒目标记完整的目标标签。

#### 已知问题

- macOS 发布包目前尚未完成代码签名和公证,Gatekeeper 可能提示 Terma 已损坏或无法打开。临时处理方法见 [Issue #36](https://github.com/zmide/Terma/issues/36)。

#### 本次变更

- 修复超长单行定位并发布 v1.7.3([#37](https://github.com/zmide/Terma/pull/37),[@JunXiaoRuo](https://github.com/JunXiaoRuo))

**完整变更**:[v1.7.2...v1.7.3](https://github.com/zmide/Terma/compare/v1.7.2...v1.7.3)

## v1.7.2

[English](#english) · [简体中文](#简体中文)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "terma",
"version": "1.7.2",
"version": "1.7.3",
"releaseRevision": 1,
"private": true,
"license": "GPL-3.0-only",
Expand Down
7 changes: 4 additions & 3 deletions public/app-sftp-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -1017,12 +1017,12 @@ function sftpTextModal(title, content, size=0, limit=5*1024*1024, encoding="utf8
const startPosition = aceDocument.indexToPosition(start, 0);
const endPosition = aceDocument.indexToPosition(end, 0);
aceEditor.selection.setRange(new Range(startPosition.row, startPosition.column, endPosition.row, endPosition.column), false);
aceEditor.scrollToLine(startPosition.row, true, true);
aceEditor.scrollToLine?.(startPosition.row, true, true);
aceEditor.renderer?.scrollCursorIntoView?.(startPosition, 0.45);
if (!keepSearchFocus) aceEditor.focus();
} else {
fallbackEditor.setSelectionRange(start, end);
const line = editorSearchSource().slice(0, start).split("\n").length - 1;
fallbackEditor.scrollTop = Math.max(0, line * 20 - fallbackEditor.clientHeight / 2);
scrollSftpFallbackEditorToOffset(fallbackEditor, editorSearchSource(), start);
if (!keepSearchFocus) fallbackEditor.focus();
}
};
Expand Down Expand Up @@ -1050,6 +1050,7 @@ function sftpTextModal(title, content, size=0, limit=5*1024*1024, encoding="utf8
releaseSvgPreviewInteractions();
releaseSvgPreviewLayout();
releaseFloatingEditor();
svgSourceLocator?.clear?.();
if (editorKey && sftpFloatingEditorRegistry.get(editorKey)?.modal === modal) sftpFloatingEditorRegistry.delete(editorKey);
try { aceEditor?.destroy(); } catch {}
lightSource = "";
Expand Down
94 changes: 83 additions & 11 deletions public/app-sftp-svg-source.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,56 @@
function scrollSftpFallbackEditorToOffset(editor, text, offset) {
if (!editor) return false;
const source = String(text || "");
const index = Math.max(0, Math.min(source.length, Number(offset) || 0));
const lineStart = source.lastIndexOf("\n", Math.max(0, index - 1)) + 1;
const column = Math.max(0, index - lineStart);
const line = source.slice(0, index).split("\n").length - 1;
const style = globalThis.getComputedStyle?.(editor);
const fontSize = Number.parseFloat(style?.fontSize || "14") || 14;
const lineHeight = Number.parseFloat(style?.lineHeight || "") || fontSize * 1.45;
const approximateCharWidth = Math.max(6, fontSize * .62);
editor.scrollTop = Math.max(0, line * lineHeight - editor.clientHeight * .4);
editor.scrollLeft = Math.max(0, Math.min(Math.max(0, editor.scrollWidth - editor.clientWidth), column * approximateCharWidth - editor.clientWidth * .42));
return true;
}

function createSftpSvgSourceLocator({getText, getAceEditor, getFallbackEditor, onHighlight}) {
const findSourceIndex = (text, targetId, {preferReference=false, sourceElement=null} = {}) => {
let aceTargetMarkerId = null;
let aceLineMarkerId = null;
let aceGutterRow = -1;
let fallbackHighlightActive = false;
const getAceRange = (start, end) => {
const Range = globalThis.ace?.require?.("ace/range")?.Range;
if (!Range) return null;
return new Range(start.row, start.column, end.row, end.column);
};
const clearAceHighlight = aceEditor => {
const session = aceEditor?.session;
if (!session) return;
if (aceTargetMarkerId !== null) {
session.removeMarker?.(aceTargetMarkerId);
aceTargetMarkerId = null;
}
if (aceLineMarkerId !== null) {
session.removeMarker?.(aceLineMarkerId);
aceLineMarkerId = null;
}
if (aceGutterRow >= 0) {
session.removeGutterDecoration?.(aceGutterRow, "sftp-svg-source-gutter");
aceGutterRow = -1;
}
};
const highlightAceRange = (aceEditor, start, end) => {
const session = aceEditor?.session;
const range = getAceRange(start, end);
if (!session || !range) return;
clearAceHighlight(aceEditor);
aceTargetMarkerId = session.addMarker?.(range, "sftp-svg-source-target", "text", true) ?? null;
aceLineMarkerId = session.addMarker?.(getAceRange(start, {row:start.row, column:Math.max(1, start.column + 1)}), "sftp-svg-source-line", "fullLine", false) ?? null;
session.addGutterDecoration?.(start.row, "sftp-svg-source-gutter");
aceGutterRow = start.row;
};
const findSourceRange = (text, targetId, {preferReference=false, sourceElement=null} = {}) => {
const wanted = String(targetId || "");
if (!wanted) return -1;
const candidates = [];
Expand Down Expand Up @@ -35,33 +86,47 @@ function createSftpSvgSourceLocator({getText, getAceEditor, getFallbackEditor, o
if (attributes[attributeName] === attributeValue) score += 6;
}
}
candidates.push({index:match.index, score});
candidates.push({index:match.index, length:raw.length, score});
}
if (!/\/\s*>$/.test(raw) && !["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"].includes(name)) stack.push(name);
}
candidates.sort((left, right) => right.score - left.score || left.index - right.index);
return candidates[0]?.index ?? -1;
return candidates[0] ? {index:candidates[0].index, length:candidates[0].length} : null;
};
const focusSourceId = (target, options={}) => {
const targetInfo = target && typeof target === "object" ? target : {id:target};
const targetId = String(targetInfo.id || "").trim();
if (!targetId) return;
const text = String(getText?.() || "");
let index = findSourceIndex(text, targetId, {preferReference:targetInfo.nodeName === "use" || options.preferReference, sourceElement:targetInfo.sourceElement});
if (index < 0) index = text.indexOf(targetId);
if (index < 0) return;
const sourceRange = findSourceRange(text, targetId, {preferReference:targetInfo.nodeName === "use" || options.preferReference, sourceElement:targetInfo.sourceElement});
const index = sourceRange?.index ?? text.indexOf(targetId);
if (index < 0) return false;
const rangeLength = Math.max(targetId.length, sourceRange?.length || 0);
const aceEditor = getAceEditor?.();
const fallbackEditor = getFallbackEditor?.();
if (aceEditor) {
const position = aceEditor.session.getDocument().indexToPosition(index, 0);
aceEditor.selection.moveTo(position.row, position.column);
aceEditor.scrollToLine(position.row, true, true);
const document = aceEditor.session.getDocument();
const start = document.indexToPosition(index, 0);
const end = document.indexToPosition(index + rangeLength, 0);
const range = getAceRange(start, end);
// Keep the caret at the opening tag while retaining the full range
// selection, so existing source-to-preview navigation can still read
// the tag from the cursor position.
if (range) aceEditor.selection.setRange(range, true);
else aceEditor.selection.moveTo(start.row, start.column);
aceEditor.scrollToLine(start.row, true, true);
aceEditor.renderer?.scrollCursorIntoView?.(start, 0.45);
highlightAceRange(aceEditor, start, end);
aceEditor.focus();
} else if (fallbackEditor) {
fallbackEditor.focus();
fallbackEditor.setSelectionRange(index, index + targetId.length);
fallbackEditor.setSelectionRange(index, index + rangeLength);
scrollSftpFallbackEditorToOffset(fallbackEditor, text, index);
fallbackEditor.classList.add("sftp-source-locate-active");
fallbackHighlightActive = true;
}
onHighlight?.(targetId, {locate:options.locate !== false, force:true});
return true;
};
const idForCursor = () => {
const text = String(getText?.() || "");
Expand Down Expand Up @@ -102,7 +167,14 @@ function createSftpSvgSourceLocator({getText, getAceEditor, getFallbackEditor, o
}
return "";
};
return {focusSourceId, idForCursor};
const clear = () => {
const aceEditor = getAceEditor?.();
clearAceHighlight(aceEditor);
const fallbackEditor = getFallbackEditor?.();
if (fallbackHighlightActive) fallbackEditor?.classList.remove("sftp-source-locate-active");
fallbackHighlightActive = false;
};
return {focusSourceId, idForCursor, clear};
}

function createSftpSvgEditorScheduler({isActive, renderPreview, syncCursor}) {
Expand Down
5 changes: 5 additions & 0 deletions public/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -2832,6 +2832,11 @@ body.sftp-item-drag-active .tab[data-kind="sftp"], body.sftp-item-drag-active .t
.sftp-editor-workspace.showing-svg-preview > .sftp-editor-svg-splitter { grid-column:2; grid-row:1; }
.sftp-editor-workspace.showing-svg-preview > :is(.sftp-code-editor,.code-editor,.sftp-light-editor-shell) { grid-column:3; grid-row:1; }
.sftp-code-editor { width:100%; height:100%; min-height:0; border:1px solid var(--line); border-radius:5px; overflow:hidden; }
.sftp-code-editor .ace_marker-layer .ace_selection { background:color-mix(in srgb,var(--accent2) 48%,transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--accent) 72%,var(--accent2)); }
.sftp-code-editor .ace_marker-layer .sftp-svg-source-line { position:absolute; z-index:1; background:color-mix(in srgb,var(--accent2) 12%,transparent); border-top:1px solid color-mix(in srgb,var(--accent2) 42%,transparent); border-bottom:1px solid color-mix(in srgb,var(--accent2) 42%,transparent); }
.sftp-code-editor .ace_marker-layer .sftp-svg-source-target { position:absolute; z-index:2; box-sizing:border-box; border:1px solid color-mix(in srgb,var(--accent) 78%,var(--accent2)); border-radius:2px; background:color-mix(in srgb,var(--accent2) 36%,transparent); box-shadow:0 0 0 1px color-mix(in srgb,var(--accent) 42%,transparent),0 0 8px color-mix(in srgb,var(--accent2) 58%,transparent); }
.sftp-code-editor .ace_gutter-cell.sftp-svg-source-gutter { color:var(--text); background:color-mix(in srgb,var(--accent2) 22%,transparent); box-shadow:inset 3px 0 0 var(--accent2); font-weight:700; }
.text-editor.sftp-source-locate-active { outline:2px solid color-mix(in srgb,var(--accent2) 68%,var(--line)); outline-offset:-2px; box-shadow:0 0 0 3px color-mix(in srgb,var(--accent2) 18%,transparent),0 0 14px color-mix(in srgb,var(--accent2) 34%,transparent); }
.sftp-svg-editor-preview { position:relative; min-width:0; min-height:0; overflow:hidden; display:grid; grid-template-rows:auto minmax(0,1fr); border:1px solid var(--line); border-radius:5px; background:repeating-conic-gradient(var(--panel2) 0 25%,var(--panel) 0 50%) 50% / 18px 18px; }
.sftp-svg-editor-preview-toolbar { min-width:0; min-height:34px; display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:5px 8px; padding:4px 6px; border-bottom:1px solid var(--line); background:color-mix(in srgb,var(--panel2) 82%,transparent); }
.sftp-svg-editor-preview-hint { min-width:120px; flex:1 1 150px; overflow:hidden; color:var(--muted); font-size:11px; text-overflow:ellipsis; white-space:nowrap; }
Expand Down
1 change: 1 addition & 0 deletions scripts/regression-check.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions scripts/release-bilingual-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ if (fs.existsSync(updatePath)) {
assertSynchronizedSections(validateBilingualReleaseBody(draftMatch[1], "下一版草稿"), "下一版草稿");
}
validateBilingualReleaseBody(bilingualPlaceholder("v9.9.9"), "双语空说明模板");
validateBilingualReleaseBody(
bilingualPlaceholder("v9.9.9")
.replaceAll("#english", "#v9-9-9-english")
.replaceAll('id="english"', 'id="v9-9-9-english"')
.replaceAll("#简体中文", "#v9-9-9-zh")
.replaceAll('id="简体中文"', 'id="v9-9-9-zh"'),
"版本化快速跳转"
);

const releaseNotesDirectory = path.join(root, ".github", "release-notes");
for (const name of fs.readdirSync(releaseNotesDirectory)) {
Expand Down
Loading