diff --git a/apple/MarkdownParser.h b/apple/MarkdownParser.h new file mode 100644 index 00000000..7ec8d195 --- /dev/null +++ b/apple/MarkdownParser.h @@ -0,0 +1,12 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MarkdownParser : NSObject + +- (NSArray *)parse:(NSString *)text withParserId:(NSNumber *)parserId; + +NS_ASSUME_NONNULL_END + +@end diff --git a/apple/MarkdownParser.mm b/apple/MarkdownParser.mm new file mode 100644 index 00000000..c9f3d018 --- /dev/null +++ b/apple/MarkdownParser.mm @@ -0,0 +1,68 @@ +#import "MarkdownParser.h" +#import +#import +#import + +@implementation MarkdownParser { + NSString *_prevText; + NSNumber *_prevParserId; + NSArray *_prevMarkdownRanges; +} + +- (NSArray *)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(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 *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(item.getProperty(rt, "start").asNumber()); + const auto &length = static_cast(item.getProperty(rt, "length").asNumber()); + const auto &depth = item.hasProperty(rt, "depth") ? static_cast(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 diff --git a/apple/RCTMarkdownUtils.mm b/apple/RCTMarkdownUtils.mm index c9ceed1c..11a20bca 100644 --- a/apple/RCTMarkdownUtils.mm +++ b/apple/RCTMarkdownUtils.mm @@ -1,12 +1,13 @@ #import #import -#import +#import #import "react_native_assert.h" #import #import #include @implementation RCTMarkdownUtils { + MarkdownParser *_markdownParser; NSString *_prevInputString; NSAttributedString *_prevAttributedString; NSDictionary *_prevTextAttributes; @@ -14,6 +15,15 @@ @implementation RCTMarkdownUtils { __weak NSNumber *_prevParserId; } +- (instancetype)init +{ + if (self = [super init]) { + _markdownParser = [MarkdownParser new]; + } + + return self; +} + - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withAttributes:(nullable NSDictionary *)attributes { @synchronized (self) { @@ -26,43 +36,8 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA return _prevAttributedString; } - static std::mutex runtimeMutex; - auto lock = std::lock_guard(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(item.getProperty(rt, "start").asNumber()); - const auto &length = static_cast(item.getProperty(rt, "length").asNumber()); - const auto &depth = item.hasProperty(rt, "depth") ? static_cast(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 *markdownRanges = [_markdownParser parse:inputString withParserId:_parserId]; + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:inputString attributes:attributes]; [attributedString beginEditing]; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 3a061a0a..d9e49e6f 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -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 @@ -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 @@ -1897,7 +1897,7 @@ SPEC CHECKSUMS: React-utils: f2afa6acd905ca2ce7bb8ffb4a22f7f8a12534e8 ReactCodegen: e35c23cdd36922f6d2990c6c1f1b022ade7ad74d ReactCommon: 289214026502e6a93484f4a46bcc0efa4f3f2864 - RNLiveMarkdown: c0d3ebfa32b4a6a33f1dbfc76ab9a06e516bfb1a + RNLiveMarkdown: a210cbb45b6cb9db0b28ef09aafdc9c77424dd38 RNReanimated: ab6c33a61e90c4cbe5dbcbe65bd6c7cb3be167e6 SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: 1354c027ab07c7736f99a3bef16172d6f1b12b47