Skip to content

Commit

Permalink
Orgmode fixes and improvements, closes #2405 (PR #2504)
Browse files Browse the repository at this point in the history
* add replacepattergenerator file for orgmode

we need this to set more correct patterns for orgmode lists and headings, which are
different to markdown ones

fix action button behaviour on unordered and ordered lists

fix checkbox to only use uppercase X as per orgmode specs

add heading action buttons

* prevent bolding of level 3+ headings

more generically, prevent emphasizing a pattern if it's made entirely of
the delimiter character

also fix heading and comments being highlighted across newlines
we were using \s but what we really want is whitespace only
and \s also matches newline

orgmode headings can also be any depth, unlike markdown 1-6
  • Loading branch information
nyxkn authored Jan 18, 2025
1 parent aaa5c5d commit 57792f2
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import net.gsantner.markor.format.markdown.MarkdownSyntaxHighlighter;
import net.gsantner.markor.format.markdown.MarkdownTextConverter;
import net.gsantner.markor.format.orgmode.OrgmodeActionButtons;
import net.gsantner.markor.format.orgmode.OrgmodeReplacePatternGenerator;
import net.gsantner.markor.format.orgmode.OrgmodeSyntaxHighlighter;
import net.gsantner.markor.format.orgmode.OrgmodeTextConverter;
import net.gsantner.markor.format.plaintext.PlaintextActionButtons;
Expand Down Expand Up @@ -184,8 +185,8 @@ public static FormatRegistry getFormat(int formatId, @NonNull final Context cont
format._converter = CONVERTER_ORGMODE;
format._highlighter = new OrgmodeSyntaxHighlighter(appSettings);
format._textActions = new OrgmodeActionButtons(context, document);
format._autoFormatInputFilter = new AutoTextFormatter(MarkdownReplacePatternGenerator.formatPatterns);
format._autoFormatTextWatcher = new ListHandler(MarkdownReplacePatternGenerator.formatPatterns);
format._autoFormatInputFilter = new AutoTextFormatter(OrgmodeReplacePatternGenerator.formatPatterns);
format._autoFormatTextWatcher = new ListHandler(OrgmodeReplacePatternGenerator.formatPatterns);
break;
}
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import net.gsantner.markor.R;
import net.gsantner.markor.format.ActionButtonBase;
import net.gsantner.markor.format.markdown.MarkdownReplacePatternGenerator;
import net.gsantner.markor.format.orgmode.OrgmodeReplacePatternGenerator;
import net.gsantner.markor.frontend.textview.AutoTextFormatter;
import net.gsantner.markor.model.Document;

Expand Down Expand Up @@ -35,7 +35,10 @@ public List<ActionItem> getFormatActionList() {
new ActionItem(R.string.abid_orgmode_italic, R.drawable.ic_format_italic_black_24dp, R.string.italic),
new ActionItem(R.string.abid_orgmode_strikeout, R.drawable.ic_format_strikethrough_black_24dp, R.string.strikeout),
new ActionItem(R.string.abid_orgmode_underline, R.drawable.ic_format_underlined_black_24dp, R.string.underline),
new ActionItem(R.string.abid_orgmode_code_inline, R.drawable.ic_code_black_24dp, R.string.inline_code)
new ActionItem(R.string.abid_orgmode_code_inline, R.drawable.ic_code_black_24dp, R.string.inline_code),
new ActionItem(R.string.abid_orgmode_h1, R.drawable.format_header_1, R.string.heading_1),
new ActionItem(R.string.abid_orgmode_h2, R.drawable.format_header_2, R.string.heading_2),
new ActionItem(R.string.abid_orgmode_h3, R.drawable.format_header_3, R.string.heading_3)
);
}

