Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3af13af
Add markdown preview pane with per-result content types
TrueCrimeDev Jun 12, 2026
ea78b53
refactor: replace preview Hidden content type with PreviewVisibility …
TrueCrimeDev Jun 29, 2026
38c27fd
feat: make the markdown code-highlight theme configurable with an Aut…
TrueCrimeDev Jun 29, 2026
1504177
fix: restore forced preview pane when the main window reopens
TrueCrimeDev Jun 29, 2026
20c7c62
fix: reserve space for code-block horizontal scrollbar
TrueCrimeDev Jun 29, 2026
7092ad2
fix: theme the markdown preview scrollbars
TrueCrimeDev Jun 29, 2026
a74b87a
docs: add markdown preview pane screenshot
TrueCrimeDev Jun 29, 2026
fc0b8e9
fix: theme the code-block scrollbars
TrueCrimeDev Jun 29, 2026
1ade524
fix: give code blocks an explicit themed scrollbar template
TrueCrimeDev Jun 29, 2026
40d2220
fix: close unclosed Grid in code-block scrollbar template
TrueCrimeDev Jun 29, 2026
8670a61
docs: add proof render of the themed preview scrollbars
TrueCrimeDev Jun 29, 2026
945d5ec
fix: make the preview scrollbars subtle
TrueCrimeDev Jun 29, 2026
560c380
Merge branch 'dev' into upstream-markdown-preview
TrueCrimeDev Jun 29, 2026
7ba1dbd
Add base style for a horizontal scrollbar
DavidGBrett Jul 5, 2026
e7ae6d7
Correctly use existing themed scrollbars in markdown preview
DavidGBrett Jul 5, 2026
4ffa212
fix: address reviewer feedback on markdown preview pane
TrueCrimeDev Jul 5, 2026
d4789b1
Merge branch 'dev' into upstream-markdown-preview
TrueCrimeDev Jul 5, 2026
8636fb2
fix: address maintainer follow-up on markdown preview pane
TrueCrimeDev Jul 5, 2026
b0656cd
Add styling for the horizontal scrollbar to the built in themes
DavidGBrett Jul 6, 2026
57d5940
Update docstrings in Result to more accurately reflect how the differ…
DavidGBrett Jul 6, 2026
61cfd30
Make hyperlinks clickable in markdown preview
DavidGBrett Jul 6, 2026
4a3a1c2
Only intercept code-block navigation keys in OnKeyDown
DavidGBrett Jul 11, 2026
99180dd
Simplify preview visibility logic
DavidGBrett Jul 11, 2026
f826c36
Await async preview methods in MainViewModel, fire-and-forget only at…
DavidGBrett Jul 12, 2026
6db5575
Move preview MaxHeight to parent Grid, bind to ResultListBox.ActualHe…
DavidGBrett Jul 14, 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
15 changes: 15 additions & 0 deletions Flow.Launcher.Infrastructure/UserSettings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,12 @@ public DoublePinyinSchemas DoublePinyinSchema

public bool AlwaysPreview { get; set; } = false;

/// <summary>
/// Name of the syntax-highlighting theme used for code blocks in the markdown preview.
/// "Auto" follows the app colour scheme (light/dark); otherwise a <see cref="CodeHighlightThemes"/> name.
/// </summary>
public string CodeHighlightTheme { get; set; } = "Auto";

public bool AlwaysStartEn { get; set; } = false;

private SearchPrecisionScore _querySearchPrecision = SearchPrecisionScore.Regular;
Expand Down Expand Up @@ -696,6 +702,15 @@ public enum ColorSchemes
Dark
}

public enum CodeHighlightThemes
{
Auto,
VSCodeLight,
VSCodeDarkPlus,
CatppuccinMacchiato,
OneDark
}

