diff --git a/app/src/main/java/net/gsantner/markor/format/FormatRegistry.java b/app/src/main/java/net/gsantner/markor/format/FormatRegistry.java index c1ca840b04..923a9e3414 100644 --- a/app/src/main/java/net/gsantner/markor/format/FormatRegistry.java +++ b/app/src/main/java/net/gsantner/markor/format/FormatRegistry.java @@ -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; @@ -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: diff --git a/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeActionButtons.java b/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeActionButtons.java index a69b4d14ba..552e1ee44e 100644 --- a/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeActionButtons.java +++ b/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeActionButtons.java @@ -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; @@ -35,7 +35,10 @@ public List 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) ); } @@ -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; diff --git a/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeReplacePatternGenerator.java b/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeReplacePatternGenerator.java new file mode 100644 index 0000000000..0c65d46d52 --- /dev/null +++ b/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeReplacePatternGenerator.java @@ -0,0 +1,96 @@ +/*####################################################### + * + * Maintained 2018-2025 by Gregor Santner + * 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 + *

+ * This routine will make the following conditional changes + *

+ * 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 setOrUnsetHeadingWithLevel(int level) { + + List 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 replaceWithUnorderedListPrefixOrRemovePrefix(String listChar) { + final String unorderedListReplacement = "$1" + listChar + " "; + return ReplacePatternGeneratorHelper.replaceWithTargetPrefixOrRemove(PREFIX_PATTERNS, PREFIX_UNORDERED_LIST, unorderedListReplacement); + } + + public static List 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 replaceWithOrderedListPrefixOrRemovePrefix() { + return ReplacePatternGeneratorHelper.replaceWithTargetPrefixOrRemove(PREFIX_PATTERNS, PREFIX_ORDERED_LIST, ORDERED_LIST_REPLACEMENT); + } +} diff --git a/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeSyntaxHighlighter.java b/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeSyntaxHighlighter.java index 22586b4de8..31d7a84fa5 100644 --- a/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeSyntaxHighlighter.java +++ b/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeSyntaxHighlighter.java @@ -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; diff --git a/app/src/main/res/values/string-not_translatable.xml b/app/src/main/res/values/string-not_translatable.xml index 71a4e670c7..679c4885d5 100644 --- a/app/src/main/res/values/string-not_translatable.xml +++ b/app/src/main/res/values/string-not_translatable.xml @@ -348,7 +348,10 @@ work. If not, see . abid_todotxt_due_date abid_todotxt_sort_todo - + + abid_orgmode_h1 + abid_orgmode_h2 + abid_orgmode_h3 abid_orgmode_bold abid_orgmode_italic abid_orgmode_strikeout