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
32 changes: 25 additions & 7 deletions Sources/MrEditorCore/MainWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1256,14 +1256,24 @@ public final class MainWindowController: NSWindowController, NSWindowDelegate {
persistSession()
}

/// 読み取り専用バナーの表示可否を更新する(編集不可のペイン=LargeFileViewer のときだけ出す)。
/// 読み取り専用バナーの表示可否・文面を更新する。
/// **理由によって文面を変える**──フィルタ中や JSON クエリ中の小さなファイルにまで
/// 「大きすぎる」を出すと、その場で嘘になってバグに見える(本人の指摘、2026-09-24)。
private func updateReadOnlyBanner() {
// 構造化表示による読み取り専用は専用バナーで案内するため除外する。
// diff も除外する(「大きすぎて編集できません」は嘘。そもそも編集する画面ではない)。
let isReadOnly = activeViewer != nil && !(activeViewer?.canEdit ?? false)
&& activeViewer?.structuredMode == nil
&& !(activeViewer is DiffViewer)
readOnlyBanner.isHidden = !(isReadOnly && !readOnlyBannerDismissed)
guard let v = activeViewer, !v.canEdit, v.structuredMode == nil, !(v is DiffViewer) else {
readOnlyBanner.isHidden = true
return
}
if v.filterMatchLines != nil {
readOnlyBanner.reason = .filtered
} else if v.jsonQueryIsActive {
readOnlyBanner.reason = .jsonQuery
} else {
readOnlyBanner.reason = .tooLarge
}
readOnlyBanner.isHidden = readOnlyBannerDismissed
}

/// アクティブなドキュメントを閉じる(なければウィンドウを閉じる)。未保存なら確認する。
Expand Down Expand Up @@ -1664,11 +1674,15 @@ public final class MainWindowController: NSWindowController, NSWindowDelegate {

func showSearch() {
guard let v = activeViewer, v.supportsSearch else { NSSound.beep(); return }
refreshSearchBarCapabilities()
searchBar.isHidden = false
// 前に自分で漏斗を入れていたなら、その状態で開く。構造化されたものを読むとき
// 主目的は「絞る」ほうで、そこへ毎回 1 手かけ直すのが B2 の詰まりだった。
// **置換の可否を引き直すより先に当てる**── 順序が逆だと、フィルタ適用前の
// 「置換できる」判定のまま置換ボタンが有効に残り、押すと canEdit のガードで
// 問答無用にビープする(フィルタ中に編集を許さないのは正しいが、ボタンだけ
// 古い状態のままになる不具合だった)。
applyRememberedFilter(to: v)
refreshSearchBarCapabilities()
searchBar.focusField()
}

Expand All @@ -1691,6 +1705,8 @@ public final class MainWindowController: NSWindowController, NSWindowDelegate {
// 漏斗が使えるペインへ戻ってきたら、覚えている意図をもう一度当てる。
// 検索バーが出ている間だけ ── 閉じているのに本文が絞られるのは事故に見える。
if !searchBar.isHidden { applyRememberedFilter(to: v) }
// フィルタで置換が落ちたのが**なぜか**を言う(本人の指摘: 黙って落とすとバグに見える)。
updateReadOnlyBanner()
}

/// 一致行の前後に出す行数を変える(検索バーの「±」欄・メニューの増減)。
Expand Down Expand Up @@ -1778,6 +1794,7 @@ public final class MainWindowController: NSWindowController, NSWindowDelegate {
v.setCaseSensitive(true)
v.setSearchQuery(value)
v.setFilterMode(true)
refreshSearchBarCapabilities()
}

/// 時間帯で選んだ行だけを表示する(時間分布のドラッグ選択)。行は **0 始まり・昇順**。
Expand All @@ -1804,6 +1821,7 @@ public final class MainWindowController: NSWindowController, NSWindowDelegate {
v.focusContent()
}
searchBar.clear()
updateReadOnlyBanner() // フィルタを解いたので、それで出ていたバナーも引っ込める
}

// MARK: - 開く
Expand Down Expand Up @@ -1937,8 +1955,8 @@ extension MainWindowController: NSToolbarItemValidation, NSMenuDelegate {
guard let v = activeViewer, v.supportsSearch, v.supportsSearchFilter else { NSSound.beep(); return }
showSearch()
searchBar.setFilterOn(true)
refreshSearchBarCapabilities()
v.setFilterMode(true)
refreshSearchBarCapabilities()
}

@objc func toolbarSetStructuredMode(_ sender: Any?) {
Expand Down
2 changes: 2 additions & 0 deletions Sources/MrEditorCore/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
"doc.untitled" = "Untitled";
"sidebar.close" = "Close";
"readonly.banner" = "Read-only — this file is too large to edit.";
"readonly.banner.filter" = "Read-only — can't edit while showing matching lines only (filter).";
"readonly.banner.jsonQuery" = "Read-only — can't edit while the JSON query is open.";

/* Status bar */
"status.placeholder" = "Drag a file here, or press ⌘O to open";
Expand Down
2 changes: 2 additions & 0 deletions Sources/MrEditorCore/Resources/ja.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
"doc.untitled" = "名称未設定";
"sidebar.close" = "閉じる";
"readonly.banner" = "読み取り専用 — このファイルは大きすぎて編集できません。";
"readonly.banner.filter" = "読み取り専用 — 一致行だけ表示(フィルタ)中は編集できません。";
"readonly.banner.jsonQuery" = "読み取り専用 — JSON その場クエリ中は編集できません。";

/* ステータスバー */
"status.placeholder" = "ファイルをドラッグ、または ⌘O で開く";
Expand Down
24 changes: 23 additions & 1 deletion Sources/MrEditorCore/UI/ReadOnlyBanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,29 @@ final class ReadOnlyBanner: NSView {

static let height: CGFloat = 26

private let label = NSTextField(labelWithString: L("readonly.banner"))

/// なぜ編集できないか。**理由によって文面を変える**──「大きすぎる」を、フィルタ中や
/// JSON その場クエリ中の小さなファイルにまで出すと、その場で嘘になって
/// 「バグでは」と誤解される(本人の指摘)。
enum Reason {
case tooLarge
case filtered
case jsonQuery

var messageKey: String {
switch self {
case .tooLarge: return "readonly.banner"
case .filtered: return "readonly.banner.filter"
case .jsonQuery: return "readonly.banner.jsonQuery"
}
}
}

var reason: Reason = .tooLarge {
didSet { label.stringValue = L(reason.messageKey) }
}

override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
setup()
Expand All @@ -30,7 +53,6 @@ final class ReadOnlyBanner: NSView {
icon.contentTintColor = .secondaryLabelColor
icon.translatesAutoresizingMaskIntoConstraints = false

let label = NSTextField(labelWithString: L("readonly.banner"))
label.font = .systemFont(ofSize: 11)
label.textColor = .secondaryLabelColor
label.translatesAutoresizingMaskIntoConstraints = false
Expand Down
Loading