public enum SearchWindowScreens
{
RememberLastLaunchLocation,
Expand Down
69 changes: 67 additions & 2 deletions Flow.Launcher.Plugin/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,18 @@ public string PluginDirectory
/// </summary>
public PreviewInfo Preview { get; set; } = PreviewInfo.Default;

/// <summary>
/// Controls whether Flow Launcher's preview pane is shown for this result.
/// </summary>
/// <remarks>
/// This is independent of <see cref="PreviewInfo.ContentType"/>, which only decides how the
/// preview is rendered. Use this to force the pane open (for example a result whose whole purpose
/// is its markdown preview) or to hide it on results that have nothing useful to preview.
/// </remarks>
/// <default><see cref="Flow.Launcher.Plugin.PreviewVisibility.Default"/></default>
[JsonConverter(typeof(JsonStringEnumConverter<PreviewVisibility>))]
public PreviewVisibility PreviewVisibility { get; set; } = PreviewVisibility.Default;

/// <summary>
/// Determines if the user selection count should be added to the score. This can be useful when set to false to allow the result sequence order to be the same everytime instead of changing based on selection.
/// </summary>
Expand Down Expand Up @@ -365,6 +377,7 @@ public Result Clone()
ProgressBar = ProgressBar,
ProgressBarColor = ProgressBarColor,
Preview = Preview,
PreviewVisibility = PreviewVisibility,
Comment thread
DavidGBrett marked this conversation as resolved.
AddSelectedCount = AddSelectedCount,
RecordKey = RecordKey,
ShowBadge = ShowBadge,
Expand All @@ -378,7 +391,7 @@ public Result Clone()
public record PreviewInfo
{
/// <summary>
/// Full image used for preview panel
/// Shown in the preview panel. Falls back to the result icon when not set.
/// </summary>
public string PreviewImagePath { get; set; } = null;

Expand All @@ -388,7 +401,8 @@ public record PreviewInfo
public bool IsMedia { get; set; } = false;

/// <summary>
/// Result description text that is shown at the bottom of the preview panel.
/// Text shown in the preview panel. How it is displayed depends on
/// <see cref="ContentType"/>.
/// </summary>
/// <remarks>
/// When a value is not set, the <see cref="SubTitle"/> will be used.
Expand All @@ -406,6 +420,12 @@ public record PreviewInfo
/// </summary>
public string FilePath { get; set; } = null;

/// <summary>
/// Controls the preview rendering mode. See <see cref="PreviewContentType"/>.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<PreviewContentType>))]
public PreviewContentType ContentType { get; set; } = PreviewContentType.ImageWithText;

/// <summary>
/// Default instance of <see cref="PreviewInfo"/>
/// </summary>
Expand All @@ -416,7 +436,52 @@ public record PreviewInfo
IsMedia = false,
PreviewDelegate = null,
FilePath = null,
ContentType = PreviewContentType.ImageWithText,
};
}
}

/// <summary>
/// Supported preview description rendering modes.
/// </summary>
public enum PreviewContentType
{
/// <summary>
/// Shows the preview image above the result title, with the description
/// rendered as plain text below a separator.
/// </summary>
[JsonStringEnumMemberName("imageWithText")]
ImageWithText,

/// <summary>
/// Shows a scrollable panel with the description rendered as formatted markdown.
/// </summary>
[JsonStringEnumMemberName("markdown")]
Markdown,
}

/// <summary>
/// Controls whether the preview pane is shown for a <see cref="Result"/>.
/// </summary>
public enum PreviewVisibility
{
/// <summary>
/// Follow Flow Launcher's normal preview behaviour (the global "Always Preview" setting and the
/// user's manual preview toggle decide whether the pane is shown).
/// </summary>
[JsonStringEnumMemberName("default")]
Default,

/// <summary>
/// Never show the preview pane for this result, even when the global "Always Preview" setting is on.
/// </summary>
[JsonStringEnumMemberName("never")]
Never,

/// <summary>
/// Always show the preview pane for this result, even when the global "Always Preview" setting is off.
/// </summary>
[JsonStringEnumMemberName("always")]
Always,
}
}
58 changes: 58 additions & 0 deletions Flow.Launcher.Test/CodeHighlightThemeTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Flow.Launcher.Resources.Controls;
using NUnit.Framework;
using NUnit.Framework.Legacy;

