Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
61bb943
feat(source-edit): byte-faithful file format and in-place writer
b451c Oct 5, 2026
4b33d88
merge: Source Edit T1 - file format and writer
b451c Oct 5, 2026
1ed01ac
feat(source-edit): editor text helpers; file watcher re-arms after a …
b451c Oct 5, 2026
ca861e8
merge: Source Edit T2 - text helpers and watcher re-arm
b451c Oct 5, 2026
9208272
feat(source-edit): close and quit guard for windows with unsaved sour…
b451c Oct 5, 2026
6c51fb8
merge: Source Edit T3 - close and quit guard
b451c Oct 5, 2026
f8c8269
feat(source-edit): source editor surface (NSTextView, own undo, colum…
b451c Oct 5, 2026
bead12f
merge: feature/source-edit (T1-T3) into T5a base
b451c Oct 5, 2026
d7ee1d5
fix(source-edit): editor review fixes - undo grouping, own undo manag…
b451c Oct 5, 2026
839ecae
merge: Source Edit T4 - editor surface
b451c Oct 5, 2026
76020a3
feat(source-edit): edit session - enter, save, conflicts, leave, clos…
b451c Oct 5, 2026
ed909a7
merge: feature/source-edit (T4 fixes) into T5a
b451c Oct 5, 2026
b275ec7
fix(source-edit): session and editor review fixes
b451c Oct 5, 2026
2ee0534
merge: Source Edit T5a - edit session
b451c Oct 5, 2026
15e83df
fix(source-edit): rendered text recovers when an emptied file returns…
b451c Oct 5, 2026
7c05d81
merge: Source Edit T5a - round-2 fixes
b451c Oct 5, 2026
6ae9b22
feat(source-edit): wire Source Edit into the document window
b451c Oct 5, 2026
4f377d9
merge: feature/source-edit (T5a round 2) into T5b
b451c Oct 5, 2026
02b5b75
merge: Source Edit T5b - window wiring
b451c Oct 5, 2026
23b6f37
fix(source-edit): wiring review and E2E fixes; selection carries into…
b451c Oct 5, 2026
5e79d8b
merge: Source Edit T5b - review and E2E fixes
b451c Oct 5, 2026
ece961d
fix(source-edit): selection carry-over picks the selected occurrence;…
b451c Oct 5, 2026
5ef5aea
merge: Source Edit T5b - round-3 fixes
b451c Oct 5, 2026
b67d354
feat(source-edit): block-level tint in the source editor, driven by t…
b451c Oct 5, 2026
f63a09f
merge: Source Edit T6 - source tint
b451c Oct 5, 2026
a026ff9
fix(source-edit): tint scheduling follows every text change; maximum …
b451c Oct 5, 2026
de41f6f
merge: Source Edit T6 - tint review fixes
b451c Oct 5, 2026
9e39ed9
docs: Source Edit in the README and the changelog (unreleased)
b451c Oct 5, 2026
b26cc7e
ui(source-edit): one pencil - no separate Open in editor pill
b451c Oct 5, 2026
3b22194
docs(readme): shorter - history lives in the changelog, compact sourc…
b451c Oct 5, 2026
a1ef423
release: v1.12.0 (build 24)
b451c Oct 5, 2026
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
18 changes: 18 additions & 0 deletions QuickMD/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ All notable changes to QuickMD will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.12.0] - 2026-10-05