Expand All @@ -47,13 +50,39 @@ int getFormatActionsKey() {

@Override
protected void renumberOrderedList() {
// Use markdown format for orgmode too
AutoTextFormatter.renumberOrderedList(_hlEditor.getText(), MarkdownReplacePatternGenerator.formatPatterns);
AutoTextFormatter.renumberOrderedList(_hlEditor.getText(), OrgmodeReplacePatternGenerator.formatPatterns);
}

@Override
public boolean onActionClick(final @StringRes int action) {
switch (action) {
case R.string.abid_orgmode_h1: {
runRegexReplaceAction(OrgmodeReplacePatternGenerator.setOrUnsetHeadingWithLevel(1));
return true;
}
case R.string.abid_orgmode_h2: {
runRegexReplaceAction(OrgmodeReplacePatternGenerator.setOrUnsetHeadingWithLevel(2));
return true;
}
case R.string.abid_orgmode_h3: {
runRegexReplaceAction(OrgmodeReplacePatternGenerator.setOrUnsetHeadingWithLevel(3));
return true;
}
case R.string.abid_common_unordered_list_char: {
final String listChar = _appSettings.getUnorderedListCharacter();
runRegexReplaceAction(OrgmodeReplacePatternGenerator.replaceWithUnorderedListPrefixOrRemovePrefix(listChar));
return true;
}
case R.string.abid_common_checkbox_list: {
final String listChar = _appSettings.getUnorderedListCharacter();
runRegexReplaceAction(OrgmodeReplacePatternGenerator.toggleToCheckedOrUncheckedListPrefix(listChar));
return true;
}
case R.string.abid_common_ordered_list_number: {
runRegexReplaceAction(OrgmodeReplacePatternGenerator.replaceWithOrderedListPrefixOrRemovePrefix());
runRenumberOrderedListIfRequired();
return true;
}
case R.string.abid_orgmode_bold: {
runSurroundAction("*");
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*#######################################################
*
* Maintained 2018-2025 by Gregor Santner <gsantner AT mailbox DOT org>
* License of this file: Apache 2.0
* https://www.apache.org/licenses/LICENSE-2.0
*
#########################################################*/
package net.gsantner.markor.format.orgmode;

import android.util.Log;

import net.gsantner.markor.format.ActionButtonBase;
import net.gsantner.markor.frontend.textview.AutoTextFormatter;
import net.gsantner.markor.frontend.textview.ReplacePatternGeneratorHelper;
import net.gsantner.opoc.format.GsTextUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

public class OrgmodeReplacePatternGenerator {

// TODO: write tests

public static final Pattern PREFIX_ORDERED_LIST = Pattern.compile("^(\\s*)((\\d+)(\\.|\\))(\\s))");
public static final Pattern PREFIX_ATX_HEADING = Pattern.compile("^(\\s{0,3})(\\*+\\s)");
public static final Pattern PREFIX_CHECKED_LIST = Pattern.compile("^(\\s*)((-|\\+)\\s\\[(X)]\\s)");
public static final Pattern PREFIX_CHECKBOX_LIST = Pattern.compile("^(\\s*)(([-+]\\s\\[)[\\sX](]\\s))");
public static final Pattern PREFIX_UNCHECKED_LIST = Pattern.compile("^(\\s*)((-|\\+)\\s\\[\\s]\\s)");
public static final Pattern PREFIX_UNORDERED_LIST = Pattern.compile("^(\\s*)((-|\\+)\\s)");
public static final Pattern PREFIX_LEADING_SPACE = Pattern.compile("^(\\s*)");

public static final AutoTextFormatter.FormatPatterns formatPatterns = new AutoTextFormatter.FormatPatterns(
OrgmodeReplacePatternGenerator.PREFIX_UNORDERED_LIST,
OrgmodeReplacePatternGenerator.PREFIX_CHECKBOX_LIST,
OrgmodeReplacePatternGenerator.PREFIX_ORDERED_LIST,
2);

public static final Pattern[] PREFIX_PATTERNS = {
PREFIX_ORDERED_LIST,
PREFIX_ATX_HEADING,
PREFIX_CHECKED_LIST,
PREFIX_UNCHECKED_LIST,
// Unordered has to be after checked list. Otherwise checklist will match as an unordered list.
PREFIX_UNORDERED_LIST,
PREFIX_LEADING_SPACE,
};

private final static String ORDERED_LIST_REPLACEMENT = "$11. ";

/**
* Set/unset ATX heading level on each selected line
* <p>
* This routine will make the following conditional changes
* <p>
* Line is heading of same level as requested -> remove heading
* Line is heading of different level that that requested -> add heading of specified level
* Line is not heading -> add heading of specified level
*
* @param level ATX heading level
*/
public static List<ActionButtonBase.ReplacePattern> setOrUnsetHeadingWithLevel(int level) {

List<ActionButtonBase.ReplacePattern> patterns = new ArrayList<>();

String heading = "\\*".repeat(level);

// Replace this exact heading level with nothing
patterns.add(new ActionButtonBase.ReplacePattern("^(\\s{0,3})" + heading + " ", "$1"));

// Replace other headings with commonmark-compatible leading space
patterns.add(new ActionButtonBase.ReplacePattern(OrgmodeReplacePatternGenerator.PREFIX_ATX_HEADING, "$1" + heading + " "));

// Replace all other prefixes with heading
for (final Pattern pp : OrgmodeReplacePatternGenerator.PREFIX_PATTERNS) {
patterns.add(new ActionButtonBase.ReplacePattern(pp, heading + "$1 "));
}

return patterns;
}

public static List<ActionButtonBase.ReplacePattern> replaceWithUnorderedListPrefixOrRemovePrefix(String listChar) {
final String unorderedListReplacement = "$1" + listChar + " ";
return ReplacePatternGeneratorHelper.replaceWithTargetPrefixOrRemove(PREFIX_PATTERNS, PREFIX_UNORDERED_LIST, unorderedListReplacement);
}

public static List<ActionButtonBase.ReplacePattern> toggleToCheckedOrUncheckedListPrefix(String listChar) {
final String unchecked = "$1" + listChar + " [ ] ";
final String checked = "$1" + listChar + " [X] ";
return ReplacePatternGeneratorHelper.replaceWithTargetPatternOrAlternative(PREFIX_PATTERNS, PREFIX_UNCHECKED_LIST, unchecked, checked);
}

public static List<ActionButtonBase.ReplacePattern> replaceWithOrderedListPrefixOrRemovePrefix() {
return ReplacePatternGeneratorHelper.replaceWithTargetPrefixOrRemove(PREFIX_PATTERNS, PREFIX_ORDERED_LIST, ORDERED_LIST_REPLACEMENT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@
import java.util.regex.Pattern;

public class OrgmodeSyntaxHighlighter extends SyntaxHighlighterBase {
public final static String COMMON_EMPHASIS_PATTERN = "(?<=(\\n|^|\\s|\\{|\\())([%s])(?=\\S)(.*?)\\S\\2(?=(\\n|$|\\s|\\.|,|:|;|-|\\}|\\)))";
public final static String COMMON_EMPHASIS_PATTERN = "(?<=(\\n|^|\\s|\\{|\\())([%s])(?=\\S)(?!\\2+\\2)(.*?)\\S\\2(?=(\\n|$|\\s|\\.|,|:|;|-|\\}|\\)))";
public final static Pattern BOLD = Pattern.compile(String.format(COMMON_EMPHASIS_PATTERN, "*"));
public final static Pattern ITALICS = Pattern.compile(String.format(COMMON_EMPHASIS_PATTERN, "/"));
public final static Pattern STRIKETHROUGH = Pattern.compile(String.format(COMMON_EMPHASIS_PATTERN, "+"));
public final static Pattern UNDERLINE = Pattern.compile(String.format(COMMON_EMPHASIS_PATTERN, "_"));
public final static Pattern CODE_INLINE = Pattern.compile(String.format(COMMON_EMPHASIS_PATTERN, "=~"));
public final static Pattern HEADING = Pattern.compile("(?m)^(\\*+)\\s(.*?)(?=\\n|$)");
public final static Pattern HEADING = Pattern.compile("(?m)^(\\*+) (.*?)(?=\\n|$)");
public final static Pattern BLOCK = Pattern.compile("(?m)(?<=#\\+BEGIN_.{1,15}$\\s)[\\s\\S]*?(?=#\\+END)");
public final static Pattern PREAMBLE = Pattern.compile("(?m)^(#\\+)(.*?)(?=\\n|$)");
public final static Pattern COMMENT = Pattern.compile("(?m)^(#+)\\s(.*?)(?=\\n|$)");
public final static Pattern LIST_UNORDERED = Pattern.compile("(\\n|^)\\s{0,16}([*+-])( \\[[ xX]\\])?(?= )");
public final static Pattern COMMENT = Pattern.compile("(?m)^(#+) (.*?)(?=\\n|$)");
public final static Pattern LIST_UNORDERED = Pattern.compile("(\\n|^)\\s{0,16}([+-])( \\[[ X]\\])?(?= )");
public final static Pattern LIST_ORDERED = Pattern.compile("(?m)^\\s{0,16}(\\d+)(:?\\.|\\))\\s");
public final static Pattern LINK = Pattern.compile("\\[\\[.*?]]|<.*?>|https?://\\S+|\\[.*?]\\[.*?]|\\[.*?]\n");
private static final int ORG_COLOR_HEADING = 0xffef6D00;
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/res/values/string-not_translatable.xml
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,10 @@ work. If not, see <https://creativecommons.org/publicdomain/zero/1.0/>.
<string name="abid_todotxt_due_date" translatable="false">abid_todotxt_due_date</string>
<string name="abid_todotxt_sort_todo" translatable="false">abid_todotxt_sort_todo</string>



<string name="abid_orgmode_h1" translatable="false">abid_orgmode_h1</string>
<string name="abid_orgmode_h2" translatable="false">abid_orgmode_h2</string>
<string name="abid_orgmode_h3" translatable="false">abid_orgmode_h3</string>
<string name="abid_orgmode_bold" translatable="false">abid_orgmode_bold</string>
<string name="abid_orgmode_italic" translatable="false">abid_orgmode_italic</string>
<string name="abid_orgmode_strikeout" translatable="false">abid_orgmode_strikeout</string>
Expand Down

0 comments on commit 57792f2

Please sign in to comment.