namespace Flow.Launcher.Test
{
[TestFixture]
internal class CodeHighlightThemeTest
{
private string _savedThemeName;

[SetUp]
public void SetUp() => _savedThemeName = PreviewMarkdownScrollViewer.ActiveThemeName;

[TearDown]
public void TearDown() => PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme(_savedThemeName, isDark: true);

[Test]
public void GivenAutoSetting_WhenAppIsDark_ThenActiveThemeIsADarkTheme()
{
PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme("Auto", isDark: true);

ClassicAssert.AreEqual("VS Code Dark+", PreviewMarkdownScrollViewer.ActiveThemeName);
}

[Test]
public void GivenAutoSetting_WhenAppIsLight_ThenActiveThemeIsALightTheme()
{
PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme("Auto", isDark: false);

ClassicAssert.AreEqual("VS Code Light", PreviewMarkdownScrollViewer.ActiveThemeName);
}

[Test]
public void GivenExplicitTheme_WhenApplied_ThenActiveThemeMatchesRegardlessOfAppScheme()
{
PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme("OneDark", isDark: false);

ClassicAssert.AreEqual("One Dark", PreviewMarkdownScrollViewer.ActiveThemeName);
}

[Test]
public void GivenEmptySetting_WhenApplied_ThenFallsBackToAutoBehaviour()
{
PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme("", isDark: false);

ClassicAssert.AreEqual("VS Code Light", PreviewMarkdownScrollViewer.ActiveThemeName);
Comment on lines +18 to +47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Reset the global theme between tests.

Each test mutates PreviewMarkdownScrollViewer.ActiveTheme, and that state is static in Flow.Launcher/Resources/Controls/PreviewMarkdownScrollViewer.cs:18-33. Leaving it changed makes this fixture order-dependent with other preview tests. A small [SetUp]/[TearDown] that restores the previous theme would keep the suite isolated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Flow.Launcher.Test/CodeHighlightThemeTest.cs` around lines 10 - 39, The tests
in CodeHighlightThemeTest are mutating the static theme state on
PreviewMarkdownScrollViewer, which can leak between tests and make the fixture
order-dependent. Add a small setup/teardown in this test class to save and
restore the previous PreviewMarkdownScrollViewer.ActiveTheme (or otherwise reset
the theme after each test), so each ApplyCodeHighlightTheme/ActiveThemeName
assertion runs in isolation.

Comment on lines +42 to +47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a real unknown-setting case here.

The test name and the resolver contract both mention "unknown or empty", but this only exercises "". Passing an unrecognized value (for example "BogusTheme") is a separate fallback path worth covering so malformed persisted settings don't slip through.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Flow.Launcher.Test/CodeHighlightThemeTest.cs` around lines 34 - 39, The test
in `GivenUnknownOrEmptySetting_WhenApplied_ThenFallsBackToAutoBehaviour` only
covers the empty-string path, so add a separate assertion using a truly
unrecognized theme name such as a bogus value to exercise the unknown-setting
fallback in `PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme`. Keep the
existing empty case if needed, but ensure the test explicitly verifies that an
invalid non-empty setting still resolves to the default theme via
`PreviewMarkdownScrollViewer.ActiveThemeName`.

}

[Test]
public void GivenUnrecognisedSetting_WhenApplied_ThenFallsBackToAutoBehaviour()
{
PreviewMarkdownScrollViewer.ApplyCodeHighlightTheme("BogusTheme", isDark: true);

ClassicAssert.AreEqual("VS Code Dark+", PreviewMarkdownScrollViewer.ActiveThemeName);
}
}
}
Loading