termux-app

Форк
0
207 строк · 8.7 Кб
1
package com.termux.shared.markdown;
2

3
import android.content.Context;
4
import android.graphics.Typeface;
5
import android.text.Spanned;
6
import android.text.style.AbsoluteSizeSpan;
7
import android.text.style.BackgroundColorSpan;
8
import android.text.style.BulletSpan;
9
import android.text.style.QuoteSpan;
10
import android.text.style.StrikethroughSpan;
11
import android.text.style.StyleSpan;
12
import android.text.style.TypefaceSpan;
13
import android.text.util.Linkify;
14

15
import androidx.annotation.NonNull;
16
import androidx.core.content.ContextCompat;
17

18
import com.google.common.base.Strings;
19
import com.termux.shared.R;
20
import com.termux.shared.theme.ThemeUtils;
21

22
import org.commonmark.ext.gfm.strikethrough.Strikethrough;
23
import org.commonmark.node.BlockQuote;
24
import org.commonmark.node.Code;
25
import org.commonmark.node.Emphasis;
26
import org.commonmark.node.FencedCodeBlock;
27
import org.commonmark.node.ListItem;
28
import org.commonmark.node.StrongEmphasis;
29

30
import java.util.regex.Matcher;
31
import java.util.regex.Pattern;
32

33
import io.noties.markwon.AbstractMarkwonPlugin;
34
import io.noties.markwon.Markwon;
35
import io.noties.markwon.MarkwonSpansFactory;
36
import io.noties.markwon.MarkwonVisitor;
37
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
38
import io.noties.markwon.linkify.LinkifyPlugin;
39

40
public class MarkdownUtils {
41

42
    public static final String backtick = "`";
43
    public static final Pattern backticksPattern = Pattern.compile("(" + backtick + "+)");
44

45
    /**
46
     * Get the markdown code {@link String} for a {@link String}. This ensures all backticks "`" are
47
     * properly escaped so that markdown does not break.
48
     *
49
     * @param string The {@link String} to convert.
50
     * @param codeBlock If the {@link String} is to be converted to a code block or inline code.
51
     * @return Returns the markdown code {@link String}.
52
     */
53
    public static String getMarkdownCodeForString(String string, boolean codeBlock) {
54
        if (string == null) return null;
55
        if (string.isEmpty()) return "";
56

57
        int maxConsecutiveBackTicksCount = getMaxConsecutiveBackTicksCount(string);
58

59
        // markdown requires surrounding backticks count to be at least one more than the count
60
        // of consecutive ticks in the string itself
61
        int backticksCountToUse;
62
        if (codeBlock)
63
            backticksCountToUse = maxConsecutiveBackTicksCount + 3;
64
        else
65
            backticksCountToUse = maxConsecutiveBackTicksCount + 1;
66

67
        // create a string with n backticks where n==backticksCountToUse
68
        String backticksToUse = Strings.repeat(backtick, backticksCountToUse);
69

70
        if (codeBlock)
71
            return backticksToUse + "\n" + string + "\n" + backticksToUse;
72
        else {
73
            // add a space to any prefixed or suffixed backtick characters
74
            if (string.startsWith(backtick))
75
                string = " " + string;
76
            if (string.endsWith(backtick))
77
                string = string + " ";
78

79
            return backticksToUse + string + backticksToUse;
80
        }
81
    }
82

83
    /**
84
     * Get the max consecutive backticks "`" in a {@link String}.
85
     *
86
     * @param string The {@link String} to check.
87
     * @return Returns the max consecutive backticks count.
88
     */
89
    public static int getMaxConsecutiveBackTicksCount(String string) {
90
        if (string == null || string.isEmpty()) return 0;
91

92
        int maxCount = 0;
93
        int matchCount;
94
        String match;
95

96
        Matcher matcher = backticksPattern.matcher(string);
97
        while(matcher.find()) {
98
            match = matcher.group(1);
99
            matchCount = match != null ? match.length() : 0;
100
            if (matchCount > maxCount)
101
                maxCount = matchCount;
102
        }
103

104
        return maxCount;
105
    }
106

107

108

109
    public static String getLiteralSingleLineMarkdownStringEntry(String label, Object object, String def) {
110
        return "**" + label + "**: " + (object != null ? object.toString() : def) +  "  ";
111
    }
112

113
    public static String getSingleLineMarkdownStringEntry(String label, Object object, String def) {
114
        if (object != null)
115
            return "**" + label + "**: " + getMarkdownCodeForString(object.toString(), false) +  "  ";
116
        else
117
            return "**" + label + "**: " + def +  "  ";
118
    }
119

120
    public static String getMultiLineMarkdownStringEntry(String label, Object object, String def) {
121
        if (object != null)
122
            return "**" + label + "**:\n" + getMarkdownCodeForString(object.toString(), true) + "\n";
123
        else
124
            return "**" + label + "**: " + def + "\n";
125
    }
126

127
    public static String getLinkMarkdownString(String label, String url) {
128
        if (url != null)
129
            return "[" + label.replaceAll("]", "\\\\]") + "](" + url.replaceAll("\\)", "\\\\)") +  ")";
130
        else
131
            return label;
132
    }
133

134

135
    /** Check following for more info:
136
     * https://github.com/noties/Markwon/tree/v4.6.2/app-sample
137
     * https://noties.io/Markwon/docs/v4/recycler/
138
     * https://github.com/noties/Markwon/blob/v4.6.2/app-sample/src/main/java/io/noties/markwon/app/readme/ReadMeActivity.kt
139
     */
140
    public static Markwon getRecyclerMarkwonBuilder(Context context) {
141
        return Markwon.builder(context)
142
            .usePlugin(LinkifyPlugin.create(Linkify.EMAIL_ADDRESSES | Linkify.WEB_URLS))
143
            .usePlugin(new AbstractMarkwonPlugin() {
144
                @Override
145
                public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
146
                    builder.on(FencedCodeBlock.class, (visitor, fencedCodeBlock) -> {
147
                        // we actually won't be applying code spans here, as our custom xml view will
148
                        // draw background and apply mono typeface
149
                        //
150
                        // NB the `trim` operation on literal (as code will have a new line at the end)
151
                        final CharSequence code = visitor.configuration()
152
                            .syntaxHighlight()
153
                            .highlight(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral().trim());
154
                        visitor.builder().append(code);
155
                    });
156
                }
157

158
                @Override
159
                public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
160
                    // Do not change color for night themes
161
                    if (!ThemeUtils.isNightModeEnabled(context)) {
162
                        builder
163
                            // set color for inline code
164
                            .setFactory(Code.class, (configuration, props) -> new Object[]{
165
                                new BackgroundColorSpan(ContextCompat.getColor(context, R.color.background_markdown_code_inline)),
166
                            });
167
                    }
168
                }
169
            })