Source Edit: QuickMD stays a viewer first, but you can now fix the Markdown you are reading without leaving the window. Asked for by [@KillSwitchSIG](https://github.com/KillSwitchSIG) (#31) and [@vinaymve](https://github.com/vinaymve) (#34).

### Added
- **Source Edit** (⌥⌘E, the pencil button, or File ▸ Edit Source): the window switches to the raw Markdown at the line you are reading; a word selected in the rendered view is already selected in the source. ⌘S saves, Esc (or Done) returns to the rendered view at the place you edited.
- Saves are byte-faithful: the file keeps its encoding (UTF-8, UTF-8 with BOM, UTF-16, ISO Latin 1), its line endings, permissions, Finder tags, creation date and symlinks — only what you typed changes. A file with mixed line endings is unified to its dominant style, and QuickMD says so. A read-only file is not edited.
- Unsaved text is never lost silently: closing the tab, the window or the app asks Save / Don't Save / Cancel, and the close button shows the edited dot.
- If another application changes the file while you edit, QuickMD does not choose for you: with no unsaved changes the editor follows the disk; otherwise a banner offers Keep My Version or Load Disk Version (undoable), and Save asks before replacing the other change.
- In the editor: undo and redo, the system find and replace bar (⌘F, ⌘G), indentation kept on Return, Tab / ⇧Tab to indent and outdent, a light syntax tint for headings, code blocks and quotes (documents up to about 100 KB), zoom and Reading Mode. Smart quotes, smart dashes and autocorrect are off.

### Changed
- The pencil button now starts Source Edit. Open in External Editor stays on ⌘E and in the File menu.
- Documents larger than 2 MB are not edited in place (QuickMD says so; ⌘E still opens them in your editor).

### Fixed
- Auto-reload recovers when a file that disappeared comes back at the same path (git checkout, stash pop). Before, the tab stayed on "File no longer exists" until the document was reopened.

## [1.11.0] - 2026-10-01

Selection, images and the editor handoff, shaped by your reports: text can now be selected and copied across the whole document ([@ssstraub](https://github.com/ssstraub), #33), embedded and HTML images render ([@filmo](https://github.com/filmo), #32), and ⌘E opens your editor right where you are reading (an answer to [@KillSwitchSIG](https://github.com/KillSwitchSIG)'s #31). Thank you!
Expand Down
92 changes: 88 additions & 4 deletions QuickMD/QuickMD.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,31 @@
A1B2C3D401000110 /* DocumentReadingPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000110 /* DocumentReadingPosition.swift */; };
A1B2C3D401000111 /* DocumentReadingPosition.swift in Sources (Tests) */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000110 /* DocumentReadingPosition.swift */; };
A1B2C3D401000112 /* ExternalEditorLineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000111 /* ExternalEditorLineTests.swift */; };
A1B2C3D401000120 /* DocumentFileFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000120 /* DocumentFileFormat.swift */; };
A1B2C3D401000121 /* DocumentFileFormat.swift in Sources (Tests) */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000120 /* DocumentFileFormat.swift */; };
A1B2C3D401000122 /* DocumentFileWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000121 /* DocumentFileWriter.swift */; };
A1B2C3D401000123 /* DocumentFileWriter.swift in Sources (Tests) */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000121 /* DocumentFileWriter.swift */; };
A1B2C3D401000124 /* DocumentFileFormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000122 /* DocumentFileFormatTests.swift */; };
A1B2C3D401000125 /* DocumentFileWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000123 /* DocumentFileWriterTests.swift */; };
A1B2C3D401000130 /* SourceEditSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000130 /* SourceEditSupport.swift */; };
A1B2C3D401000131 /* SourceEditSupport.swift in Sources (Tests) */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000130 /* SourceEditSupport.swift */; };
A1B2C3D401000132 /* SourceEditSupportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000131 /* SourceEditSupportTests.swift */; };
A1B2C3D401000150 /* SourceEditorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000150 /* SourceEditorController.swift */; };
A1B2C3D401000151 /* SourceEditorController.swift in Sources (Tests) */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000150 /* SourceEditorController.swift */; };
A1B2C3D401000170 /* SourceTint.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000170 /* SourceTint.swift */; };
A1B2C3D401000171 /* SourceTint.swift in Sources (Tests) */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000170 /* SourceTint.swift */; };
A1B2C3D401000172 /* SourceTintTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000171 /* SourceTintTests.swift */; };
A1B2C3D401000152 /* SourceEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000151 /* SourceEditorView.swift */; };
A1B2C3D401000153 /* SourceEditorControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000152 /* SourceEditorControllerTests.swift */; };
A1B2C3D401000140 /* EditCloseGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000140 /* EditCloseGuard.swift */; };
A1B2C3D401000141 /* EditCloseGuard.swift in Sources (Tests) */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000140 /* EditCloseGuard.swift */; };
A1B2C3D401000142 /* EditCloseGuardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000141 /* EditCloseGuardTests.swift */; };
A1B2C3D401000160 /* SourceEditSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000160 /* SourceEditSession.swift */; };
A1B2C3D401000161 /* SourceEditSession.swift in Sources (Tests) */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000160 /* SourceEditSession.swift */; };
A1B2C3D401000162 /* SourceEditSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000161 /* SourceEditSessionTests.swift */; };
A1B2C3D401000180 /* SourceEditLanding.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000180 /* SourceEditLanding.swift */; };
A1B2C3D401000181 /* SourceEditLanding.swift in Sources (Tests) */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000180 /* SourceEditLanding.swift */; };
A1B2C3D401000182 /* SourceEditLandingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D400000181 /* SourceEditLandingTests.swift */; };
A1B2C3D4010000F0 /* HeadingLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4000000F0 /* HeadingLayoutTests.swift */; };
/* End PBXBuildFile section */

