Skip to content

Commit

Permalink
Move parsing logic to separate class on Android (#560)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomekzaw authored Dec 5, 2024
1 parent dcb21de commit 790c02c
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 87 deletions.
35 changes: 35 additions & 0 deletions android/src/main/cpp/MarkdownParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "MarkdownParser.h"
#include "MarkdownGlobal.h"

#include <fbjni/fbjni.h>

using namespace facebook;

namespace expensify {
namespace livemarkdown {
jni::local_ref<jni::JString> MarkdownParser::nativeParse(
jni::alias_ref<jhybridobject> jThis,
jni::alias_ref<jni::JString> text,
const int parserId) {
static std::mutex workletRuntimeMutex; // this needs to be global since the worklet runtime is also global
const auto lock = std::lock_guard<std::mutex>(workletRuntimeMutex);

const auto markdownRuntime = expensify::livemarkdown::getMarkdownRuntime();
jsi::Runtime &rt = markdownRuntime->getJSIRuntime();

const auto markdownWorklet = expensify::livemarkdown::getMarkdownWorklet(parserId);

const auto input = jsi::String::createFromUtf8(rt, text->toStdString());
const auto output = markdownRuntime->runGuarded(markdownWorklet, input);

const auto json = rt.global().getPropertyAsObject(rt, "JSON").getPropertyAsFunction(rt, "stringify").call(rt, output).asString(rt).utf8(rt);
return jni::make_jstring(json);
}

void MarkdownParser::registerNatives() {
registerHybrid({
makeNativeMethod("nativeParse", MarkdownParser::nativeParse)});
}

} // namespace livemarkdown
} // namespace expensify
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ using namespace facebook;
namespace expensify {
namespace livemarkdown {

class MarkdownUtils : public jni::HybridClass<MarkdownUtils>,
class MarkdownParser : public jni::HybridClass<MarkdownParser>,
public jsi::HostObject {
public:
static constexpr auto kJavaDescriptor =
"Lcom/expensify/livemarkdown/MarkdownUtils;";
"Lcom/expensify/livemarkdown/MarkdownParser;";

static jni::local_ref<jni::JString> nativeParseMarkdown(
static jni::local_ref<jni::JString> nativeParse(
jni::alias_ref<jhybridobject> jThis,
jni::alias_ref<jni::JString> input,
int parserId);
jni::alias_ref<jni::JString> text,
const int parserId);

static void registerNatives();

Expand Down
33 changes: 0 additions & 33 deletions android/src/main/cpp/MarkdownUtils.cpp

This file was deleted.

4 changes: 2 additions & 2 deletions android/src/main/cpp/OnLoad.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#include <fbjni/fbjni.h>

#include "MarkdownUtils.h"
#include "MarkdownParser.h"
#include "RuntimeDecorator.h"

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
return facebook::jni::initialize(
vm, [] { expensify::livemarkdown::MarkdownUtils::registerNatives(); });
vm, [] { expensify::livemarkdown::MarkdownParser::registerNatives(); });
}

extern "C" JNIEXPORT void JNICALL Java_com_expensify_livemarkdown_LiveMarkdownModule_injectJSIBindings(JNIEnv *env, jobject thiz, jlong jsiRuntime) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.expensify.livemarkdown;

import androidx.annotation.NonNull;

import com.facebook.react.bridge.ReactContext;
import com.facebook.react.util.RNLog;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class MarkdownParser {
private final @NonNull ReactContext mReactContext;
private String mPrevText;
private int mPrevParserId;
private List<MarkdownRange> mPrevMarkdownRanges;

public MarkdownParser(@NonNull ReactContext reactContext) {
mReactContext = reactContext;
}

private native String nativeParse(String text, int parserId);

public synchronized List<MarkdownRange> parse(String text, int parserId) {
if (text.equals(mPrevText) && parserId == mPrevParserId) {
return mPrevMarkdownRanges;
}

String json;
try {
json = nativeParse(text, parserId);
} catch (Exception e) {
// Skip formatting, runGuarded will show the error in LogBox
mPrevText = text;
mPrevParserId = parserId;
mPrevMarkdownRanges = Collections.emptyList();
return mPrevMarkdownRanges;
}

List<MarkdownRange> markdownRanges = new LinkedList<>();
try {
JSONArray ranges = new JSONArray(json);
for (int i = 0; i < ranges.length(); i++) {
JSONObject range = ranges.getJSONObject(i);
String type = range.getString("type");
int start = range.getInt("start");
int length = range.getInt("length");
int depth = range.optInt("depth", 1);
if (length == 0 || start + length > text.length()) {
continue;
}
markdownRanges.add(new MarkdownRange(type, start, length, depth));
}
} catch (JSONException e) {
RNLog.w(mReactContext, "[react-native-live-markdown] Incorrect schema of worklet parser output: " + e.getMessage());
mPrevText = text;
mPrevParserId = parserId;
mPrevMarkdownRanges = Collections.emptyList();
return mPrevMarkdownRanges;
}

mPrevText = text;
mPrevParserId = parserId;
mPrevMarkdownRanges = markdownRanges;
return mPrevMarkdownRanges;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,9 @@

import com.expensify.livemarkdown.spans.*;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.util.RNLog;
import com.facebook.react.views.text.internal.span.CustomLineHeightSpan;
import com.facebook.soloader.SoLoader;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.LinkedList;
import java.util.List;
import java.util.Objects;

Expand All @@ -25,19 +19,13 @@ public class MarkdownUtils {
SoLoader.loadLibrary("livemarkdown");
}

private static synchronized native String nativeParseMarkdown(String input, int parserId);

public MarkdownUtils(@NonNull ReactContext reactContext) {
mReactContext = reactContext;
mAssetManager = reactContext.getAssets();
mMarkdownParser = new MarkdownParser(reactContext);
}

private final @NonNull ReactContext mReactContext;
private final @NonNull AssetManager mAssetManager;

private String mPrevInput;
private String mPrevOutput;
private int mPrevParserId;
private final @NonNull MarkdownParser mMarkdownParser;

private MarkdownStyle mMarkdownStyle;
private int mParserId;
Expand All @@ -55,39 +43,8 @@ public void applyMarkdownFormatting(SpannableStringBuilder ssb) {

removeSpans(ssb);

String input = ssb.toString();
String output;
if (input.equals(mPrevInput) && mParserId == mPrevParserId) {
output = mPrevOutput;
} else {
try {
output = nativeParseMarkdown(input, mParserId);
} catch (Exception e) {
output = "[]";
}
mPrevInput = input;
mPrevOutput = output;
mPrevParserId = mParserId;
}

List<MarkdownRange> markdownRanges = new LinkedList<>();
try {
JSONArray ranges = new JSONArray(output);
for (int i = 0; i < ranges.length(); i++) {
JSONObject range = ranges.getJSONObject(i);
String type = range.getString("type");
int start = range.getInt("start");
int length = range.getInt("length");
int depth = range.optInt("depth", 1);
int end = start + length;
if (length == 0 || end > input.length()) {
continue;
}
markdownRanges.add(new MarkdownRange(type, start, length, depth));
}
} catch (JSONException e) {
RNLog.w(mReactContext, "[react-native-live-markdown] Incorrect schema of worklet parser output: " + e.getMessage());
}
String text = ssb.toString();
List<MarkdownRange> markdownRanges = mMarkdownParser.parse(text, mParserId);

for (MarkdownRange markdownRange : markdownRanges) {
applyRange(ssb, markdownRange);
Expand Down

0 comments on commit 790c02c

Please sign in to comment.