170
            .build();
171
    }
172

173
    /** Check following for more info:
174
     * https://github.com/noties/Markwon/tree/v4.6.2/app-sample
175
     * https://github.com/noties/Markwon/blob/v4.6.2/app-sample/src/main/java/io/noties/markwon/app/samples/notification/NotificationSample.java
176
     */
177
    public static Markwon getSpannedMarkwonBuilder(Context context) {
178
        return Markwon.builder(context)
179
                .usePlugin(StrikethroughPlugin.create())
180
                .usePlugin(new AbstractMarkwonPlugin() {
181
                    @Override
182
                    public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
183
                        builder
184
                            .setFactory(Emphasis.class, (configuration, props) -> new StyleSpan(Typeface.ITALIC))
185
                            .setFactory(StrongEmphasis.class, (configuration, props) -> new StyleSpan(Typeface.BOLD))
186
                            .setFactory(BlockQuote.class, (configuration, props) -> new QuoteSpan())
187
                            .setFactory(Strikethrough.class, (configuration, props) -> new StrikethroughSpan())
188
                            // NB! notification does not handle background color
189
                            .setFactory(Code.class, (configuration, props) -> new Object[]{
190
                                new BackgroundColorSpan(ContextCompat.getColor(context, R.color.background_markdown_code_inline)),
191
                                new TypefaceSpan("monospace"),
192
                                new AbsoluteSizeSpan(48)
193
                            })
194
                            // NB! both ordered and bullet list items
195
                            .setFactory(ListItem.class, (configuration, props) -> new BulletSpan());
196
                    }
197
                })
198
                .build();
199
    }
200

201
    public static Spanned getSpannedMarkdownText(Context context, String string) {
202
        if (context == null || string == null) return null;
203
        final Markwon markwon = getSpannedMarkwonBuilder(context);
204
        return markwon.toMarkdown(string);
205
    }
206

207
}
208

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.