Skip to content

Commit

Permalink
Move parsing logic to separate class on iOS (#558)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomekzaw authored Dec 5, 2024
1 parent 67dd5d1 commit 02793be
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 42 deletions.
12 changes: 12 additions & 0 deletions apple/MarkdownParser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#import <Foundation/Foundation.h>
#import <RNLiveMarkdown/MarkdownRange.h>

NS_ASSUME_NONNULL_BEGIN

@interface MarkdownParser : NSObject

- (NSArray<MarkdownRange *> *)parse:(NSString *)text withParserId:(NSNumber *)parserId;

NS_ASSUME_NONNULL_END

@end
68 changes: 68 additions & 0 deletions apple/MarkdownParser.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#import "MarkdownParser.h"
#import <RNLiveMarkdown/MarkdownGlobal.h>
#import <React/RCTLog.h>
#import <mutex>

@implementation MarkdownParser {
NSString *_prevText;
NSNumber *_prevParserId;
NSArray<MarkdownRange *> *_prevMarkdownRanges;
}

- (NSArray<MarkdownRange *> *)parse:(NSString *)text withParserId:(nonnull NSNumber *)parserId {
@synchronized (self) {
if ([text isEqualToString:_prevText] && [parserId isEqualToNumber:_prevParserId]) {
return _prevMarkdownRanges;
}

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 intValue]);

const auto &input = jsi::String::createFromUtf8(rt, [text UTF8String]);

jsi::Value output;
try {
output = markdownRuntime->runGuarded(markdownWorklet, input);
} catch (const jsi::JSError &error) {
// Skip formatting, runGuarded will show the error in LogBox
_prevText = text;
_prevParserId = parserId;
_prevMarkdownRanges = @[];
return _prevMarkdownRanges;
}

NSMutableArray<MarkdownRange *> *markdownRanges = [[NSMutableArray alloc] init];
try {
const auto &ranges = output.asObject(rt).asArray(rt);
for (size_t i = 0, n = ranges.size(rt); i < n; ++i) {
const auto &item = ranges.getValueAtIndex(rt, i).asObject(rt);
const auto &type = item.getProperty(rt, "type").asString(rt).utf8(rt);
const auto &start = static_cast<int>(item.getProperty(rt, "start").asNumber());
const auto &length = static_cast<int>(item.getProperty(rt, "length").asNumber());
const auto &depth = item.hasProperty(rt, "depth") ? static_cast<int>(item.getProperty(rt, "depth").asNumber()) : 1;

NSRange range = NSMakeRange(start, length);
MarkdownRange *markdownRange = [[MarkdownRange alloc] initWithType:@(type.c_str()) range:range depth:depth];
[markdownRanges addObject:markdownRange];
}
} catch (const jsi::JSError &error) {
RCTLogWarn(@"[react-native-live-markdown] Incorrect schema of worklet parser output: %s", error.getMessage().c_str());
_prevText = text;
_prevParserId = parserId;
_prevMarkdownRanges = @[];
return _prevMarkdownRanges;
}

_prevText = text;
_prevParserId = parserId;
_prevMarkdownRanges = markdownRanges;
return _prevMarkdownRanges;
}
}

@end
51 changes: 13 additions & 38 deletions apple/RCTMarkdownUtils.mm
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
#import <RNLiveMarkdown/RCTMarkdownUtils.h>
#import <RNLiveMarkdown/MarkdownGlobal.h>
#import <RNLiveMarkdown/MarkdownRange.h>
#import <RNLiveMarkdown/MarkdownParser.h>
#import "react_native_assert.h"
#import <React/RCTAssert.h>
#import <React/RCTFont.h>
#include <jsi/jsi.h>

@implementation RCTMarkdownUtils {
MarkdownParser *_markdownParser;
NSString *_prevInputString;
NSAttributedString *_prevAttributedString;
NSDictionary<NSAttributedStringKey, id> *_prevTextAttributes;
__weak RCTMarkdownStyle *_prevMarkdownStyle;
__weak NSNumber *_prevParserId;
}

