diff --git a/README.ja.md b/README.ja.md index ff42840..63039b8 100644 --- a/README.ja.md +++ b/README.ja.md @@ -272,7 +272,7 @@ python3 scripts/gen_testdata.py --encoding-set --out-dir testdata/ # UTF-8 / S python3 scripts/gen_testdata.py --size 10G --jp --out testdata/test_10gb.log ``` -配布用ディスクイメージ(`.build/MrEditor-1.18.0.dmg`)の作成: +配布用ディスクイメージ(`.build/MrEditor-1.19.0.dmg`)の作成: ```sh sh scripts/make_dmg.sh @@ -362,7 +362,8 @@ vmmap $(pgrep -x MrEditor) | grep test_10gb.log # → 10.0G 2.8G 0K (vsiz - **1.15.0 — 日本語の CSV が重かった。** 日本語の CSV は Shift-JIS・CRLF・引用符つきで届く。その検索が **1 行ずつ `String` へ変換してから照合していて、1.06GB・581万行で 26.2 秒**かかっていた。release ビルドにしても縮まない —— 自分のコードではなく、581万回の文字列生成そのものが費用だったから。**探す語のほうを Shift-JIS のバイト列にして、直接走査するようにした。2.4 秒(11 倍)。**2 バイト目が ASCII と重なるので、当たった位置が文字の切れ目に乗っているかを確かめて誤爆を弾く。あわせて **検索バーが「終わった」を出す** ようになった —— 走査中は件数と進み具合が一緒に動き、終わると進み具合が消える。前は 1 件当たった瞬間に件数だけになり、まだ増え続けるのに確定したように見えていた ✅(1.15.0) - **1.16.0 — 閉じるタブを1つ間違えると、戻り道はファイル ▸ 開く…でパスを記憶から打ち直すことだけだった。** **`⇧⌘T` で、いま閉じたファイルを開き直せる**ようにした。閉じたドキュメントはスタックで持ち(直近が末尾)、押すたびに1件 pop して開く——押し続ければどんどん前まで戻れる。保存先が確定していない新規ドキュメント(まだ「名称未設定」のまま)は積まない。ディスク上に開き直す先が無いため ✅(1.16.0) - **1.17.0 — 新しくコピーすると、直前のコピーは消える。** 貼り付け先を間違えたとき、戻る方法がコピー元のアプリを開き直す以外に無かった。**編集メニュー→クリップボード履歴**(ツールバーにも任意で追加できる)に直近のコピーを並べ、選べばそのまま貼り付く。**ディスクには残さず**、1Password 等が付ける「見せない」の印(`org.nspasteboard.ConcealedType`)が立ったコピーは記録しない ✅(1.17.0) -- **1.18.0 — AI診断は「鍵は自分の Mac から出さない」と約束したが、鍵そのものを外に置きたくない人には、まだクラウドの契約が要った。** **BYOK にローカルの Ollama を追加**(環境設定 > AI)——選べば鍵は不要で、リクエストも機外に出ない。あわせて検索バーに**「最近の検索」**(過去に検索した語をメニューから選び直せる)と、**Markdown/主要言語の構文ハイライト**(見出し・強調・コード・リンク、コメント・文字列・数値・予約語。対応: Python/Swift/JS/Shell/YAML/JSON/SQL/CSS/Java/PHP/Go/Ruby/Perl)を追加。一致行だけ表示(フィルタ)中に検索バーを開き直すと置換ボタンが押せる見た目のまま実は効かない不具合も修正——読み取り専用バナーに理由を出すようにした ✅(このリリース) +- **1.18.0 — AI診断は「鍵は自分の Mac から出さない」と約束したが、鍵そのものを外に置きたくない人には、まだクラウドの契約が要った。** **BYOK にローカルの Ollama を追加**(環境設定 > AI)——選べば鍵は不要で、リクエストも機外に出ない。あわせて検索バーに**「最近の検索」**(過去に検索した語をメニューから選び直せる)と、**Markdown/主要言語の構文ハイライト**(見出し・強調・コード・リンク、コメント・文字列・数値・予約語。対応: Python/Swift/JS/Shell/YAML/JSON/SQL/CSS/Java/PHP/Go/Ruby/Perl)を追加。一致行だけ表示(フィルタ)中に検索バーを開き直すと置換ボタンが押せる見た目のまま実は効かない不具合も修正——読み取り専用バナーに理由を出すようにした ✅(1.18.0) +- **1.19.0 — ターミナルやAIの回答からコピーしたパスは、File ▸ Openで手打ちし直す以外に開く方法が無かった。** **ファイル ▸ パスを指定して開く…(⌥⌘O)**は貼り付けたパスをそのまま受け取る——前後の引用符や`~`を処理し、ファイルはエディタで開く。フォルダはMrEditor内に行き先が無いのでFinderで表示するだけにした——独自の閲覧UIは作っていない ✅(このリリース) - **以降** — その他の分析ツール > **⚠️ v0.7 以前の dmg は、ダウンロードした Mac で起動しません。** @@ -388,8 +389,10 @@ vmmap $(pgrep -x MrEditor) | grep test_10gb.log # → 10.0G 2.8G 0K (vsiz | フォルダを横断して検索 | 結果をクリックするとその行が開く。Shift-JIS / EUC-JP も判定して読む | 10GB・リテラル 1 語で **3.14 秒**(ripgrep と互角) | 無料版にも「分析」メニューは同じ位置にあります(グレーアウトはしません)。押すと、 -その機能の説明が 1 枚出ます。MrkEditor は Polar で販売しています: -https://buy.polar.sh/polar_cl_S32h7CYTXCxbYQ5IimcpI4DayCFkHUYQ8I6bv2tOki8 +その機能の説明が 1 枚出ます。専用ページで詳しく読めます。購入は Polar からです: + +- https://mr-tabata.github.io/MrEditor/MrkEditor.ja.html +- https://buy.polar.sh/polar_cl_S32h7CYTXCxbYQ5IimcpI4DayCFkHUYQ8I6bv2tOki8 ## まだ「作らない」もの diff --git a/README.md b/README.md index e4e052f..106c3a0 100644 --- a/README.md +++ b/README.md @@ -296,7 +296,7 @@ python3 scripts/gen_testdata.py --encoding-set --out-dir testdata/ # UTF-8 / S python3 scripts/gen_testdata.py --size 10G --jp --out testdata/test_10gb.log ``` -Build a distributable disk image (`.build/MrEditor-1.18.0.dmg`): +Build a distributable disk image (`.build/MrEditor-1.19.0.dmg`): ```sh sh scripts/make_dmg.sh @@ -387,7 +387,8 @@ vmmap $(pgrep -x MrEditor) | grep test_10gb.log # → 10.0G 9.1G 0K (vsiz - **1.15.0 — Japanese CSVs were slow.** They arrive as Shift-JIS with CRLF and quoted fields, and searching one **decoded every line into a `String` before matching — 26.2s on a 1.06 GB, 5.8M-line file**. A release build did not help: the cost was 5.8M string allocations, not our own code. **Now the search term is encoded into Shift-JIS and the bytes are scanned directly. 2.4s — 11x.** A trailing byte can look like ASCII, so a match only counts when it lands on a character boundary. **The search bar also tells you when it is done**: the count and the percentage move together while scanning, and the percentage disappears at the end. Before, the count replaced "searching" as soon as the first match landed, so it looked settled while it was still growing ✅ (1.15.0) - **1.16.0 — Close the wrong tab a moment too soon, and the only way back was File ▸ Open, typing the path from memory.** **`⇧⌘T` reopens the file you just closed.** Closed documents are kept on a stack, most recent last; each press pops one and opens it, going further back the more you press. A document still untitled (never saved to a path) is not pushed — there is nothing on disk to reopen ✅ (1.16.0) - **1.17.0 — Copy something new, and the one before it is gone.** Paste in the wrong place and the only way back was switching to whatever app you copied it from and doing it again. **Edit ▸ Clipboard History** (and an optional toolbar button) lists recent copies — pick one and it pastes right there. **Nothing is written to disk**, and anything a password manager marks concealed (`org.nspasteboard.ConcealedType`) is never recorded ✅ (1.17.0) -- **1.18.0 — AI diagnose already promised the key never leaves your Mac, but anyone unwilling to hand a key to the cloud at all still had nowhere to point it.** **BYOK now includes local Ollama** (Preferences ▸ AI) — pick it and no key, and no request, ever leaves the machine. Also added: **recent searches** in the search bar (pick a past query from a menu), and **Markdown/major-language syntax highlighting** (headings, emphasis, code, links; comments, strings, numbers, keywords — Python/Swift/JS/Shell/YAML/JSON/SQL/CSS/Java/PHP/Go/Ruby/Perl). Fixed a bug where reopening the search bar while filtering left Replace looking enabled when it silently did nothing — the read-only banner now says why ✅ (this release) +- **1.18.0 — AI diagnose already promised the key never leaves your Mac, but anyone unwilling to hand a key to the cloud at all still had nowhere to point it.** **BYOK now includes local Ollama** (Preferences ▸ AI) — pick it and no key, and no request, ever leaves the machine. Also added: **recent searches** in the search bar (pick a past query from a menu), and **Markdown/major-language syntax highlighting** (headings, emphasis, code, links; comments, strings, numbers, keywords — Python/Swift/JS/Shell/YAML/JSON/SQL/CSS/Java/PHP/Go/Ruby/Perl). Fixed a bug where reopening the search bar while filtering left Replace looking enabled when it silently did nothing — the read-only banner now says why ✅ (1.18.0) +- **1.19.0 — A path copied from a terminal or an AI's answer had nowhere to go but File ▸ Open, retyped by hand.** **File ▸ Open by Path… (⌥⌘O)** takes a pasted path directly — surrounding quotes and `~` are handled — and opens a file in the editor. A folder has nowhere to go inside MrEditor, so it is revealed in Finder instead; no path-browsing UI was built for it ✅ (this release) - **later** — more analysis tooling > **⚠️ Builds up to v0.7 do not launch on a Mac that downloaded them.** @@ -413,8 +414,10 @@ answer out of it is paid.** | Search across folders | click a hit and the file opens at that line; Shift-JIS / EUC-JP detected per file | **3.14 s** for one literal over a single 10 GB log (on par with ripgrep) | The free app has the same Analyze menu in the same place (never greyed out); choosing an item -shows one page explaining that feature. MrkEditor is sold through Polar: -https://buy.polar.sh/polar_cl_S32h7CYTXCxbYQ5IimcpI4DayCFkHUYQ8I6bv2tOki8 +shows one page explaining that feature. See the dedicated MrkEditor page, or buy through Polar: + +- https://mr-tabata.github.io/MrEditor/MrkEditor.html +- https://buy.polar.sh/polar_cl_S32h7CYTXCxbYQ5IimcpI4DayCFkHUYQ8I6bv2tOki8 ## Not yet diff --git a/Sources/MrEditorCore/AppDelegate.swift b/Sources/MrEditorCore/AppDelegate.swift index a23b35f..b34d1b8 100644 --- a/Sources/MrEditorCore/AppDelegate.swift +++ b/Sources/MrEditorCore/AppDelegate.swift @@ -17,6 +17,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenu /// 開いた遠隔の面。持っておかないと即座に閉じる(NSWindowController は自分を保持しない)。 private var remoteWindows: [RemoteWindowController] = [] private var preferencesController: PreferencesWindowController? + private var pathOpenController: PathOpenWindowController? func applicationDidFinishLaunching(_ notification: Notification) { // 開発ビルド(バンドル無し)でも Dock・About でアプリアイコンを出す。 @@ -342,6 +343,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenu controller.window?.makeKeyAndOrderFront(nil) } + /// ターミナルや AI の回答からコピーしたパスを貼り付けて開く(⌥⌘O)。 + @objc private func openByPath(_ sender: Any?) { + if pathOpenController == nil { + pathOpenController = PathOpenWindowController(openHandler: { [weak self] url in + self?.ensureController().open(url: url) + }) + } + pathOpenController?.show() + } + /// File > 最近使った項目/Edit > クリップボード履歴、サブメニューを開くたびに再構築する。 func menuNeedsUpdate(_ menu: NSMenu) { if menu === recentMenu { updateRecentMenu(menu); return } @@ -607,6 +618,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, NSMenu remoteItem.keyEquivalentModifierMask = [.command, .control] remoteItem.target = self fileMenu.addItem(remoteItem) + // ターミナルや AI の回答からコピーしたパスを貼り付けて開く。 + let pathItem = NSMenuItem(title: L("menu.openByPath"), + action: #selector(openByPath(_:)), keyEquivalent: "o") + pathItem.keyEquivalentModifierMask = [.command, .option] + pathItem.target = self + fileMenu.addItem(pathItem) // 最近使った項目(サブメニューは開くたびに menuNeedsUpdate で再構築) let recentItem = NSMenuItem(title: L("menu.openRecent"), action: nil, keyEquivalent: "") let recent = NSMenu(title: L("menu.openRecent")) diff --git a/Sources/MrEditorCore/AppInfo.swift b/Sources/MrEditorCore/AppInfo.swift index 8683d05..eb8bcc1 100644 --- a/Sources/MrEditorCore/AppInfo.swift +++ b/Sources/MrEditorCore/AppInfo.swift @@ -20,7 +20,7 @@ enum AppInfo { static var version: String { (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? fallbackVersion } - private static let fallbackVersion = "1.18.0" + private static let fallbackVersion = "1.19.0" /// ヘルプメニューから開くプロジェクトページ。 static let helpURL = URL(string: "https://github.com/MR-TABATA/MrEditor")! diff --git a/Sources/MrEditorCore/Resources/en.lproj/Localizable.strings b/Sources/MrEditorCore/Resources/en.lproj/Localizable.strings index ec186ed..cf98fbd 100644 --- a/Sources/MrEditorCore/Resources/en.lproj/Localizable.strings +++ b/Sources/MrEditorCore/Resources/en.lproj/Localizable.strings @@ -206,6 +206,12 @@ "prefs.share.copied" = "Share link copied to clipboard."; "prefs.share.imported" = "Settings applied."; "prefs.share.export.defaultName" = "MrEditor Theme"; + +/* Open by path */ +"pathOpen.title" = "Open by Path"; +"pathOpen.placeholder" = "Path to a file or folder"; +"pathOpen.open" = "Open"; +"pathOpen.notFound" = "That path could not be found"; "share.apply.title" = "Apply these appearance settings?"; "share.apply.confirm" = "Apply"; "share.import.failed.title" = "Couldn’t read the settings"; @@ -384,6 +390,8 @@ "menu.nextBookmark" = "Next Bookmark"; "menu.prevBookmark" = "Previous Bookmark"; +"menu.openByPath" = "Open by Path…"; + /* --- Remote (B5) --- */ "menu.openRemote" = "Open Remote…"; "remote.title" = "Remote"; diff --git a/Sources/MrEditorCore/Resources/ja.lproj/Localizable.strings b/Sources/MrEditorCore/Resources/ja.lproj/Localizable.strings index 8739e8f..1ae2272 100644 --- a/Sources/MrEditorCore/Resources/ja.lproj/Localizable.strings +++ b/Sources/MrEditorCore/Resources/ja.lproj/Localizable.strings @@ -206,6 +206,12 @@ "prefs.share.copied" = "共有リンクをクリップボードにコピーしました。"; "prefs.share.imported" = "設定を適用しました。"; "prefs.share.export.defaultName" = "MrEditor テーマ"; + +/* パスを指定して開く */ +"pathOpen.title" = "パスを指定して開く"; +"pathOpen.placeholder" = "ファイルまたはフォルダのパス"; +"pathOpen.open" = "開く"; +"pathOpen.notFound" = "そのパスが見つかりません"; "share.apply.title" = "この外観設定を適用しますか?"; "share.apply.confirm" = "適用"; "share.import.failed.title" = "設定を読み込めませんでした"; @@ -384,6 +390,8 @@ "menu.nextBookmark" = "次のしおりへ"; "menu.prevBookmark" = "前のしおりへ"; +"menu.openByPath" = "パスを指定して開く…"; + /* --- リモート(B5) --- */ "menu.openRemote" = "リモートを開く…"; "remote.title" = "リモート"; diff --git a/Sources/MrEditorCore/UI/PathOpenWindowController.swift b/Sources/MrEditorCore/UI/PathOpenWindowController.swift new file mode 100644 index 0000000..81ea25d --- /dev/null +++ b/Sources/MrEditorCore/UI/PathOpenWindowController.swift @@ -0,0 +1,105 @@ +import AppKit + +/// 「パスを指定して開く…」(⌥⌘O)。ターミナルや AI の回答からコピーしたローカルパスを +/// 貼り付けて開くための小さな窓。狙いは独自性ではなく、パスを迷わず開ける使いやすさ。 +/// +/// **フォルダを開く仕組みは MrEditor に無い**(サイドバーのファイルツリー等を持たない)。 +/// フォルダが渡されたら、独自の閲覧 UI は作らず Finder で見せるだけにする。 +final class PathOpenWindowController: NSWindowController { + + private let pathField = NSTextField() + private let errorLabel = NSTextField(labelWithString: "") + private let openHandler: (URL) -> Void + + init(openHandler: @escaping (URL) -> Void) { + self.openHandler = openHandler + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 480, height: 110), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + window.title = L("pathOpen.title") + window.isReleasedWhenClosed = false + super.init(window: window) + window.center() + buildLayout() + } + + required init?(coder: NSCoder) { fatalError("init(coder:) は使わない") } + + /// ウィンドウを最前面に出す(無ければ生成済みのものを再利用)。呼ぶたびに入力欄を空にする。 + func show() { + pathField.stringValue = "" + errorLabel.stringValue = "" + showWindow(nil) + window?.makeKeyAndOrderFront(nil) + window?.makeFirstResponder(pathField) + NSApp.activate(ignoringOtherApps: true) + } + + private func buildLayout() { + guard let content = window?.contentView else { return } + + pathField.placeholderString = L("pathOpen.placeholder") + pathField.target = self + pathField.action = #selector(openTapped) + pathField.translatesAutoresizingMaskIntoConstraints = false + pathField.widthAnchor.constraint(greaterThanOrEqualToConstant: 340).isActive = true + + let openButton = NSButton(title: L("pathOpen.open"), target: self, action: #selector(openTapped)) + openButton.keyEquivalent = "\r" + + errorLabel.textColor = .systemRed + errorLabel.font = .systemFont(ofSize: 11) + + let row = NSStackView(views: [pathField, openButton]) + row.orientation = .horizontal + row.spacing = 8 + + let stack = NSStackView(views: [row, errorLabel]) + stack.orientation = .vertical + stack.alignment = .leading + stack.spacing = 6 + stack.translatesAutoresizingMaskIntoConstraints = false + content.addSubview(stack) + NSLayoutConstraint.activate([ + stack.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 20), + stack.trailingAnchor.constraint(lessThanOrEqualTo: content.trailingAnchor, constant: -20), + stack.topAnchor.constraint(equalTo: content.topAnchor, constant: 24), + ]) + } + + @objc private func openTapped() { + guard let url = Self.resolve(pathField.stringValue) else { + errorLabel.stringValue = L("pathOpen.notFound") + return + } + var isDir: ObjCBool = false + FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir) + if isDir.boolValue { + // 開く先が無いので、Finder で見せるところまでで済ませる。 + NSWorkspace.shared.activateFileViewerSelecting([url]) + } else { + openHandler(url) + } + window?.performClose(nil) + } + + /// 入力文字列を実在するファイル/フォルダの URL にする。無ければ nil。 + /// 前後の空白と、コピー時に付く外側の引用符(1 組だけ)を落とし、`~` を展開する。 + /// 相対パスは対応しない(GUI アプリの cwd は実用上 `/` になるだけで、基準にならない)。 + static func resolve(_ raw: String) -> URL? { + var s = raw.trimmingCharacters(in: .whitespacesAndNewlines) + if s.count >= 2 { + let pairs: [(Character, Character)] = [("\"", "\""), ("'", "'"), ("\u{201C}", "\u{201D}")] + for (open, close) in pairs where s.first == open && s.last == close { + s = String(s.dropFirst().dropLast()) + break + } + } + s = (s as NSString).expandingTildeInPath + guard !s.isEmpty, FileManager.default.fileExists(atPath: s) else { return nil } + return URL(fileURLWithPath: s) + } +} diff --git a/Tests/MrEditorTests/PathOpenWindowControllerTests.swift b/Tests/MrEditorTests/PathOpenWindowControllerTests.swift new file mode 100644 index 0000000..a63da69 --- /dev/null +++ b/Tests/MrEditorTests/PathOpenWindowControllerTests.swift @@ -0,0 +1,58 @@ +import XCTest +@testable import MrEditorCore + +/// パス解決(引用符除去・`~`展開・存在確認)の純粋な部分を検証する。 +final class PathOpenWindowControllerTests: XCTestCase { + + private var tempFile: URL! + private var tempDir: URL! + + override func setUpWithError() throws { + tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) + tempFile = tempDir.appendingPathComponent("a.txt") + try Data("hello".utf8).write(to: tempFile) + } + + override func tearDownWithError() throws { + try? FileManager.default.removeItem(at: tempDir) + } + + func testResolvesPlainExistingFile() { + XCTAssertEqual(PathOpenWindowController.resolve(tempFile.path), tempFile) + } + + func testTrimsSurroundingWhitespace() { + XCTAssertEqual(PathOpenWindowController.resolve(" \(tempFile.path) "), tempFile) + } + + func testStripsOneSurroundingPairOfStraightQuotes() { + XCTAssertEqual(PathOpenWindowController.resolve("\"\(tempFile.path)\""), tempFile) + } + + func testStripsOneSurroundingPairOfSingleQuotes() { + XCTAssertEqual(PathOpenWindowController.resolve("'\(tempFile.path)'"), tempFile) + } + + func testDoesNotStripUnbalancedQuote() { + XCTAssertNil(PathOpenWindowController.resolve("\"\(tempFile.path)")) + } + + func testExpandsTilde() { + let home = FileManager.default.homeDirectoryForCurrentUser + XCTAssertEqual(PathOpenWindowController.resolve("~"), home) + } + + func testResolvesExistingDirectory() { + // フォルダは `URL(fileURLWithPath:)` が末尾に `/` を付けるので、パス文字列で比べる。 + XCTAssertEqual(PathOpenWindowController.resolve(tempDir.path)?.path, tempDir.path) + } + + func testReturnsNilForMissingPath() { + XCTAssertNil(PathOpenWindowController.resolve(tempDir.appendingPathComponent("does-not-exist").path)) + } + + func testReturnsNilForEmptyInput() { + XCTAssertNil(PathOpenWindowController.resolve(" ")) + } +} diff --git a/scripts/make_app.sh b/scripts/make_app.sh index 99d8021..25c5ebc 100755 --- a/scripts/make_app.sh +++ b/scripts/make_app.sh @@ -17,7 +17,7 @@ BUNDLE_ID="${BUNDLE_ID:-com.aaedit.MrEditor}" # 実行ファイル名(SPM の executableTarget 名)。無料版=MrEditor / Pro 版=MrkEditor。 EXECUTABLE="${EXECUTABLE:-MrEditor}" # バージョン(Info.plist へ埋め込む)。make_dmg.sh と揃えるため VERSION で上書き可能。 -VERSION="${VERSION:-1.18.0}" +VERSION="${VERSION:-1.19.0}" ROOT="$(cd "$(dirname "$0")/.." && pwd)" ICON="${ICON:-$ROOT/art/AppIcon.icns}" diff --git a/scripts/make_dmg.sh b/scripts/make_dmg.sh index 05a4fda..0d4e377 100755 --- a/scripts/make_dmg.sh +++ b/scripts/make_dmg.sh @@ -38,7 +38,7 @@ set -e ROOT="$(cd "$(dirname "$0")/.." && pwd)" APP_NAME="${APP_NAME:-MrEditor}" -VERSION="${VERSION:-1.18.0}" +VERSION="${VERSION:-1.19.0}" APP="$ROOT/.build/$APP_NAME.app" DMG="$ROOT/.build/$APP_NAME-$VERSION.dmg" diff --git a/site/index.en.html b/site/index.en.html index 1c9cea2..77a9179 100644 --- a/site/index.en.html +++ b/site/index.en.html @@ -112,6 +112,19 @@ .feat li{list-style:none;padding-left:26px;position:relative;color:var(--fg);font-size:15px} .feat li::before{content:"";position:absolute;left:0;top:9px;width:8px;height:8px;border-radius:2px;background:var(--teal)} .feat li span{color:var(--mut);font-size:13.5px} + .pro-box{position:relative;overflow:hidden;background:linear-gradient(180deg,rgba(20,184,166,.12),rgba(16,34,38,.72)); + border:1px solid rgba(20,184,166,.34);border-radius:18px;padding:30px;margin-top:12px; + box-shadow:0 28px 80px rgba(0,0,0,.28)} + .pro-box::before{content:"";position:absolute;inset:0 0 auto 0;height:3px;background:linear-gradient(90deg,var(--teal),#5eead4,var(--amber))} + .pro-head{display:flex;justify-content:space-between;gap:24px;align-items:flex-start;margin-bottom:24px} + .pro-price{min-width:150px;border:1px solid rgba(20,184,166,.28);border-radius:14px;padding:14px 16px;background:rgba(5,16,18,.62); + text-align:right;color:var(--mut);font-size:13px} + .pro-price b{display:block;color:var(--fg);font-size:34px;line-height:1;font-weight:800;letter-spacing:-.5px} + .pro-actions{display:flex;gap:14px;flex-wrap:wrap;align-items:center;margin-top:22px} + .pro-actions .btn{font-size:16px;padding:15px 22px} + .pro-actions .buy{background:linear-gradient(180deg,#FACC15,var(--amber));color:#2b1700; + box-shadow:0 14px 34px rgba(245,158,11,.28)} + .pro-trial{color:var(--mut);font-size:13px;margin-top:12px;max-width:46em} footer{border-top:1px solid var(--line);padding:30px 0 50px;color:var(--mut);font-size:14px; display:flex;justify-content:space-between;flex-wrap:wrap;gap:12px} @@ -124,6 +137,8 @@ .stats{grid-template-columns:repeat(2,1fr)} .grid3{grid-template-columns:1fr} .feat{grid-template-columns:1fr} + .pro-head{display:block} + .pro-price{text-align:left;margin-top:18px} nav .ghlink{display:none} } @@ -160,7 +175,7 @@
- macOS 13+ · v1.18.0 · MIT + macOS 13+ · v1.19.0 · MIT

