Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
71dc615
Bump version to 0.3.4
albert-gee Sep 1, 2026
d19fd6a
Use accent color for About version tag
albert-gee Sep 1, 2026
ea8863f
Added shared block-command capability checks enforced by both toolbar…
albert-gee Sep 4, 2026
ba4c92b
Real no-wrap layout with horizontal scrolling, scrollbar, caret revea…
albert-gee Sep 4, 2026
fc45f17
Nested list-to-blockquote/admonition conversions now perform an expli…
albert-gee Sep 4, 2026
9b75c68
Added cancellable search replacement using an isolate, truncated matc…
albert-gee Sep 4, 2026
1e2db8e
Add indentation handling for non-collapsed selections in source_comma…
albert-gee Sep 4, 2026
34c1e99
Source selections now use the tallest styled glyph on each line inste…
albert-gee Sep 4, 2026
e3dc3ab
Removed unnecessary `Center` widget wrapping `FittedBox` in `visualiz…
albert-gee Sep 4, 2026
3d227c6
Add support for responsive raster scaling with `pixelRatio` across re…
albert-gee Sep 4, 2026
1885ad7
Add test for OpenAPI fence rendering in Markdown, update fixture for …
albert-gee Sep 4, 2026
dbb62f4
Preserve nested list children on backspace merge
albert-gee Sep 4, 2026
fbc527c
Preserve table cell formatting during edits
albert-gee Sep 4, 2026
c233bc3
Preserve zero-width inline images during edits
albert-gee Sep 4, 2026
6b346de
Preserve escaped Markdown literals after edits
albert-gee Sep 4, 2026
bf1386a
Preserve hard breaks during WYSIWYG typing
albert-gee Sep 4, 2026
b01893e
Open sibling links from single-file Reading view
albert-gee Sep 4, 2026
494765e
Follow linked images in Reading view
albert-gee Sep 4, 2026
28e4d67
Render Markdown table alignment
albert-gee Sep 4, 2026
65017af
Protect Source IME composition from shortcuts
albert-gee Sep 5, 2026
2c12dcc
Group Source undo with caret state
albert-gee Sep 5, 2026
a8baedf
Fix multiline XML folding scanner
albert-gee Sep 5, 2026
c30a54b
Avoid redundant Source session updates
albert-gee Sep 5, 2026
6c346c6
Defer hidden Source preview rebuilds
albert-gee Sep 5, 2026
e485777
Avoid duplicate Source width layouts
albert-gee Sep 5, 2026
5049384
Show inactive Source search results accurately
albert-gee Sep 5, 2026
3468842
Preserve inline semantics in structural edits
albert-gee Sep 5, 2026
83b5256
Pin Flutter toolchain for reproducible CI
albert-gee Sep 5, 2026
0b75487
Keep Source undo groups unique across remounts
albert-gee Sep 5, 2026
f861226
Use pinned Flutter for local Snap builds
albert-gee Sep 5, 2026
02f986e
Harden CI against transient Snap provisioning failures
albert-gee Sep 5, 2026
9533856
Restore live Source outlines in source-only mode
albert-gee Sep 5, 2026
c72971c
Keep local Snap compiler output off tmpfs
albert-gee Sep 5, 2026
0253e70
Remove outdated screenshot.
albert-gee Sep 5, 2026
06a9c39
Fix responsive raster smoke assertion
albert-gee Sep 5, 2026
339db2e
Pin Flutter in Snapcraft builds
albert-gee Sep 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
29 changes: 23 additions & 6 deletions .github/workflows/flutter-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version: '3.47.0'
flutter-version-file: pubspec.yaml
cache: true

- name: Enable Linux desktop
run: flutter config --enable-linux-desktop

- name: Resolve dependencies
run: flutter pub get
- name: Resolve locked dependencies
run: flutter pub get --enforce-lockfile

- name: Generate localizations
run: flutter gen-l10n
Expand Down Expand Up @@ -166,15 +166,32 @@ jobs:
- name: Check out repository
uses: actions/checkout@v7

- name: Build strict Snap from a clean core24 environment
- name: Build strict Snap from a clean core24 environment (attempt 1)
id: snapcraft
uses: snapcore/action-build@v1
continue-on-error: true

- name: Retry strict Snap build after an infrastructure failure
id: snapcraft-retry
if: steps.snapcraft.outcome == 'failure'
uses: snapcore/action-build@v1

- name: Select built Snap
id: snap-artifact
env:
PRIMARY_SNAP: ${{ steps.snapcraft.outputs.snap }}
RETRY_SNAP: ${{ steps.snapcraft-retry.outputs.snap }}
run: |
snap_path="${PRIMARY_SNAP:-$RETRY_SNAP}"
test -n "$snap_path"
test -f "$snap_path"
echo "snap=$snap_path" >> "$GITHUB_OUTPUT"

- name: Upload Snap artifact
uses: actions/upload-artifact@v7
with:
name: busymark-snap
path: ${{ steps.snapcraft.outputs.snap }}
path: ${{ steps.snap-artifact.outputs.snap }}

- name: Install desktop smoke dependencies
run: |
Expand All @@ -184,7 +201,7 @@ jobs:
- name: Install strict Snap
run: >-
sudo snap install --dangerous
"${{ steps.snapcraft.outputs.snap }}"
"${{ steps.snap-artifact.outputs.snap }}"

- name: Verify GTK SVG icon loader
run: |
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ references. An interactive exercise is available in

## Run From Source

1. [Install Flutter](https://docs.flutter.dev/install)
1. [Install Flutter 3.47.0](https://docs.flutter.dev/install). The exact
project version is declared in `pubspec.yaml` and shared with CI.

2. Run the application:

Expand Down
Binary file removed docs/screenshots/busymark-welcome.png
Binary file not shown.
2 changes: 1 addition & 1 deletion lib/src/app/app_metadata.dart
Original file line number Diff line number Diff line change
@@ -1 +1 @@
const busyMarkAppVersion = '0.3.3';
const busyMarkAppVersion = '0.3.4';
12 changes: 7 additions & 5 deletions lib/src/app/busymark_dialogs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2657,13 +2657,15 @@ class _AboutVersionTag extends StatelessWidget {

@override
Widget build(BuildContext context) {
final colors = BusyMarkSurfaceColors.of(context);
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Center(
child: DecoratedBox(
key: const ValueKey('about-version-tag'),
decoration: BoxDecoration(
color: colors.control,
color: colorScheme.primary,
borderRadius: BorderRadius.circular(BusyMarkRadius.pill),
border: Border.all(color: colors.subtleBorder),
border: Border.all(color: colorScheme.primary),
),
child: Padding(
padding: const EdgeInsets.symmetric(
Expand All @@ -2675,8 +2677,8 @@ class _AboutVersionTag extends StatelessWidget {
child: Text(
version,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: colors.foreground,
style: theme.textTheme.labelMedium?.copyWith(
color: colorScheme.onPrimary,
fontWeight: FontWeight.w600,
),
),
Expand Down
29 changes: 20 additions & 9 deletions lib/src/editor/document_collapsible.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class BusyMarkDocumentCollapsible extends StatefulWidget {
required this.child,
required this.kindLabel,
this.initiallyExpanded = false,
this.expanded,
this.onExpansionChanged,
this.framed = false,
this.toggleOnHeaderTap = true,
this.margin = const EdgeInsets.symmetric(vertical: BusyMarkSpacing.xs),
Expand All @@ -21,6 +23,8 @@ class BusyMarkDocumentCollapsible extends StatefulWidget {
final Widget child;
final String kindLabel;
final bool initiallyExpanded;
final bool? expanded;
final ValueChanged<bool>? onExpansionChanged;
final bool framed;
final bool toggleOnHeaderTap;
final EdgeInsetsGeometry margin;
Expand All @@ -34,29 +38,36 @@ class _BusyMarkDocumentCollapsibleState
extends State<BusyMarkDocumentCollapsible> {
late bool _expanded = widget.initiallyExpanded;

bool get _effectiveExpanded => widget.expanded ?? _expanded;

@override
void didUpdateWidget(BusyMarkDocumentCollapsible oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.initiallyExpanded != widget.initiallyExpanded) {
if (widget.expanded == null &&
oldWidget.initiallyExpanded != widget.initiallyExpanded) {
_expanded = widget.initiallyExpanded;
}
}

void _toggle() => setState(() => _expanded = !_expanded);
void _toggle() {
final next = !_effectiveExpanded;
widget.onExpansionChanged?.call(next);
if (widget.expanded == null) {
setState(() => _expanded = next);
}
}

@override
Widget build(BuildContext context) {
final colors = BusyMarkSurfaceColors.of(context);
final tooltip = _expanded
final expanded = _effectiveExpanded;
final tooltip = expanded
? context.l10n.collapseKind(widget.kindLabel)
: context.l10n.expandKind(widget.kindLabel);
final content = Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
_header(context, colors, tooltip),
if (_expanded) widget.child,
],
children: [_header(context, colors, tooltip), if (expanded) widget.child],
);
if (!widget.framed) {
return Padding(padding: widget.margin, child: content);
Expand All @@ -80,7 +91,7 @@ class _BusyMarkDocumentCollapsibleState
BusyMarkSurfaceColors colors,
String tooltip,
) {
final icon = _expanded
final icon = _effectiveExpanded
? BusyMarkGlyphs.downArrow
: BusyMarkGlyphs.collapsedTreeArrowFor(Directionality.of(context));
final row = Padding(
Expand Down Expand Up @@ -118,7 +129,7 @@ class _BusyMarkDocumentCollapsibleState
}
return Semantics(
button: true,
expanded: _expanded,
expanded: _effectiveExpanded,
label: tooltip,
child: InkWell(
onTap: _toggle,
Expand Down
7 changes: 6 additions & 1 deletion lib/src/editor/document_text_geometry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ abstract final class BusyMarkDocumentTextGeometry {
editableCaretGap + editableCursorWidth;

/// Uses the complete text strut so selection has balanced breathing room
/// above capitals and below descenders in both Source and Editor views.
/// above capitals and below descenders in rich Editor fields.
static const BoxHeightStyle selectionHeightStyle = BoxHeightStyle.strut;

/// Follows the tallest styled glyph on each Source line. Source syntax can
/// enlarge headings beyond the base monospace strut, so a strut-sized box
/// can clip the lower edge of selected heading text.
static const BoxHeightStyle sourceSelectionHeightStyle = BoxHeightStyle.max;
static const BoxWidthStyle selectionWidthStyle = BoxWidthStyle.tight;
static const double fallbackSelectionAlpha = 0.40;
}
3 changes: 3 additions & 0 deletions lib/src/editor/source/source_commands.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ abstract final class SourceCommands {
TextEditingValue value, {
int indentWidth = defaultIndentWidth,
}) {
if (!_normalizedSelection(value).isCollapsed) {
return indentSelection(value, indentWidth: indentWidth);
}
return _replaceSelection(value, ' ' * indentWidth);
}

Expand Down
54 changes: 46 additions & 8 deletions lib/src/editor/source/source_document.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,41 @@ class SourceDocument {
visibleLineIndex = SourceLineIndex(visibleText);
}

SourceDocument._({
required this.fullText,
required this.hiddenRanges,
required this.lineIndex,
required this.visibleText,
required this.visibleLineIndex,
});

factory SourceDocument.afterVisibleEdit({
required SourceDocument previous,
required String fullText,
required SourceHiddenRanges hiddenRanges,
required SourceVisibleEdit edit,
}) {
final visibleText = hiddenRanges.visibleTextFor(fullText);
final visibleChange = _changedRange(previous.visibleText, visibleText);
return SourceDocument._(
fullText: fullText,
hiddenRanges: hiddenRanges,
lineIndex: SourceLineIndex.updated(
previous: previous.lineIndex,
source: fullText,
oldStart: edit.fullStart,
oldEnd: edit.fullEnd,
),
visibleText: visibleText,
visibleLineIndex: SourceLineIndex.updated(
previous: previous.visibleLineIndex,
source: visibleText,
oldStart: visibleChange.oldStart,
oldEnd: visibleChange.oldEnd,
),
);
}

final String fullText;
final SourceHiddenRanges hiddenRanges;
final SourceLineIndex lineIndex;
Expand Down Expand Up @@ -88,15 +123,18 @@ class SourceDocument {
affinity: selection.affinity,
);
}
final fullStart = visibleOffsetToFullOffset(
selection.start,
affinity: SourceHiddenAffinity.downstream,
);
final fullEnd = visibleOffsetToFullOffset(
selection.end,
affinity: SourceHiddenAffinity.upstream,
);
final forward = selection.baseOffset <= selection.extentOffset;
return selection.copyWith(
baseOffset: visibleOffsetToFullOffset(
selection.baseOffset,
affinity: SourceHiddenAffinity.downstream,
),
extentOffset: visibleOffsetToFullOffset(
selection.extentOffset,
affinity: SourceHiddenAffinity.upstream,
),
baseOffset: forward ? fullStart : fullEnd,
extentOffset: forward ? fullEnd : fullStart,
);
}

Expand Down
Loading
Loading