diff --git a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java index a9b8639da5..9068696ca4 100644 --- a/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java +++ b/app/src/main/java/net/gsantner/markor/activity/DocumentEditAndViewFragment.java @@ -499,6 +499,7 @@ public boolean onOptionsItemSelected(@NonNull final MenuItem item) { case R.string.action_format_csv: case R.string.action_format_plaintext: case R.string.action_format_asciidoc: + case R.string.action_format_orgmode: case R.string.action_format_markdown: { if (itemId != _document.getFormat()) { _document.setFormat(itemId); diff --git a/app/src/main/java/net/gsantner/markor/activity/SettingsActivity.java b/app/src/main/java/net/gsantner/markor/activity/SettingsActivity.java index da7436c9a2..2f9b653487 100644 --- a/app/src/main/java/net/gsantner/markor/activity/SettingsActivity.java +++ b/app/src/main/java/net/gsantner/markor/activity/SettingsActivity.java @@ -330,6 +330,7 @@ public void onFsViewerConfig(GsFileBrowserOptions.Options dopt) { case R.string.pref_key__asciidoc__reorder_actions: case R.string.pref_key__markdown__reorder_actions: case R.string.pref_key__wikitext_reorder_actions: + case R.string.pref_key__orgmode__reorder_actions: case R.string.pref_key__todotxt__reorder_actions: { startActivity(new Intent(getActivity(), ActionButtonSettingsActivity.class).putExtra(ActionButtonSettingsActivity.EXTRA_FORMAT_KEY, keyResId)); break; 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 ca1ee64022..1c465fe718 100644 --- a/app/src/main/java/net/gsantner/markor/format/FormatRegistry.java +++ b/app/src/main/java/net/gsantner/markor/format/FormatRegistry.java @@ -27,6 +27,9 @@ import net.gsantner.markor.format.markdown.MarkdownReplacePatternGenerator; 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.OrgmodeSyntaxHighlighter; +import net.gsantner.markor.format.orgmode.OrgmodeTextConverter; import net.gsantner.markor.format.plaintext.PlaintextActionButtons; import net.gsantner.markor.format.plaintext.PlaintextSyntaxHighlighter; import net.gsantner.markor.format.plaintext.PlaintextTextConverter; @@ -57,6 +60,7 @@ public class FormatRegistry { public static final int FORMAT_TODOTXT = R.string.action_format_todotxt; public static final int FORMAT_KEYVALUE = R.string.action_format_keyvalue; public static final int FORMAT_EMBEDBINARY = R.string.action_format_embedbinary; + public static final int FORMAT_ORGMODE = R.string.action_format_orgmode; public final static MarkdownTextConverter CONVERTER_MARKDOWN = new MarkdownTextConverter(); @@ -67,6 +71,7 @@ public class FormatRegistry { public final static PlaintextTextConverter CONVERTER_PLAINTEXT = new PlaintextTextConverter(); public final static AsciidocTextConverter CONVERTER_ASCIIDOC = new AsciidocTextConverter(); public final static EmbedBinaryTextConverter CONVERTER_EMBEDBINARY = new EmbedBinaryTextConverter(); + public final static OrgmodeTextConverter CONVERTER_ORGMODE = new OrgmodeTextConverter(); // Order here is used to **determine** format by it's file extension and/or content heading @@ -79,6 +84,7 @@ public class FormatRegistry { CONVERTER_ASCIIDOC, CONVERTER_PLAINTEXT, CONVERTER_EMBEDBINARY, + CONVERTER_ORGMODE, }; public static boolean isFileSupported(final File file, final boolean... textOnly) { @@ -159,6 +165,14 @@ public static FormatRegistry getFormat(int formatId, @NonNull final Context cont format._textActions = new PlaintextActionButtons(context, document); break; } + case FORMAT_ORGMODE: { + 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); + break; + } default: case FORMAT_MARKDOWN: { formatId = FORMAT_MARKDOWN; 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 new file mode 100644 index 0000000000..8fd0032d63 --- /dev/null +++ b/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeActionButtons.java @@ -0,0 +1,60 @@ +package net.gsantner.markor.format.orgmode; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; + +import net.gsantner.markor.R; +import net.gsantner.markor.format.ActionButtonBase; +import net.gsantner.markor.format.markdown.MarkdownReplacePatternGenerator; +import net.gsantner.markor.frontend.textview.AutoTextFormatter; +import net.gsantner.markor.model.Document; + +import java.util.Arrays; +import java.util.List; + +public class OrgmodeActionButtons extends ActionButtonBase { + + public OrgmodeActionButtons(@NonNull Context context, Document document) { + super(context, document); + } + + @Override + public List getActiveActionList() { + final ActionItem[] TMA_ACTIONS = { + new ActionItem(R.string.abid_common_checkbox_list, R.drawable.ic_check_box_black_24dp, R.string.check_list), + new ActionItem(R.string.abid_common_unordered_list_char, R.drawable.ic_list_black_24dp, R.string.unordered_list), + new ActionItem(R.string.abid_common_ordered_list_number, R.drawable.ic_format_list_numbered_black_24dp, R.string.ordered_list), + new ActionItem(R.string.abid_common_delete_lines, R.drawable.ic_delete_black_24dp, R.string.delete_lines), + new ActionItem(R.string.abid_common_open_link_browser, R.drawable.ic_open_in_browser_black_24dp, R.string.open_link), + new ActionItem(R.string.abid_common_attach_something, R.drawable.ic_attach_file_black_24dp, R.string.attach), + new ActionItem(R.string.abid_common_special_key, R.drawable.ic_keyboard_black_24dp, R.string.special_key), + new ActionItem(R.string.abid_common_time, R.drawable.ic_access_time_black_24dp, R.string.date_and_time), + new ActionItem(R.string.abid_common_indent, R.drawable.ic_format_indent_increase_black_24dp, R.string.indent), + new ActionItem(R.string.abid_common_deindent, R.drawable.ic_format_indent_decrease_black_24dp, R.string.deindent), + new ActionItem(R.string.abid_common_new_line_below, R.drawable.ic_baseline_keyboard_return_24, R.string.start_new_line_below), + new ActionItem(R.string.abid_common_move_text_one_line_up, R.drawable.ic_baseline_arrow_upward_24, R.string.move_text_one_line_up), + new ActionItem(R.string.abid_common_move_text_one_line_down, R.drawable.ic_baseline_arrow_downward_24, R.string.move_text_one_line_down), + new ActionItem(R.string.abid_common_insert_snippet, R.drawable.ic_baseline_file_copy_24, R.string.insert_snippet), + + new ActionItem(R.string.abid_common_web_jump_to_very_top_or_bottom, R.drawable.ic_vertical_align_center_black_24dp, R.string.jump_to_bottom, ActionItem.DisplayMode.VIEW), + new ActionItem(R.string.abid_common_view_file_in_other_app, R.drawable.ic_open_in_browser_black_24dp, R.string.open_with, ActionItem.DisplayMode.ANY), + new ActionItem(R.string.abid_common_rotate_screen, R.drawable.ic_rotate_left_black_24dp, R.string.rotate, ActionItem.DisplayMode.ANY), + }; + + return Arrays.asList(TMA_ACTIONS); + } + + @Override + protected @StringRes + int getFormatActionsKey() { + return R.string.pref_key__orgmode__action_keys; + } + + @Override + protected void renumberOrderedList() { + // Use markdown format for orgmode too + AutoTextFormatter.renumberOrderedList(_hlEditor.getText(), MarkdownReplacePatternGenerator.formatPatterns); + } +} 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 new file mode 100644 index 0000000000..02eaf15da4 --- /dev/null +++ b/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeSyntaxHighlighter.java @@ -0,0 +1,50 @@ +package net.gsantner.markor.format.orgmode; + +import android.graphics.Paint; + +import net.gsantner.markor.frontend.textview.SyntaxHighlighterBase; +import net.gsantner.markor.model.AppSettings; + +import java.util.regex.Pattern; + +public class OrgmodeSyntaxHighlighter extends SyntaxHighlighterBase { + + public final static Pattern HEADING = Pattern.compile("(?m)^(\\*+)\\s(.*?)(?=\\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 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; + private static final int ORG_COLOR_LINK = 0xff1ea3fe; + private static final int ORG_COLOR_LIST = 0xffdaa521; + private static final int ORG_COLOR_DIM = 0xff8c8c8c; + private static final int ORG_COLOR_BLOCK = 0xdddddddd; + + public OrgmodeSyntaxHighlighter(AppSettings as) { + super(as); + } + + @Override + public SyntaxHighlighterBase configure(Paint paint) { + _delay = _appSettings.getOrgmodeHighlightingDelay(); + return super.configure(paint); + } + + @Override + protected void generateSpans() { + createTabSpans(_tabSize); + createUnderlineHexColorsSpans(); + createSmallBlueLinkSpans(); + createColorSpanForMatches(HEADING, ORG_COLOR_HEADING); + createColorSpanForMatches(LINK, ORG_COLOR_LINK); + createColorSpanForMatches(LIST_UNORDERED, ORG_COLOR_LIST); + createColorSpanForMatches(LIST_ORDERED, ORG_COLOR_LIST); + createColorSpanForMatches(PREAMBLE, ORG_COLOR_DIM); + createColorSpanForMatches(COMMENT, ORG_COLOR_DIM); + createColorBackgroundSpan(BLOCK, ORG_COLOR_BLOCK); + } + +} + diff --git a/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeTextConverter.java b/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeTextConverter.java new file mode 100644 index 0000000000..a1ff1a287d --- /dev/null +++ b/app/src/main/java/net/gsantner/markor/format/orgmode/OrgmodeTextConverter.java @@ -0,0 +1,53 @@ +package net.gsantner.markor.format.orgmode; + +import android.content.Context; + +import androidx.core.text.TextUtilsCompat; + +import net.gsantner.markor.format.TextConverterBase; +import net.gsantner.opoc.util.GsFileUtils; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@SuppressWarnings("WeakerAccess") +public class OrgmodeTextConverter extends TextConverterBase { + private static final String HTML100_BODY_PRE_BEGIN = "
";
+    private static final String HTML101_BODY_PRE_END = "
"; + private static final List EXT_ORG = Arrays.asList(".org"); + private static final List EXT = new ArrayList<>(); + + static { + EXT.addAll(EXT_ORG); + } + + //######################## + //## Methods + //######################## + + @Override + public String convertMarkup(String markup, Context context, boolean lightMode, boolean lineNum, File file) { + String converted = "", onLoadJs = "", head = ""; + final String extWithDot = GsFileUtils.getFilenameExtension(file); + + /////////////////////////////////////////// + // Convert + /////////////////////////////////////////// + converted = HTML100_BODY_PRE_BEGIN + + TextUtilsCompat.htmlEncode(markup) + + HTML101_BODY_PRE_END; + return putContentIntoTemplate(context, converted, lightMode, file, onLoadJs, head); + } + + @Override + protected String getContentType() { + return CONTENT_TYPE_HTML; + } + + @Override + protected boolean isFileOutOfThisFormat(String filepath, String extWithDot) { + return EXT.contains(extWithDot); + } +} diff --git a/app/src/main/java/net/gsantner/markor/model/AppSettings.java b/app/src/main/java/net/gsantner/markor/model/AppSettings.java index baec60b154..3670000ba8 100644 --- a/app/src/main/java/net/gsantner/markor/model/AppSettings.java +++ b/app/src/main/java/net/gsantner/markor/model/AppSettings.java @@ -151,6 +151,10 @@ public int getAsciidocHighlightingDelay() { return getInt(R.string.pref_key__asciidoc__hl_delay, 650); } + public int getOrgmodeHighlightingDelay() { + return getInt(R.string.pref_key__orgmode__hl_delay, 650); + } + public boolean isMarkdownHighlightLineEnding() { return getBool(R.string.pref_key__markdown__highlight_lineending_two_or_more_space, false); } diff --git a/app/src/main/java/net/gsantner/markor/model/Document.java b/app/src/main/java/net/gsantner/markor/model/Document.java index dcff3484c5..58284393e3 100644 --- a/app/src/main/java/net/gsantner/markor/model/Document.java +++ b/app/src/main/java/net/gsantner/markor/model/Document.java @@ -90,6 +90,8 @@ public Document(@NonNull final File file) { setFormat(FormatRegistry.FORMAT_WIKITEXT); } else if (FormatRegistry.CONVERTER_EMBEDBINARY.isFileOutOfThisFormat(getPath())) { setFormat(FormatRegistry.FORMAT_EMBEDBINARY); + } else if (FormatRegistry.CONVERTER_ORGMODE.isFileOutOfThisFormat(getPath())) { + setFormat(FormatRegistry.FORMAT_ORGMODE); } else { setFormat(FormatRegistry.FORMAT_PLAIN); } diff --git a/app/src/main/res/menu/document__edit__menu.xml b/app/src/main/res/menu/document__edit__menu.xml index 8c7ab3d38d..8be97a0810 100644 --- a/app/src/main/res/menu/document__edit__menu.xml +++ b/app/src/main/res/menu/document__edit__menu.xml @@ -181,6 +181,10 @@ android:id="@string/action_format_embedbinary" android:icon="@drawable/ic_image_black_24dp" android:title="@string/embed_binary" /> + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 6bca059d42..7e2cd8c5ab 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -104,6 +104,7 @@ @string/wikitext @string/asciidoc @string/csv + @string/orgmode @string/none @@ -130,6 +131,7 @@ .txt .adoc .csv + .org diff --git a/app/src/main/res/values/string-not_translatable.xml b/app/src/main/res/values/string-not_translatable.xml index 2c1171a611..1346df6dc2 100644 --- a/app/src/main/res/values/string-not_translatable.xml +++ b/app/src/main/res/values/string-not_translatable.xml @@ -114,6 +114,7 @@ work. If not, see . --> pref_key__markdown__hl_delay_v2 pref_key__asciidoc__hl_delay + pref_key__orgmode__hl_delay pref_key__quicknote_filepath pref_key__markdown__highlight_lineending_two_or_more_space pref_key__asciidoc__highlight_lineending_hard_line_break @@ -121,9 +122,11 @@ work. If not, see . pref_key__markdown__reorder_actions pref_key__todotxt__reorder_actions pref_key__plaintext__reorder_actions + pref_key__orgmode__reorder_actions pref_key__asciidoc__reorder_actions pref_key__markdown__action_keys pref_key__todotxt__action_keys + pref_key__orgmode__action_keys pref_key__plaintext__action_keys pref_key__asciidoc__action_keys pref_key__todotxt__hl_delay @@ -428,7 +431,9 @@ work. If not, see . action_format_markdown action_format_todotxt action_format_wikitext + action_format_orgmode pref_key__share_into_format Square Brackets CSV + OrgMode diff --git a/app/src/main/res/xml/preferences_master.xml b/app/src/main/res/xml/preferences_master.xml index 8c4942e432..7eb31e1ff2 100644 --- a/app/src/main/res/xml/preferences_master.xml +++ b/app/src/main/res/xml/preferences_master.xml @@ -602,6 +602,32 @@ + + + + + + + + + + + + + diff --git a/doc/assets/2023-10-07-orgmode.webp b/doc/assets/2023-10-07-orgmode.webp new file mode 100644 index 0000000000..17ba64fb0d Binary files /dev/null and b/doc/assets/2023-10-07-orgmode.webp differ