- (instancetype)init
{
if (self = [super init]) {
_markdownParser = [MarkdownParser new];
}

return self;
}

- (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withAttributes:(nullable NSDictionary<NSAttributedStringKey,id> *)attributes
{
@synchronized (self) {
Expand All @@ -26,43 +36,8 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA
return _prevAttributedString;
}

static std::mutex runtimeMutex;
auto lock = std::lock_guard<std::mutex>(runtimeMutex);

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

auto markdownWorklet = expensify::livemarkdown::getMarkdownWorklet([_parserId intValue]);

NSMutableArray *markdownRanges = [[NSMutableArray alloc] init];

try {
const auto &text = jsi::String::createFromUtf8(rt, [inputString UTF8String]);
const auto &output = markdownRuntime->runGuarded(markdownWorklet, text);
const auto &ranges = output.asObject(rt).asArray(rt);

for (size_t i = 0, n = ranges.size(rt); i < n; ++i) {
const auto &item = ranges.getValueAtIndex(rt, i).asObject(rt);
const auto &type = item.getProperty(rt, "type").asString(rt).utf8(rt);
const auto &start = static_cast<int>(item.getProperty(rt, "start").asNumber());
const auto &length = static_cast<int>(item.getProperty(rt, "length").asNumber());
const auto &depth = item.hasProperty(rt, "depth") ? static_cast<int>(item.getProperty(rt, "depth").asNumber()) : 1;

NSRange range = NSMakeRange(start, length);
MarkdownRange *markdownRange = [[MarkdownRange alloc] initWithType:@(type.c_str()) range:range depth:depth];
[markdownRanges addObject:markdownRange];
}
} catch (const jsi::JSError &error) {
RCTLogWarn(@"[react-native-live-markdown] Incorrect schema of worklet parser output: %s", error.getMessage().c_str());
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:inputString attributes:attributes];
_prevInputString = inputString;
_prevAttributedString = attributedString;
_prevTextAttributes = attributes;
_prevMarkdownStyle = _markdownStyle;
_prevParserId = _parserId;
return attributedString;
}

NSArray<MarkdownRange *> *markdownRanges = [_markdownParser parse:inputString withParserId:_parserId];

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:inputString attributes:attributes];
[attributedString beginEditing];

Expand Down
8 changes: 4 additions & 4 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1497,7 +1497,7 @@ PODS:
- React-logger (= 0.75.3)
- React-perflogger (= 0.75.3)
- React-utils (= 0.75.3)
- RNLiveMarkdown (0.1.188):
- RNLiveMarkdown (0.1.190):
- DoubleConversion
- glog
- hermes-engine
Expand All @@ -1517,10 +1517,10 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNLiveMarkdown/newarch (= 0.1.188)
- RNLiveMarkdown/newarch (= 0.1.190)
- RNReanimated/worklets
- Yoga
- RNLiveMarkdown/newarch (0.1.188):
- RNLiveMarkdown/newarch (0.1.190):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -1897,7 +1897,7 @@ SPEC CHECKSUMS:
React-utils: f2afa6acd905ca2ce7bb8ffb4a22f7f8a12534e8
ReactCodegen: e35c23cdd36922f6d2990c6c1f1b022ade7ad74d
ReactCommon: 289214026502e6a93484f4a46bcc0efa4f3f2864
RNLiveMarkdown: c0d3ebfa32b4a6a33f1dbfc76ab9a06e516bfb1a
RNLiveMarkdown: a210cbb45b6cb9db0b28ef09aafdc9c77424dd38
RNReanimated: ab6c33a61e90c4cbe5dbcbe65bd6c7cb3be167e6
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
Yoga: 1354c027ab07c7736f99a3bef16172d6f1b12b47
Expand Down

0 comments on commit 02793be

Please sign in to comment.