Expand All @@ -142,6 +167,23 @@
A1B2C3D4000000C1 /* DocumentSelectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentSelectionTests.swift; sourceTree = "<group>"; };
A1B2C3D400000110 /* DocumentReadingPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentReadingPosition.swift; sourceTree = "<group>"; };
A1B2C3D400000111 /* ExternalEditorLineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalEditorLineTests.swift; sourceTree = "<group>"; };
A1B2C3D400000120 /* DocumentFileFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentFileFormat.swift; sourceTree = "<group>"; };
A1B2C3D400000121 /* DocumentFileWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentFileWriter.swift; sourceTree = "<group>"; };
A1B2C3D400000122 /* DocumentFileFormatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentFileFormatTests.swift; sourceTree = "<group>"; };
A1B2C3D400000123 /* DocumentFileWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentFileWriterTests.swift; sourceTree = "<group>"; };
A1B2C3D400000130 /* SourceEditSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditSupport.swift; sourceTree = "<group>"; };
A1B2C3D400000131 /* SourceEditSupportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditSupportTests.swift; sourceTree = "<group>"; };
A1B2C3D400000150 /* SourceEditorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditorController.swift; sourceTree = "<group>"; };
A1B2C3D400000170 /* SourceTint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceTint.swift; sourceTree = "<group>"; };
A1B2C3D400000171 /* SourceTintTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceTintTests.swift; sourceTree = "<group>"; };
A1B2C3D400000151 /* SourceEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditorView.swift; sourceTree = "<group>"; };
A1B2C3D400000152 /* SourceEditorControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditorControllerTests.swift; sourceTree = "<group>"; };
A1B2C3D400000140 /* EditCloseGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCloseGuard.swift; sourceTree = "<group>"; };
A1B2C3D400000141 /* EditCloseGuardTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCloseGuardTests.swift; sourceTree = "<group>"; };
A1B2C3D400000160 /* SourceEditSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditSession.swift; sourceTree = "<group>"; };
A1B2C3D400000161 /* SourceEditSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditSessionTests.swift; sourceTree = "<group>"; };
A1B2C3D400000180 /* SourceEditLanding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditLanding.swift; sourceTree = "<group>"; };
A1B2C3D400000181 /* SourceEditLandingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditLandingTests.swift; sourceTree = "<group>"; };
A1B2C3D4000000F0 /* HeadingLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadingLayoutTests.swift; sourceTree = "<group>"; };
A1B2C3D400000001 /* QuickMDApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickMDApp.swift; sourceTree = "<group>"; };
A1B2C3D400000002 /* MarkdownDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownDocument.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -270,6 +312,14 @@
A1B2C3D4000000F0 /* HeadingLayoutTests.swift */,
A1B2C3D4000000C1 /* DocumentSelectionTests.swift */,
A1B2C3D400000111 /* ExternalEditorLineTests.swift */,
A1B2C3D400000122 /* DocumentFileFormatTests.swift */,
A1B2C3D400000123 /* DocumentFileWriterTests.swift */,
A1B2C3D400000131 /* SourceEditSupportTests.swift */,
A1B2C3D400000152 /* SourceEditorControllerTests.swift */,
A1B2C3D400000171 /* SourceTintTests.swift */,
A1B2C3D400000141 /* EditCloseGuardTests.swift */,
A1B2C3D400000161 /* SourceEditSessionTests.swift */,
A1B2C3D400000181 /* SourceEditLandingTests.swift */,
);
path = QuickMDTests;
sourceTree = "<group>";
Expand All @@ -290,6 +340,14 @@
A1B2C3D400000081 /* ImageLoader.swift */,
A1B2C3D4000000C0 /* DocumentSelection.swift */,
A1B2C3D400000110 /* DocumentReadingPosition.swift */,
A1B2C3D400000120 /* DocumentFileFormat.swift */,
A1B2C3D400000121 /* DocumentFileWriter.swift */,
A1B2C3D400000130 /* SourceEditSupport.swift */,
A1B2C3D400000150 /* SourceEditorController.swift */,
A1B2C3D400000170 /* SourceTint.swift */,
A1B2C3D400000140 /* EditCloseGuard.swift */,
A1B2C3D400000160 /* SourceEditSession.swift */,
A1B2C3D400000180 /* SourceEditLanding.swift */,
A1B2C3D40000003D /* SectionExtractor.swift */,
A1B2C3D40000003E /* DocumentSearch.swift */,
A1B2C3D40000003F /* WindowTabbing.swift */,
Expand Down Expand Up @@ -339,6 +397,7 @@
A1B2C3D400000063 /* FontPickerView.swift */,
A1B2C3D40000006C /* VirtualBlockList.swift */,
A1B2C3D40000006D /* SVGBlockView.swift */,
A1B2C3D400000151 /* SourceEditorView.swift */,
);
path = Views;
sourceTree = "<group>";
Expand Down Expand Up @@ -573,6 +632,15 @@
A1B2C3D401000082 /* ImageLoader.swift in Sources */,
A1B2C3D4010000C0 /* DocumentSelection.swift in Sources */,
A1B2C3D401000110 /* DocumentReadingPosition.swift in Sources */,
A1B2C3D401000120 /* DocumentFileFormat.swift in Sources */,
A1B2C3D401000122 /* DocumentFileWriter.swift in Sources */,
A1B2C3D401000130 /* SourceEditSupport.swift in Sources */,
A1B2C3D401000150 /* SourceEditorController.swift in Sources */,
A1B2C3D401000170 /* SourceTint.swift in Sources */,
A1B2C3D401000152 /* SourceEditorView.swift in Sources */,
A1B2C3D401000140 /* EditCloseGuard.swift in Sources */,
A1B2C3D401000160 /* SourceEditSession.swift in Sources */,
A1B2C3D401000180 /* SourceEditLanding.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -614,6 +682,22 @@
A1B2C3D4010000C2 /* DocumentSelectionTests.swift in Sources */,
A1B2C3D401000111 /* DocumentReadingPosition.swift in Sources (Tests) */,
A1B2C3D401000112 /* ExternalEditorLineTests.swift in Sources */,
A1B2C3D401000121 /* DocumentFileFormat.swift in Sources (Tests) */,
A1B2C3D401000123 /* DocumentFileWriter.swift in Sources (Tests) */,
A1B2C3D401000124 /* DocumentFileFormatTests.swift in Sources */,
A1B2C3D401000125 /* DocumentFileWriterTests.swift in Sources */,
A1B2C3D401000131 /* SourceEditSupport.swift in Sources (Tests) */,
A1B2C3D401000132 /* SourceEditSupportTests.swift in Sources */,
A1B2C3D401000151 /* SourceEditorController.swift in Sources (Tests) */,
A1B2C3D401000171 /* SourceTint.swift in Sources (Tests) */,
A1B2C3D401000153 /* SourceEditorControllerTests.swift in Sources */,
A1B2C3D401000172 /* SourceTintTests.swift in Sources */,
A1B2C3D401000141 /* EditCloseGuard.swift in Sources (Tests) */,
A1B2C3D401000142 /* EditCloseGuardTests.swift in Sources */,
A1B2C3D401000161 /* SourceEditSession.swift in Sources (Tests) */,
A1B2C3D401000162 /* SourceEditSessionTests.swift in Sources */,
A1B2C3D401000181 /* SourceEditLanding.swift in Sources (Tests) */,
A1B2C3D401000182 /* SourceEditLandingTests.swift in Sources */,
A1B2C3D4010000F0 /* HeadingLayoutTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -753,7 +837,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 23;
CURRENT_PROJECT_VERSION = 24;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = VJ92852M32;
ENABLE_APP_SANDBOX = YES;
Expand All @@ -778,7 +862,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.11.0;
MARKETING_VERSION = 1.12.0;
PRODUCT_BUNDLE_IDENTIFIER = pl.falami.studio.QuickMD;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand All @@ -797,7 +881,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 23;
CURRENT_PROJECT_VERSION = 24;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = VJ92852M32;
ENABLE_APP_SANDBOX = YES;
Expand All @@ -822,7 +906,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.11.0;
MARKETING_VERSION = 1.12.0;
PRODUCT_BUNDLE_IDENTIFIER = pl.falami.studio.QuickMD;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
Loading
Loading