The editor for text
you didn't write.

Logs. CSVs. Exports, dumps, diffs. Text that arrived from somewhere else — so you don't get to choose its size, its encoding, or how badly it is formatted. MrEditor opens it, filters it, lines up its columns, compares it, fixes it, and saves it without ever leaving the file half-written.

@@ -169,7 +184,7 @@

The editor for text
you didn't write.

記録を返さないので事後に割れない。中継ページは CF がボットを弾いて数えてくれる。 版はここから ?v= で渡す(中継側に版を書くと、リリースのたびに直す場所が増える)。 言語ごとに URL が違うので data-only で出し分ける。 --> - ↓ Download .dmg + ↓ Download .dmg View source
@@ -295,10 +310,15 @@

Read it. Then change it.

-
-
Paid companion: MrkEditor
-

Free reads one file. MrkEditor answers the question.

-

Everything above stays free, and stays MIT. Sold separately, MrkEditor is the other half: counting, summarising, seeing time, and searching across folders — the things you currently leave the editor to do. Numbers below are measured on the shipping code (M4 Max, August 2026), not estimates.

+
+
+
+
Paid companion: MrkEditor
+

Free reads one file. MrkEditor answers the question.

+

Everything above stays free, and stays MIT. Sold separately, MrkEditor is the other half: counting, summarising, seeing time, and searching across folders — the things you currently leave the editor to do. Numbers below are measured on the shipping code (M4 Max, August 2026), not estimates.

+
+
$39one-time purchase
+
  • Count by value — what sort | uniq -c | sort -rn does, on a column or a key=value field. 3.40 s over a 10 GB, 86,420,337-line log. Double-click a row and the document filters down to that value
  • Column stats — every column of a CSV in one pass: type, blanks, distinct, min/max, sum & average. 4.78 s for all 30 columns of a 1.18 GiB CSV (5,816,534 data rows). A column is only called numeric when every row reads as a number
  • @@ -306,7 +326,12 @@

    Free reads one file. MrkEditor answers the question.

  • Search across folders — 3.14 s for one literal across a single 10 GB log, on par with ripgrep. What ripgrep does not do: click a hit and the file opens at that line, and Shift-JIS / EUC-JP files are detected per file and read correctly

MrkEditor is a one-time purchase through Polar. The free app keeps the same Analyze menu in the same place; choosing an item explains what MrkEditor adds.

-

Buy MrkEditor

+ +

No separate trial: the free MrEditor app uses the same editor core, so try your real files there before buying. MrkEditor adds the Analyze menu.