@apollohg/react-native-rich-text-editor 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +160 -0
- package/README.md +259 -0
- package/RUST-STANDARD-LIBRARY-NOTICES.html +8266 -0
- package/ReactNativeProseEditor.podspec +59 -0
- package/THIRD_PARTY_NOTICES.md +31155 -0
- package/android/build.gradle +188 -0
- package/android/consumer-rules.pro +18 -0
- package/android/expo/build.gradle +17 -0
- package/android/src/debug/java/com/apollohg/editor/viewer/PreparedProseDrawInstrumentation.kt +7 -0
- package/android/src/main/assets/editor-icons/MaterialIcons.json +2236 -0
- package/android/src/main/assets/editor-icons/MaterialIcons.ttf +0 -0
- package/android/src/main/java/com/apollohg/editor/AndroidApiCompat.kt +19 -0
- package/android/src/main/java/com/apollohg/editor/AndroidCollaborationTransport.kt +860 -0
- package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +91 -0
- package/android/src/main/java/com/apollohg/editor/CodeHighlighting.kt +141 -0
- package/android/src/main/java/com/apollohg/editor/DecodedBitmapBudget.kt +250 -0
- package/android/src/main/java/com/apollohg/editor/EditorAddons.kt +133 -0
- package/android/src/main/java/com/apollohg/editor/EditorBlockSpacing.kt +51 -0
- package/android/src/main/java/com/apollohg/editor/EditorBoxDrawable.kt +16 -0
- package/android/src/main/java/com/apollohg/editor/EditorBoxDrawing.kt +234 -0
- package/android/src/main/java/com/apollohg/editor/EditorCheckboxSpan.kt +87 -0
- package/android/src/main/java/com/apollohg/editor/EditorCodeHighlighting.kt +120 -0
- package/android/src/main/java/com/apollohg/editor/EditorDecorationLayer.java +18 -0
- package/android/src/main/java/com/apollohg/editor/EditorDocumentLayout.kt +626 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +1042 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextAnnotations.kt +191 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextCaret.kt +100 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextClipboard.kt +101 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextComposition.kt +220 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextConfiguration.kt +316 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextCorrection.kt +303 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextDeferredUpdates.kt +327 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextDeletion.kt +258 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextDiagnostics.kt +83 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextDragDrop.kt +67 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextExternalComposition.kt +357 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextHardwareInput.kt +277 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextImages.kt +280 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextInputLifecycle.kt +183 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextInputPreparation.kt +79 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextInputTraits.kt +384 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextLayout.kt +100 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextNativeMutation.kt +243 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextPlaceholder.kt +154 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextRenderPatch.kt +347 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextRendering.kt +313 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextRustCommands.kt +281 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextSelection.kt +352 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextState.kt +113 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextTaskMarkers.kt +92 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextTextCommit.kt +327 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextToolbar.kt +84 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextUpdates.kt +245 -0
- package/android/src/main/java/com/apollohg/editor/EditorGlyphHeightCursorDrawable.kt +42 -0
- package/android/src/main/java/com/apollohg/editor/EditorHeightBehavior.kt +13 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +923 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputCoordinates.kt +191 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputCorrection.kt +107 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputCursorGeometry.kt +128 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputSnapshots.kt +134 -0
- package/android/src/main/java/com/apollohg/editor/EditorInputSurroundingDeletion.kt +321 -0
- package/android/src/main/java/com/apollohg/editor/EditorKeyboardToolbarView.kt +851 -0
- package/android/src/main/java/com/apollohg/editor/EditorMentionStyle.kt +192 -0
- package/android/src/main/java/com/apollohg/editor/EditorReconciliationWatcher.kt +46 -0
- package/android/src/main/java/com/apollohg/editor/EditorStyleSheet.kt +303 -0
- package/android/src/main/java/com/apollohg/editor/EditorStyleSheetSpans.kt +253 -0
- package/android/src/main/java/com/apollohg/editor/EditorSurfaceInputConnection.kt +67 -0
- package/android/src/main/java/com/apollohg/editor/EditorTextDecorationDrawing.kt +143 -0
- package/android/src/main/java/com/apollohg/editor/EditorTextSurface.kt +712 -0
- package/android/src/main/java/com/apollohg/editor/EditorTextSurfaceDragDrop.kt +94 -0
- package/android/src/main/java/com/apollohg/editor/EditorTextSurfaceInteraction.kt +708 -0
- package/android/src/main/java/com/apollohg/editor/EditorTheme.kt +650 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2Adapter.kt +912 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2AdapterMutations.kt +402 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2AdapterRendering.kt +123 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2AdapterSelection.kt +203 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2Backend.kt +120 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2Driver.kt +87 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2Registry.kt +97 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2RenderValidation.kt +409 -0
- package/android/src/main/java/com/apollohg/editor/ExpoAutoGrowStyleSizePublisher.kt +34 -0
- package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +266 -0
- package/android/src/main/java/com/apollohg/editor/ImeTextCoordinateMapper.kt +102 -0
- package/android/src/main/java/com/apollohg/editor/KeyboardToolbarImeAnimationController.kt +101 -0
- package/android/src/main/java/com/apollohg/editor/MentionSuggestionChipView.kt +234 -0
- package/android/src/main/java/com/apollohg/editor/NativeCollaborationTransportRegistry.kt +493 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +813 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewAtoms.kt +68 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewBinding.kt +126 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewDiagnostics.kt +208 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewErrors.kt +146 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewEvents.kt +150 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewExternalComposition.kt +39 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewFocus.kt +176 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewLifecycle.kt +302 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewMentions.kt +259 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewNativeActions.kt +179 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewOutsideTap.kt +214 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewPendingUpdates.kt +376 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewProperties.kt +234 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewPropertyRetries.kt +197 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewToolbar.kt +285 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewTypes.kt +28 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoViewUpdates.kt +271 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +687 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorOutsideTapDispatcher.kt +380 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorV2Numbers.kt +45 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorViewRegistry.kt +372 -0
- package/android/src/main/java/com/apollohg/editor/NativeImagePipeline.kt +22 -0
- package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +193 -0
- package/android/src/main/java/com/apollohg/editor/NativeToolbarIcons.kt +177 -0
- package/android/src/main/java/com/apollohg/editor/NativeToolbarItem.kt +274 -0
- package/android/src/main/java/com/apollohg/editor/OkHttpCollaborationSocket.kt +102 -0
- package/android/src/main/java/com/apollohg/editor/OrderedListMarkerFormatter.kt +130 -0
- package/android/src/main/java/com/apollohg/editor/PointerTransparentView.java +18 -0
- package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +128 -0
- package/android/src/main/java/com/apollohg/editor/ProseViewerView.kt +854 -0
- package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +314 -0
- package/android/src/main/java/com/apollohg/editor/RenderBlockSpans.kt +314 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +224 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridgeBlockStyles.kt +407 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridgeElements.kt +544 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridgeInlineAndAtoms.kt +631 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridgeParagraphs.kt +215 -0
- package/android/src/main/java/com/apollohg/editor/RenderImageDecoder.kt +748 -0
- package/android/src/main/java/com/apollohg/editor/RenderImageLoader.kt +900 -0
- package/android/src/main/java/com/apollohg/editor/RenderLayoutConstants.kt +117 -0
- package/android/src/main/java/com/apollohg/editor/RenderMediaSpans.kt +337 -0
- package/android/src/main/java/com/apollohg/editor/RenderParagraphSpans.kt +182 -0
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +951 -0
- package/android/src/main/java/com/apollohg/editor/SharedNativeImagePipeline.kt +78 -0
- package/android/src/main/java/com/apollohg/editor/UniffiEditorV2Backend.kt +261 -0
- package/android/src/main/java/com/apollohg/editor/viewer/AndroidProseLayoutEngine.kt +1656 -0
- package/android/src/main/java/com/apollohg/editor/viewer/FabricAttachmentSidecars.kt +80 -0
- package/android/src/main/java/com/apollohg/editor/viewer/FabricLeaseHandleBridge.kt +59 -0
- package/android/src/main/java/com/apollohg/editor/viewer/FallbackSelectionGeometry.kt +450 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseDrawingView.kt +678 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseInstrumentation.kt +608 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseLayout.kt +186 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseLayoutCache.kt +393 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseLayoutRegistry.kt +828 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseTheme.kt +316 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseViewerManager.kt +900 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedProseViewerPackage.kt +16 -0
- package/android/src/main/java/com/apollohg/editor/viewer/PreparedViewerAtoms.kt +89 -0
- package/android/src/main/java/com/apollohg/editor/viewer/ViewerCodeHighlighting.kt +91 -0
- package/android/src/main/java/com/apollohg/editor/viewer/ViewerDocument.kt +453 -0
- package/android/src/main/java/com/apollohg/editor/viewer/ViewerFontEnvironment.kt +282 -0
- package/android/src/main/java/com/apollohg/editor/viewer/ViewerImagePipeline.kt +460 -0
- package/android/src/main/jni/CMakeLists.txt +85 -0
- package/android/src/main/jni/PreparedProseViewerMeasurementsManager.cpp +334 -0
- package/android/src/main/jni/ReactNativeProseEditorSpec.h +17 -0
- package/android/src/main/jni/reactnativeproseeditor.h +6 -0
- package/android/src/main/res/values/attrs.xml +6 -0
- package/android/src/release/java/com/apollohg/editor/viewer/PreparedProseDrawInstrumentation.kt +9 -0
- package/app.plugin.js +62 -0
- package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseMeasurementsManager.h +62 -0
- package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseViewerComponentDescriptor.h +30 -0
- package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseViewerShadowNode.cpp +192 -0
- package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseViewerShadowNode.h +46 -0
- package/common/cpp/react/renderer/components/PreparedProseViewer/PreparedProseViewerState.h +145 -0
- package/dist/AtomHost.d.ts +17 -0
- package/dist/AtomHost.js +135 -0
- package/dist/DefaultAtomChip.d.ts +2 -0
- package/dist/DefaultAtomChip.js +22 -0
- package/dist/EditorAddon.d.ts +32 -0
- package/dist/EditorAddon.js +29 -0
- package/dist/EditorAddonNormalization.d.ts +6 -0
- package/dist/EditorAddonNormalization.js +48 -0
- package/dist/EditorMentionThemeNormalization.d.ts +8 -0
- package/dist/EditorMentionThemeNormalization.js +22 -0
- package/dist/EditorMentionThemeValidation.d.ts +12 -0
- package/dist/EditorMentionThemeValidation.js +95 -0
- package/dist/EditorStyleSheet.d.ts +4 -0
- package/dist/EditorStyleSheet.js +10 -0
- package/dist/EditorStyleSheetNormalization.d.ts +3 -0
- package/dist/EditorStyleSheetNormalization.js +226 -0
- package/dist/EditorStyleSheetTypes.d.ts +151 -0
- package/dist/EditorStyleSheetTypes.js +2 -0
- package/dist/EditorTheme.d.ts +117 -0
- package/dist/EditorTheme.js +41 -0
- package/dist/EditorToolbar.d.ts +4 -0
- package/dist/EditorToolbar.js +17 -0
- package/dist/EditorToolbarComponent.d.ts +13 -0
- package/dist/EditorToolbarComponent.js +24 -0
- package/dist/EditorToolbarItems.d.ts +9 -0
- package/dist/EditorToolbarItems.js +58 -0
- package/dist/EditorToolbarRegistry.d.ts +59 -0
- package/dist/EditorToolbarRegistry.js +175 -0
- package/dist/EditorToolbarTypes.d.ts +273 -0
- package/dist/EditorToolbarTypes.js +2 -0
- package/dist/EditorToolbarVisuals.d.ts +154 -0
- package/dist/EditorToolbarVisuals.js +228 -0
- package/dist/EditorUpdateRevision.d.ts +8 -0
- package/dist/EditorUpdateRevision.js +22 -0
- package/dist/ExternalTextComposition.d.ts +45 -0
- package/dist/ExternalTextComposition.js +537 -0
- package/dist/ImageLoadingPolicy.d.ts +54 -0
- package/dist/ImageLoadingPolicy.js +60 -0
- package/dist/NativeEditorBoundaryError.d.ts +111 -0
- package/dist/NativeEditorBoundaryError.js +268 -0
- package/dist/NativeEditorBridge.d.ts +9 -0
- package/dist/NativeEditorBridge.js +37 -0
- package/dist/NativeEditorCollaborationTransport.d.ts +154 -0
- package/dist/NativeEditorCollaborationTransport.js +275 -0
- package/dist/NativeEditorCreateConfig.d.ts +16 -0
- package/dist/NativeEditorCreateConfig.js +249 -0
- package/dist/NativeEditorCreateJson.d.ts +76 -0
- package/dist/NativeEditorCreateJson.js +474 -0
- package/dist/NativeEditorCreateValidation.d.ts +32 -0
- package/dist/NativeEditorCreateValidation.js +150 -0
- package/dist/NativeEditorDocumentBridge.d.ts +72 -0
- package/dist/NativeEditorDocumentBridge.js +305 -0
- package/dist/NativeEditorDocumentHandle.d.ts +70 -0
- package/dist/NativeEditorDocumentHandle.js +89 -0
- package/dist/NativeEditorLocalAwareness.d.ts +34 -0
- package/dist/NativeEditorLocalAwareness.js +153 -0
- package/dist/NativeEditorNativeModule.d.ts +48 -0
- package/dist/NativeEditorNativeModule.js +34 -0
- package/dist/NativeEditorRenderNormalization.d.ts +22 -0
- package/dist/NativeEditorRenderNormalization.js +274 -0
- package/dist/NativeEditorResultNormalization.d.ts +108 -0
- package/dist/NativeEditorResultNormalization.js +400 -0
- package/dist/NativeEditorTypes.d.ts +318 -0
- package/dist/NativeEditorTypes.js +15 -0
- package/dist/NativeEditorV2Decimal.d.ts +5 -0
- package/dist/NativeEditorV2Decimal.js +18 -0
- package/dist/NativeProseViewer.d.ts +122 -0
- package/dist/NativeProseViewer.js +130 -0
- package/dist/NativeRichTextEditor.d.ts +3 -0
- package/dist/NativeRichTextEditor.js +6 -0
- package/dist/ResourceLimits.d.ts +100 -0
- package/dist/ResourceLimits.js +80 -0
- package/dist/RichTextEditor.d.ts +4 -0
- package/dist/RichTextEditor.js +25 -0
- package/dist/RichTextEditorNativeTypes.d.ts +122 -0
- package/dist/RichTextEditorNativeTypes.js +2 -0
- package/dist/RichTextEditorNativeView.d.ts +12 -0
- package/dist/RichTextEditorNativeView.js +15 -0
- package/dist/RichTextEditorSerialization.d.ts +53 -0
- package/dist/RichTextEditorSerialization.js +283 -0
- package/dist/RichTextEditorTypes.d.ts +281 -0
- package/dist/RichTextEditorTypes.js +2 -0
- package/dist/ViewerConfiguration.d.ts +22 -0
- package/dist/ViewerConfiguration.js +6 -0
- package/dist/YjsCollaboration.d.ts +146 -0
- package/dist/YjsCollaboration.js +496 -0
- package/dist/addons.d.ts +193 -0
- package/dist/addons.js +122 -0
- package/dist/atomConstants.d.ts +3 -0
- package/dist/atomConstants.js +6 -0
- package/dist/atomInstances.d.ts +35 -0
- package/dist/atomInstances.js +79 -0
- package/dist/atomPolicy.d.ts +4 -0
- package/dist/atomPolicy.js +47 -0
- package/dist/atomUpdates.d.ts +2 -0
- package/dist/atomUpdates.js +38 -0
- package/dist/atoms.d.ts +92 -0
- package/dist/atoms.js +222 -0
- package/dist/attributeValidation.d.ts +4 -0
- package/dist/attributeValidation.js +97 -0
- package/dist/contentExpression.d.ts +6 -0
- package/dist/contentExpression.js +335 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +78 -0
- package/dist/listMarkers.d.ts +15 -0
- package/dist/listMarkers.js +21 -0
- package/dist/schemaDefinition.d.ts +143 -0
- package/dist/schemaDefinition.js +206 -0
- package/dist/schemaDocument.d.ts +17 -0
- package/dist/schemaDocument.js +69 -0
- package/dist/schemaDocumentConstruction.d.ts +5 -0
- package/dist/schemaDocumentConstruction.js +125 -0
- package/dist/schemaNormalization.d.ts +29 -0
- package/dist/schemaNormalization.js +313 -0
- package/dist/schemaPresets.d.ts +68 -0
- package/dist/schemaPresets.js +198 -0
- package/dist/schemaResolution.d.ts +9 -0
- package/dist/schemaResolution.js +211 -0
- package/dist/schemas.d.ts +5 -0
- package/dist/schemas.js +28 -0
- package/dist/specs/PreparedProseViewerNativeComponent.d.ts +37 -0
- package/dist/specs/PreparedProseViewerNativeComponent.js +6 -0
- package/dist/useEditorToolbarInteractions.d.ts +12 -0
- package/dist/useEditorToolbarInteractions.js +218 -0
- package/dist/useEditorToolbarItems.d.ts +8 -0
- package/dist/useEditorToolbarItems.js +280 -0
- package/dist/useEditorToolbarPresentation.d.ts +4 -0
- package/dist/useEditorToolbarPresentation.js +203 -0
- package/dist/useEditorToolbarState.d.ts +63 -0
- package/dist/useEditorToolbarState.js +108 -0
- package/dist/useFocusPreservingFrames.d.ts +17 -0
- package/dist/useFocusPreservingFrames.js +108 -0
- package/dist/useNativeEditor.d.ts +66 -0
- package/dist/useNativeEditor.js +370 -0
- package/dist/useRichTextEditorCommands.d.ts +21 -0
- package/dist/useRichTextEditorCommands.js +299 -0
- package/dist/useRichTextEditorEvents.d.ts +18 -0
- package/dist/useRichTextEditorEvents.js +216 -0
- package/dist/useRichTextEditorMentions.d.ts +11 -0
- package/dist/useRichTextEditorMentions.js +316 -0
- package/dist/useRichTextEditorPresentation.d.ts +5 -0
- package/dist/useRichTextEditorPresentation.js +178 -0
- package/dist/useRichTextEditorState.d.ts +180 -0
- package/dist/useRichTextEditorState.js +405 -0
- package/dist/useRichTextEditorUpdates.d.ts +10 -0
- package/dist/useRichTextEditorUpdates.js +308 -0
- package/dist/useViewerAtoms.d.ts +38 -0
- package/dist/useViewerAtoms.js +288 -0
- package/expo-module.config.json +13 -0
- package/ios/EditorAddons.swift +354 -0
- package/ios/EditorCodeHighlighting.swift +184 -0
- package/ios/EditorCore.xcframework/Info.plist +44 -0
- package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/ios/EditorInteractionOverlays.swift +697 -0
- package/ios/EditorLayoutManager.swift +971 -0
- package/ios/EditorNodeTypes.swift +37 -0
- package/ios/EditorStyleSheet.swift +461 -0
- package/ios/EditorTextView+AttributedPatch.swift +305 -0
- package/ios/EditorTextView+Binding.swift +61 -0
- package/ios/EditorTextView+Caret.swift +333 -0
- package/ios/EditorTextView+Commands.swift +203 -0
- package/ios/EditorTextView+Delegate.swift +65 -0
- package/ios/EditorTextView+Diagnostics.swift +225 -0
- package/ios/EditorTextView+DragDrop.swift +290 -0
- package/ios/EditorTextView+ExternalComposition.swift +364 -0
- package/ios/EditorTextView+Images.swift +354 -0
- package/ios/EditorTextView+InputTraits.swift +200 -0
- package/ios/EditorTextView+KeyboardInput.swift +318 -0
- package/ios/EditorTextView+Layout.swift +245 -0
- package/ios/EditorTextView+MarkedText.swift +267 -0
- package/ios/EditorTextView+NativeInputLifecycle.swift +372 -0
- package/ios/EditorTextView+NativeMutation.swift +471 -0
- package/ios/EditorTextView+Reconciliation.swift +97 -0
- package/ios/EditorTextView+RenderPatch.swift +573 -0
- package/ios/EditorTextView+Rendering.swift +539 -0
- package/ios/EditorTextView+Selection.swift +542 -0
- package/ios/EditorTextView.swift +953 -0
- package/ios/EditorTheme.swift +626 -0
- package/ios/EditorV2Adapter+Commands.swift +204 -0
- package/ios/EditorV2Adapter+Envelope.swift +238 -0
- package/ios/EditorV2Adapter+Mutation.swift +392 -0
- package/ios/EditorV2Adapter+Registry.swift +185 -0
- package/ios/EditorV2Adapter+RenderState.swift +360 -0
- package/ios/EditorV2Adapter+RenderValidation.swift +420 -0
- package/ios/EditorV2Adapter+Selection.swift +329 -0
- package/ios/EditorV2Adapter+TextCommands.swift +430 -0
- package/ios/EditorV2Adapter.swift +376 -0
- package/ios/EditorV2Shadow.swift +353 -0
- package/ios/Generated_editor_core.swift +2378 -0
- package/ios/NativeCodeHighlighting.swift +159 -0
- package/ios/NativeCollaborationSocket.swift +144 -0
- package/ios/NativeCollaborationTransport+Protocol.swift +258 -0
- package/ios/NativeCollaborationTransport+Socket.swift +379 -0
- package/ios/NativeCollaborationTransport+Validation.swift +103 -0
- package/ios/NativeCollaborationTransport.swift +322 -0
- package/ios/NativeCollaborationTransportRegistry.swift +521 -0
- package/ios/NativeEditorExpoView+AccessoryRetry.swift +219 -0
- package/ios/NativeEditorExpoView+Binding.swift +268 -0
- package/ios/NativeEditorExpoView+Diagnostics.swift +66 -0
- package/ios/NativeEditorExpoView+Events.swift +131 -0
- package/ios/NativeEditorExpoView+Focus.swift +169 -0
- package/ios/NativeEditorExpoView+MentionRetry.swift +288 -0
- package/ios/NativeEditorExpoView+Mentions.swift +288 -0
- package/ios/NativeEditorExpoView+Properties.swift +300 -0
- package/ios/NativeEditorExpoView+Toolbar.swift +86 -0
- package/ios/NativeEditorExpoView+Updates.swift +312 -0
- package/ios/NativeEditorExpoView.swift +349 -0
- package/ios/NativeEditorModule.swift +856 -0
- package/ios/NativeEditorViewRegistry.swift +226 -0
- package/ios/NativeToolbar+ButtonStyle.swift +214 -0
- package/ios/NativeToolbar+Buttons.swift +228 -0
- package/ios/NativeToolbar+Diagnostics.swift +114 -0
- package/ios/NativeToolbar+Menu.swift +101 -0
- package/ios/NativeToolbar+Models.swift +519 -0
- package/ios/NativeToolbar+Placeholder.swift +39 -0
- package/ios/NativeToolbar.swift +612 -0
- package/ios/OrderedListMarkerFormatter.swift +103 -0
- package/ios/PositionBridge.swift +596 -0
- package/ios/ProseViewerView.swift +462 -0
- package/ios/RenderBridge+Atoms.swift +288 -0
- package/ios/RenderBridge+Attachments.swift +246 -0
- package/ios/RenderBridge+BlockBoundaries.swift +313 -0
- package/ios/RenderBridge+Style.swift +343 -0
- package/ios/RenderBridge.swift +704 -0
- package/ios/RichTextEditorView.swift +930 -0
- package/ios/SharedNativeImagePipeline+Cache.swift +189 -0
- package/ios/SharedNativeImagePipeline+Transport.swift +328 -0
- package/ios/SharedNativeImagePipeline.swift +533 -0
- package/ios/Viewer/CoreTextProseLayoutEngine+AttributedText.swift +436 -0
- package/ios/Viewer/CoreTextProseLayoutEngine+Models.swift +322 -0
- package/ios/Viewer/CoreTextProseLayoutEngine.swift +635 -0
- package/ios/Viewer/Fabric/PREPPreparedProseViewerComponentView.h +4 -0
- package/ios/Viewer/Fabric/PREPPreparedProseViewerComponentView.mm +767 -0
- package/ios/Viewer/Fabric/PreparedProseMeasurementsManager.mm +118 -0
- package/ios/Viewer/NativeSVGImageDecoder.h +9 -0
- package/ios/Viewer/NativeSVGImageDecoder.mm +106 -0
- package/ios/Viewer/NativeSVGPreflight.h +11 -0
- package/ios/Viewer/NativeSVGPreflight.mm +219 -0
- package/ios/Viewer/PreparedProseDrawingView.swift +639 -0
- package/ios/Viewer/PreparedProseInstrumentation.swift +387 -0
- package/ios/Viewer/PreparedProseLayout.swift +326 -0
- package/ios/Viewer/PreparedProseLayoutCache.swift +766 -0
- package/ios/Viewer/PreparedProseLayoutRegistry+Artifacts.swift +377 -0
- package/ios/Viewer/PreparedProseLayoutRegistry+Fabric.swift +582 -0
- package/ios/Viewer/PreparedProseLayoutRegistry+FabricOwnership.swift +128 -0
- package/ios/Viewer/PreparedProseLayoutRegistry.swift +381 -0
- package/ios/Viewer/ViewerDocument.swift +575 -0
- package/ios/Viewer/ViewerFontEnvironment.swift +283 -0
- package/ios/Viewer/ViewerImagePipeline.swift +478 -0
- package/ios/editor_coreFFI/editor_coreFFI.h +1023 -0
- package/ios/editor_coreFFI/module.modulemap +7 -0
- package/package.json +151 -0
- package/react-native.config.js +17 -0
- package/rust/android/arm64-v8a/libeditor_core.so +0 -0
- package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
- package/rust/android/x86/libeditor_core.so +0 -0
- package/rust/android/x86_64/libeditor_core.so +0 -0
- package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +3146 -0
- package/src/AtomHost.tsx +180 -0
- package/src/DefaultAtomChip.tsx +27 -0
- package/src/EditorAddon.ts +65 -0
- package/src/EditorAddonNormalization.ts +49 -0
- package/src/EditorMentionThemeNormalization.ts +25 -0
- package/src/EditorMentionThemeValidation.ts +133 -0
- package/src/EditorStyleSheet.ts +9 -0
- package/src/EditorStyleSheetNormalization.ts +219 -0
- package/src/EditorStyleSheetTypes.ts +189 -0
- package/src/EditorTheme.ts +196 -0
- package/src/EditorToolbar.tsx +31 -0
- package/src/EditorToolbarComponent.tsx +23 -0
- package/src/EditorToolbarItems.tsx +64 -0
- package/src/EditorToolbarRegistry.tsx +254 -0
- package/src/EditorToolbarTypes.tsx +322 -0
- package/src/EditorToolbarVisuals.tsx +270 -0
- package/src/EditorUpdateRevision.ts +26 -0
- package/src/ExternalTextComposition.ts +658 -0
- package/src/ImageLoadingPolicy.ts +125 -0
- package/src/NativeEditorBoundaryError.ts +345 -0
- package/src/NativeEditorBridge.ts +93 -0
- package/src/NativeEditorCollaborationTransport.ts +493 -0
- package/src/NativeEditorCreateConfig.ts +326 -0
- package/src/NativeEditorCreateJson.ts +579 -0
- package/src/NativeEditorCreateValidation.ts +178 -0
- package/src/NativeEditorDocumentBridge.ts +485 -0
- package/src/NativeEditorDocumentHandle.ts +168 -0
- package/src/NativeEditorLocalAwareness.ts +168 -0
- package/src/NativeEditorNativeModule.ts +97 -0
- package/src/NativeEditorRenderNormalization.ts +320 -0
- package/src/NativeEditorResultNormalization.ts +469 -0
- package/src/NativeEditorTypes.ts +366 -0
- package/src/NativeEditorV2Decimal.ts +13 -0
- package/src/NativeProseViewer.tsx +356 -0
- package/src/NativeRichTextEditor.tsx +33 -0
- package/src/ResourceLimits.ts +187 -0
- package/src/RichTextEditor.tsx +27 -0
- package/src/RichTextEditorNativeTypes.tsx +144 -0
- package/src/RichTextEditorNativeView.tsx +21 -0
- package/src/RichTextEditorSerialization.tsx +381 -0
- package/src/RichTextEditorTypes.tsx +327 -0
- package/src/ViewerConfiguration.ts +23 -0
- package/src/YjsCollaboration.ts +692 -0
- package/src/addons.ts +311 -0
- package/src/atomConstants.ts +3 -0
- package/src/atomInstances.ts +123 -0
- package/src/atomPolicy.ts +47 -0
- package/src/atomUpdates.ts +52 -0
- package/src/atoms.ts +379 -0
- package/src/attributeValidation.ts +116 -0
- package/src/contentExpression.ts +338 -0
- package/src/index.ts +295 -0
- package/src/listMarkers.ts +18 -0
- package/src/schemaDefinition.ts +395 -0
- package/src/schemaDocument.ts +92 -0
- package/src/schemaDocumentConstruction.ts +148 -0
- package/src/schemaNormalization.ts +367 -0
- package/src/schemaPresets.ts +250 -0
- package/src/schemaResolution.ts +242 -0
- package/src/schemas.ts +43 -0
- package/src/specs/PreparedProseViewerNativeComponent.ts +40 -0
- package/src/useEditorToolbarInteractions.tsx +329 -0
- package/src/useEditorToolbarItems.tsx +390 -0
- package/src/useEditorToolbarPresentation.tsx +483 -0
- package/src/useEditorToolbarState.tsx +172 -0
- package/src/useFocusPreservingFrames.ts +171 -0
- package/src/useNativeEditor.ts +534 -0
- package/src/useRichTextEditorCommands.tsx +478 -0
- package/src/useRichTextEditorEvents.tsx +342 -0
- package/src/useRichTextEditorMentions.tsx +441 -0
- package/src/useRichTextEditorPresentation.tsx +563 -0
- package/src/useRichTextEditorState.tsx +620 -0
- package/src/useRichTextEditorUpdates.tsx +403 -0
- package/src/useViewerAtoms.tsx +427 -0
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// UITextView that intercepts input and routes it through editor-core.
|
|
5
|
+
///
|
|
6
|
+
/// The text view is a rendering surface, not a text engine: intent (typing,
|
|
7
|
+
/// deleting, pasting, autocorrect) goes to Rust, which returns render elements
|
|
8
|
+
/// that RenderBridge converts back to NSAttributedString.
|
|
9
|
+
///
|
|
10
|
+
/// IME: UITextView owns marked text during composition so the user sees it;
|
|
11
|
+
/// `unmarkText` commits through Rust at the Rust-authorized range.
|
|
12
|
+
///
|
|
13
|
+
/// Every UITextView method runs on the main thread, and the UniFFI calls are
|
|
14
|
+
/// synchronous.
|
|
15
|
+
final class EditorTextView: UITextView, UIGestureRecognizerDelegate, UITextDragDelegate, UITextDropDelegate {
|
|
16
|
+
static let emptyBlockPlaceholderScalar = UnicodeScalar(0x200B)!
|
|
17
|
+
|
|
18
|
+
lazy var internalTextViewDelegate = EditorTextViewInternalDelegate(editor: self)
|
|
19
|
+
var imageLoadOwner: RenderImageLoadOwner?
|
|
20
|
+
|
|
21
|
+
override var undoManager: UndoManager? { nil }
|
|
22
|
+
|
|
23
|
+
// MARK: - Properties
|
|
24
|
+
|
|
25
|
+
/// The Rust editor instance ID (from editor_create / editor_create_with_max_length).
|
|
26
|
+
/// Set to 0 when no editor is bound.
|
|
27
|
+
var editorId: UInt64 = 0
|
|
28
|
+
let nativeBindingToken = UUID()
|
|
29
|
+
|
|
30
|
+
/// Guard flag to prevent re-entrant input interception while we're
|
|
31
|
+
/// applying state from Rust (calling replaceCharacters on the text storage).
|
|
32
|
+
var isApplyingRustState = false
|
|
33
|
+
var localTextDragState = LocalTextDragState.idle
|
|
34
|
+
var visibleSelectionTintColor: UIColor = .systemBlue
|
|
35
|
+
var hidesNativeSelectionChrome = false
|
|
36
|
+
var isPreviewingImageResize = false
|
|
37
|
+
var allowImageResizing = true
|
|
38
|
+
|
|
39
|
+
override var isEditable: Bool {
|
|
40
|
+
didSet {
|
|
41
|
+
if oldValue, !isEditable {
|
|
42
|
+
_ = finishExternalTextComposition(
|
|
43
|
+
cause: "lifecycle",
|
|
44
|
+
finalText: nil,
|
|
45
|
+
cancel: true
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// The base font used for unstyled text. Configurable from React props.
|
|
52
|
+
var baseFont: UIFont = .systemFont(ofSize: 16) {
|
|
53
|
+
didSet {
|
|
54
|
+
placeholderLabel.font = resolvedDefaultFont()
|
|
55
|
+
renderAppearanceRevision &+= 1
|
|
56
|
+
invalidateAutoGrowHeightMeasurement()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// The base text color. Configurable from React props.
|
|
61
|
+
var baseTextColor: UIColor = .label {
|
|
62
|
+
didSet {
|
|
63
|
+
renderAppearanceRevision &+= 1
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// The base background color before theme overrides.
|
|
68
|
+
var baseBackgroundColor: UIColor = .systemBackground
|
|
69
|
+
var baseTextContainerInset: UIEdgeInsets = .zero
|
|
70
|
+
var baseLineFragmentPadding: CGFloat = 0
|
|
71
|
+
|
|
72
|
+
/// Optional render theme supplied by React.
|
|
73
|
+
lazy var styleContentView: EditorStyleBoxView = {
|
|
74
|
+
let view = EditorStyleBoxView()
|
|
75
|
+
view.isUserInteractionEnabled = false
|
|
76
|
+
view.backgroundColor = .clear
|
|
77
|
+
insertSubview(view, at: 0)
|
|
78
|
+
return view
|
|
79
|
+
}()
|
|
80
|
+
|
|
81
|
+
let codeHighlightingSession = NativeCodeHighlightingSession()
|
|
82
|
+
var onCodeHighlightingError: ((Error) -> Void)?
|
|
83
|
+
var codeHighlighting: NativeCodeHighlightConfiguration? {
|
|
84
|
+
didSet {
|
|
85
|
+
guard oldValue != codeHighlighting else { return }
|
|
86
|
+
codeHighlightingSession.cancel()
|
|
87
|
+
restoreCodeHighlighting()
|
|
88
|
+
scheduleCodeHighlighting()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
var theme: EditorTheme? {
|
|
93
|
+
didSet {
|
|
94
|
+
renderAppearanceRevision &+= 1
|
|
95
|
+
placeholderLabel.font = resolvedDefaultFont()
|
|
96
|
+
placeholderLabel.textColor = theme?.placeholderColor ?? .placeholderText
|
|
97
|
+
if let sheet = theme?.styleSheet {
|
|
98
|
+
var attributes: [NSAttributedString.Key: Any] = [.font: resolvedDefaultFont(), .foregroundColor: theme?.placeholderColor ?? UIColor.placeholderText]
|
|
99
|
+
EditorStyleSheet.applyText(sheet["placeholder"], to: &attributes)
|
|
100
|
+
placeholderLabel.attributedText = NSAttributedString(string: placeholder, attributes: attributes)
|
|
101
|
+
} else { placeholderLabel.attributedText = nil; placeholderLabel.text = placeholder }
|
|
102
|
+
styleContentView.box = theme?.styleSheet?.box("content")
|
|
103
|
+
backgroundColor = theme?.backgroundColor ?? baseBackgroundColor
|
|
104
|
+
if let contentInsets = theme?.contentInsets {
|
|
105
|
+
textContainerInset = UIEdgeInsets(
|
|
106
|
+
top: contentInsets.top ?? 0,
|
|
107
|
+
left: contentInsets.left ?? 0,
|
|
108
|
+
bottom: contentInsets.bottom ?? 0,
|
|
109
|
+
right: contentInsets.right ?? 0
|
|
110
|
+
)
|
|
111
|
+
textContainer.lineFragmentPadding = 0
|
|
112
|
+
} else {
|
|
113
|
+
textContainerInset = baseTextContainerInset
|
|
114
|
+
textContainer.lineFragmentPadding = baseLineFragmentPadding
|
|
115
|
+
}
|
|
116
|
+
invalidateAutoGrowHeightMeasurement()
|
|
117
|
+
setNeedsLayout()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
var atomRenderConfiguration: AtomRenderConfiguration? {
|
|
122
|
+
didSet {
|
|
123
|
+
guard oldValue != atomRenderConfiguration else { return }
|
|
124
|
+
renderAppearanceRevision &+= 1
|
|
125
|
+
invalidateAutoGrowHeightMeasurement()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
var heightBehavior: EditorHeightBehavior = .fixed {
|
|
130
|
+
didSet {
|
|
131
|
+
guard oldValue != heightBehavior else { return }
|
|
132
|
+
isScrollEnabled = heightBehavior == .fixed
|
|
133
|
+
invalidateAutoGrowHeightMeasurement()
|
|
134
|
+
invalidateIntrinsicContentSize()
|
|
135
|
+
notifyHeightChangeIfNeeded(force: true)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
var onHeightMayChange: ((CGFloat) -> Void)?
|
|
140
|
+
var onViewportMayChange: (() -> Void)?
|
|
141
|
+
var onSelectionOrContentMayChange: (() -> Void)?
|
|
142
|
+
var onExternalUpdateReadinessMayChange: (() -> Void)?
|
|
143
|
+
var lastAutoGrowMeasuredHeight: CGFloat = 0
|
|
144
|
+
var lastAutoGrowMeasuredWidth: CGFloat = 0
|
|
145
|
+
var autoGrowHostHeight: CGFloat = 0
|
|
146
|
+
var autoGrowHeightCheckIsDirty = true
|
|
147
|
+
var lastHeightNotifyMeasureNanosForTesting: UInt64 = 0
|
|
148
|
+
var lastHeightNotifyCallbackNanosForTesting: UInt64 = 0
|
|
149
|
+
var lastHeightNotifyEnsureLayoutNanosForTesting: UInt64 = 0
|
|
150
|
+
var lastHeightNotifyUsedRectNanosForTesting: UInt64 = 0
|
|
151
|
+
var lastHeightNotifyContentSizeNanosForTesting: UInt64 = 0
|
|
152
|
+
var lastHeightNotifySizeThatFitsNanosForTesting: UInt64 = 0
|
|
153
|
+
|
|
154
|
+
/// Delegate for editor events.
|
|
155
|
+
weak var editorDelegate: EditorTextViewDelegate?
|
|
156
|
+
|
|
157
|
+
/// The plain text from the last Rust render, used by the reconciliation
|
|
158
|
+
/// fallback to detect unauthorized text storage mutations.
|
|
159
|
+
var lastAuthorizedTextStorage = NSMutableString()
|
|
160
|
+
var lastAuthorizedAttributedTextStorage = NSMutableAttributedString()
|
|
161
|
+
var lastAuthorizedText: String {
|
|
162
|
+
lastAuthorizedTextStorage as String
|
|
163
|
+
}
|
|
164
|
+
var lastRenderAppliedPatchForTesting: Bool = false
|
|
165
|
+
var onApplyingRustTextForTesting: (() -> Void)?
|
|
166
|
+
var captureApplyUpdateTraceForTesting = false
|
|
167
|
+
var lastApplyUpdateTraceForTesting: ApplyUpdateTrace?
|
|
168
|
+
var currentRenderBlocks: [[[String: Any]]]?
|
|
169
|
+
var currentRenderBlocksDocumentVersion: UInt64?
|
|
170
|
+
var recoveringRenderPatchBaseMismatch = false
|
|
171
|
+
var currentTopLevelChildMetadata: [TopLevelChildMetadata]?
|
|
172
|
+
var renderAppearanceRevision: UInt64 = 1
|
|
173
|
+
var lastAppliedRenderAppearanceRevision: UInt64 = 0
|
|
174
|
+
|
|
175
|
+
/// Number of times the reconciliation fallback has fired. Exposed for
|
|
176
|
+
/// monitoring / kill-condition telemetry.
|
|
177
|
+
var reconciliationCount: Int = 0
|
|
178
|
+
|
|
179
|
+
/// Logger for reconciliation events (visible in Console.app / device logs).
|
|
180
|
+
static let reconciliationLog = Logger(
|
|
181
|
+
subsystem: "com.apollohg.prose-editor",
|
|
182
|
+
category: "reconciliation"
|
|
183
|
+
)
|
|
184
|
+
static let inputLog = Logger(
|
|
185
|
+
subsystem: "com.apollohg.prose-editor",
|
|
186
|
+
category: "input"
|
|
187
|
+
)
|
|
188
|
+
static let updateLog = Logger(
|
|
189
|
+
subsystem: "com.apollohg.prose-editor",
|
|
190
|
+
category: "update"
|
|
191
|
+
)
|
|
192
|
+
static let selectionLog = Logger(
|
|
193
|
+
subsystem: "com.apollohg.prose-editor",
|
|
194
|
+
category: "selection"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
/// Tracks whether we're in a composition session (CJK / IME input).
|
|
198
|
+
var isComposing = false
|
|
199
|
+
var hasPendingCompositionForExternalRefresh: Bool { isComposing }
|
|
200
|
+
lazy var caretPlacementTapRecognizer: CaretPlacementTapRecognizer = {
|
|
201
|
+
let recognizer = CaretPlacementTapRecognizer(target: self, action: #selector(handleCaretPlacementTap(_:)))
|
|
202
|
+
recognizer.cancelsTouchesInView = false
|
|
203
|
+
recognizer.delaysTouchesBegan = false
|
|
204
|
+
recognizer.delaysTouchesEnded = false
|
|
205
|
+
recognizer.delegate = self
|
|
206
|
+
return recognizer
|
|
207
|
+
}()
|
|
208
|
+
lazy var imageSelectionTapRecognizer: UITapGestureRecognizer = {
|
|
209
|
+
let recognizer = UITapGestureRecognizer(target: self, action: #selector(handleImageSelectionTap(_:)))
|
|
210
|
+
recognizer.cancelsTouchesInView = true
|
|
211
|
+
recognizer.delaysTouchesBegan = false
|
|
212
|
+
recognizer.delaysTouchesEnded = false
|
|
213
|
+
recognizer.delegate = self
|
|
214
|
+
return recognizer
|
|
215
|
+
}()
|
|
216
|
+
|
|
217
|
+
/// Guards against reconciliation firing while we're already intercepting
|
|
218
|
+
/// and replaying a user input operation through Rust, including the
|
|
219
|
+
/// trailing UIKit text-storage callbacks that arrive on the next run loop.
|
|
220
|
+
var interceptedInputDepth = 0
|
|
221
|
+
var deferredInsertTexts: [String] = []
|
|
222
|
+
var deferredInsertDrainScheduled = false
|
|
223
|
+
var isReplayingDeferredInsertText = false
|
|
224
|
+
var reconciliationWorkScheduled = false
|
|
225
|
+
var nativeTextMutationCommitScheduled = false
|
|
226
|
+
var pendingNativeTextMutation: NativeTextMutation?
|
|
227
|
+
var nativeTextMutationGeneration: UInt64 = 0
|
|
228
|
+
var nativeTextMutationAfterBlurDeadline: TimeInterval?
|
|
229
|
+
var nativeTextMutationAfterBlurGeneration: UInt64?
|
|
230
|
+
private let nativeTextMutationAfterBlurGraceInterval: TimeInterval = 1.0
|
|
231
|
+
/// Last selection known to match `lastAuthorizedText`, stored in that text's UTF-16 coordinates.
|
|
232
|
+
var lastAuthorizedSelectedUtf16Range: NSRange?
|
|
233
|
+
var lastAuthorizedSelectionIsBackward = false
|
|
234
|
+
var logicalSelectionScalarRange: (anchor: UInt32, head: UInt32)?
|
|
235
|
+
/// The UIKit selection `logicalSelectionScalarRange` was resolved to, after
|
|
236
|
+
/// any empty-block autocapitalization nudge. Lets a deliberate nudge be
|
|
237
|
+
/// told apart from the user moving the caret.
|
|
238
|
+
var logicalSelectionUtf16Range: NSRange?
|
|
239
|
+
var selectionRevision: UInt64 = 0
|
|
240
|
+
var desiredInputTraitState = InputTraitState()
|
|
241
|
+
var appliedInputTraitState = InputTraitState()
|
|
242
|
+
var pendingInputTraitChange = PendingInputTraitChange()
|
|
243
|
+
var pendingInputTraitRetryScheduled = false
|
|
244
|
+
var pendingInputTraitRetryGeneration: UInt64 = 0
|
|
245
|
+
|
|
246
|
+
/// Coalesces selection sync until UIKit has finished resolving the
|
|
247
|
+
/// current tap/drag gesture's final caret position.
|
|
248
|
+
var pendingSelectionSyncGeneration: UInt64 = 0
|
|
249
|
+
var pendingDeferredImageSelectionRange: NSRange?
|
|
250
|
+
var pendingDeferredImageSelectionGeneration: UInt64 = 0
|
|
251
|
+
|
|
252
|
+
/// Stores the Rust-authorized scalar range replaced by the active marked
|
|
253
|
+
/// text session. UIKit mutates visible TextKit state during composition,
|
|
254
|
+
/// so final commits must not infer their range from the transient cursor.
|
|
255
|
+
var markedTextReplacementScalarRange: (from: UInt32, to: UInt32)?
|
|
256
|
+
var markedTextReplacementUtf16Range: NSRange?
|
|
257
|
+
var markedTextCompositionText: String?
|
|
258
|
+
var markedTextCompositionIsExplicitlyEmpty = false
|
|
259
|
+
|
|
260
|
+
var externalTextComposition: ExternalTextCompositionState?
|
|
261
|
+
var externalTextCompositionTerminalResults: [String: String] = [:]
|
|
262
|
+
|
|
263
|
+
let editorLayoutManager: EditorLayoutManager
|
|
264
|
+
|
|
265
|
+
// MARK: - Placeholder
|
|
266
|
+
|
|
267
|
+
lazy var placeholderLabel: UILabel = {
|
|
268
|
+
let label = UILabel()
|
|
269
|
+
label.textColor = .placeholderText
|
|
270
|
+
label.font = baseFont
|
|
271
|
+
label.numberOfLines = 0
|
|
272
|
+
label.isUserInteractionEnabled = false
|
|
273
|
+
return label
|
|
274
|
+
}()
|
|
275
|
+
|
|
276
|
+
var placeholder: String = "" {
|
|
277
|
+
didSet {
|
|
278
|
+
placeholderLabel.text = placeholder
|
|
279
|
+
refreshPlaceholderVisibility()
|
|
280
|
+
setNeedsLayout()
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// MARK: - Initialization
|
|
285
|
+
|
|
286
|
+
override init(frame: CGRect, textContainer: NSTextContainer?) {
|
|
287
|
+
let layoutManager = EditorLayoutManager()
|
|
288
|
+
let container = textContainer ?? NSTextContainer(
|
|
289
|
+
size: CGSize(width: 0, height: CGFloat.greatestFiniteMagnitude)
|
|
290
|
+
)
|
|
291
|
+
let textStorage = NSTextStorage()
|
|
292
|
+
layoutManager.addTextContainer(container)
|
|
293
|
+
textStorage.addLayoutManager(layoutManager)
|
|
294
|
+
editorLayoutManager = layoutManager
|
|
295
|
+
super.init(frame: frame, textContainer: container)
|
|
296
|
+
commonInit()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
required init?(coder: NSCoder) {
|
|
300
|
+
return nil
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private func commonInit() {
|
|
304
|
+
textContainer.widthTracksTextView = true
|
|
305
|
+
// Large documents edit more smoothly when TextKit can invalidate and
|
|
306
|
+
// relayout only the touched region instead of forcing contiguous layout.
|
|
307
|
+
editorLayoutManager.allowsNonContiguousLayout = true
|
|
308
|
+
NotificationCenter.default.addObserver(
|
|
309
|
+
self,
|
|
310
|
+
selector: #selector(handleImageAttachmentDidLoad(_:)),
|
|
311
|
+
name: .editorImageAttachmentDidLoad,
|
|
312
|
+
object: nil
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
for name in [UIResponder.keyboardWillChangeFrameNotification, UIResponder.keyboardWillHideNotification] {
|
|
316
|
+
NotificationCenter.default.addObserver(
|
|
317
|
+
self,
|
|
318
|
+
selector: #selector(handleKeyboardFrameChange(_:)),
|
|
319
|
+
name: name,
|
|
320
|
+
object: nil
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Configure the text view as a Rust-controlled editor surface.
|
|
325
|
+
// UIKit smart-edit features mutate text storage outside our transaction
|
|
326
|
+
// pipeline and can race with stored-mark typing after toolbar actions.
|
|
327
|
+
setAutoCorrect(nil)
|
|
328
|
+
setAutoCapitalize(nil)
|
|
329
|
+
setKeyboardType(nil)
|
|
330
|
+
smartQuotesType = .no
|
|
331
|
+
smartDashesType = .no
|
|
332
|
+
smartInsertDeleteType = .no
|
|
333
|
+
|
|
334
|
+
isScrollEnabled = heightBehavior == .fixed
|
|
335
|
+
isEditable = true
|
|
336
|
+
isSelectable = true
|
|
337
|
+
|
|
338
|
+
font = baseFont
|
|
339
|
+
textColor = baseTextColor
|
|
340
|
+
backgroundColor = baseBackgroundColor
|
|
341
|
+
baseTextContainerInset = textContainerInset
|
|
342
|
+
baseLineFragmentPadding = textContainer.lineFragmentPadding
|
|
343
|
+
visibleSelectionTintColor = tintColor
|
|
344
|
+
|
|
345
|
+
// Register as the text storage delegate so we can detect unauthorized
|
|
346
|
+
// mutations (reconciliation fallback).
|
|
347
|
+
textStorage.delegate = self
|
|
348
|
+
ensureInternalTextViewDelegate()
|
|
349
|
+
textDragDelegate = self
|
|
350
|
+
textDropDelegate = self
|
|
351
|
+
addGestureRecognizer(caretPlacementTapRecognizer)
|
|
352
|
+
addGestureRecognizer(imageSelectionTapRecognizer)
|
|
353
|
+
installImageSelectionTapDependencies()
|
|
354
|
+
|
|
355
|
+
addSubview(placeholderLabel)
|
|
356
|
+
refreshPlaceholderVisibility()
|
|
357
|
+
refreshNativeSelectionChromeVisibility()
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
override func didMoveToWindow() {
|
|
361
|
+
super.didMoveToWindow()
|
|
362
|
+
if window == nil {
|
|
363
|
+
codeHighlightingSession.cancel()
|
|
364
|
+
keyboardFrameInScreen = nil
|
|
365
|
+
updateKeyboardInset()
|
|
366
|
+
} else {
|
|
367
|
+
scheduleCodeHighlighting()
|
|
368
|
+
}
|
|
369
|
+
installImageSelectionTapDependencies()
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
override func didAddSubview(_ subview: UIView) {
|
|
373
|
+
super.didAddSubview(subview)
|
|
374
|
+
installImageSelectionTapDependencies()
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
override func tintColorDidChange() {
|
|
378
|
+
super.tintColorDidChange()
|
|
379
|
+
if !hidesNativeSelectionChrome, tintColor.cgColor.alpha > 0 {
|
|
380
|
+
visibleSelectionTintColor = tintColor
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// MARK: - Layout
|
|
385
|
+
|
|
386
|
+
override func layoutSubviews() {
|
|
387
|
+
super.layoutSubviews()
|
|
388
|
+
updateKeyboardInset()
|
|
389
|
+
styleContentView.frame = CGRect(x: 0, y: 0, width: bounds.width, height: max(bounds.height, contentSize.height))
|
|
390
|
+
let placeholderX = textContainerInset.left + textContainer.lineFragmentPadding
|
|
391
|
+
let placeholderY = textContainerInset.top
|
|
392
|
+
let placeholderWidth = max(
|
|
393
|
+
0,
|
|
394
|
+
bounds.width - textContainerInset.left - textContainerInset.right - 2 * textContainer.lineFragmentPadding
|
|
395
|
+
)
|
|
396
|
+
if placeholderLabel.isHidden {
|
|
397
|
+
placeholderLabel.frame = CGRect(
|
|
398
|
+
x: placeholderX,
|
|
399
|
+
y: placeholderY,
|
|
400
|
+
width: placeholderWidth,
|
|
401
|
+
height: 0
|
|
402
|
+
)
|
|
403
|
+
} else {
|
|
404
|
+
let maxPlaceholderHeight = max(
|
|
405
|
+
0,
|
|
406
|
+
bounds.height - textContainerInset.top - textContainerInset.bottom
|
|
407
|
+
)
|
|
408
|
+
let fittedHeight = placeholderLabel.sizeThatFits(
|
|
409
|
+
CGSize(width: placeholderWidth, height: CGFloat.greatestFiniteMagnitude)
|
|
410
|
+
).height
|
|
411
|
+
placeholderLabel.frame = CGRect(
|
|
412
|
+
x: placeholderX,
|
|
413
|
+
y: placeholderY,
|
|
414
|
+
width: placeholderWidth,
|
|
415
|
+
height: min(maxPlaceholderHeight, ceil(fittedHeight))
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
if heightBehavior == .autoGrow, !isPreviewingImageResize {
|
|
419
|
+
let currentWidth = ceil(bounds.width)
|
|
420
|
+
if abs(currentWidth - lastAutoGrowMeasuredWidth) > 0.5 {
|
|
421
|
+
autoGrowHeightCheckIsDirty = true
|
|
422
|
+
lastAutoGrowMeasuredWidth = currentWidth
|
|
423
|
+
}
|
|
424
|
+
if autoGrowHeightCheckIsDirty {
|
|
425
|
+
notifyHeightChangeIfNeeded()
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if !isPreviewingImageResize {
|
|
429
|
+
onViewportMayChange?()
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
deinit {
|
|
434
|
+
codeHighlightingSession.cancel()
|
|
435
|
+
NotificationCenter.default.removeObserver(self)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
var keyboardFrameInScreen: CGRect?
|
|
439
|
+
var keyboardBottomInset: CGFloat = 0
|
|
440
|
+
|
|
441
|
+
override var contentOffset: CGPoint {
|
|
442
|
+
didSet {
|
|
443
|
+
if !isPreviewingImageResize {
|
|
444
|
+
onViewportMayChange?()
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
override func becomeFirstResponder() -> Bool {
|
|
450
|
+
let didBecomeFirstResponder = super.becomeFirstResponder()
|
|
451
|
+
if didBecomeFirstResponder {
|
|
452
|
+
ensureInternalTextViewDelegate()
|
|
453
|
+
clearNativeTextMutationAfterBlurWindow()
|
|
454
|
+
DispatchQueue.main.async { [weak self] in
|
|
455
|
+
self?.ensureInternalTextViewDelegate()
|
|
456
|
+
}
|
|
457
|
+
_ = normalizeSelectionForEmptyBlockAutocapitalizationIfNeeded()
|
|
458
|
+
recordAuthorizedSelectionIfPossible()
|
|
459
|
+
refreshTypingAttributesForSelection()
|
|
460
|
+
}
|
|
461
|
+
return didBecomeFirstResponder
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
override func resignFirstResponder() -> Bool {
|
|
465
|
+
ensureInternalTextViewDelegate()
|
|
466
|
+
_ = drainPendingNativeTextMutation(allowAfterBlur: false, allowWhileIntercepting: true)
|
|
467
|
+
|
|
468
|
+
let wasFirstResponder = isFirstResponder
|
|
469
|
+
if wasFirstResponder {
|
|
470
|
+
nativeTextMutationAfterBlurGeneration = nativeTextMutationGeneration
|
|
471
|
+
nativeTextMutationAfterBlurDeadline = ProcessInfo.processInfo.systemUptime
|
|
472
|
+
+ nativeTextMutationAfterBlurGraceInterval
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
let didResignFirstResponder = super.resignFirstResponder()
|
|
476
|
+
if wasFirstResponder || didResignFirstResponder {
|
|
477
|
+
_ = drainPendingNativeTextMutation(allowAfterBlur: true, allowWhileIntercepting: true)
|
|
478
|
+
DispatchQueue.main.async { [weak self] in
|
|
479
|
+
guard let self else { return }
|
|
480
|
+
_ = self.drainPendingNativeTextMutation(
|
|
481
|
+
allowAfterBlur: true,
|
|
482
|
+
allowWhileIntercepting: true
|
|
483
|
+
)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return didResignFirstResponder
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/// The core's `documentIsEmpty` from the most recent editor update, or nil
|
|
490
|
+
/// when the current render arrived without one.
|
|
491
|
+
var coreReportedDocumentIsEmpty: Bool?
|
|
492
|
+
|
|
493
|
+
override func caretRect(for position: UITextPosition) -> CGRect {
|
|
494
|
+
if hidesNativeSelectionChrome {
|
|
495
|
+
return .zero
|
|
496
|
+
}
|
|
497
|
+
let utf16Offset = offset(from: beginningOfDocument, to: position)
|
|
498
|
+
if isAtomBoundaryCaretOffset(utf16Offset) {
|
|
499
|
+
return .zero
|
|
500
|
+
}
|
|
501
|
+
// Noncontiguous layout can report estimated positions until the preceding text is laid out.
|
|
502
|
+
let layoutEnd = min(textStorage.length, max(0, utf16Offset) + 2)
|
|
503
|
+
layoutManager.ensureLayout(forCharacterRange: NSRange(location: 0, length: layoutEnd))
|
|
504
|
+
let rect = resolvedCaretReferenceRect(for: position)
|
|
505
|
+
guard rect.height > 0 else { return rect }
|
|
506
|
+
|
|
507
|
+
let caretFont = resolvedCaretFont(for: position)
|
|
508
|
+
let screenScale = window?.screen.scale ?? UIScreen.main.scale
|
|
509
|
+
let targetHeight = ceil(caretFont.lineHeight)
|
|
510
|
+
guard targetHeight > 0, targetHeight < rect.height else { return rect }
|
|
511
|
+
|
|
512
|
+
if let baselineY = caretBaselineY(for: position, referenceRect: rect) {
|
|
513
|
+
return Self.adjustedCaretRect(
|
|
514
|
+
from: rect,
|
|
515
|
+
baselineY: baselineY,
|
|
516
|
+
font: caretFont,
|
|
517
|
+
screenScale: screenScale
|
|
518
|
+
)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return Self.adjustedCaretRect(
|
|
522
|
+
from: rect,
|
|
523
|
+
font: caretFont,
|
|
524
|
+
screenScale: screenScale
|
|
525
|
+
)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
override func closestPosition(to point: CGPoint) -> UITextPosition? {
|
|
529
|
+
if atomAttachmentRange(at: point) != nil,
|
|
530
|
+
let selectedTextRange,
|
|
531
|
+
selectedTextRange.isEmpty {
|
|
532
|
+
return selectedTextRange.start
|
|
533
|
+
}
|
|
534
|
+
return super.closestPosition(to: point)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
override func closestPosition(
|
|
538
|
+
to point: CGPoint,
|
|
539
|
+
within range: UITextRange
|
|
540
|
+
) -> UITextPosition? {
|
|
541
|
+
if atomAttachmentRange(at: point) != nil,
|
|
542
|
+
let selectedTextRange,
|
|
543
|
+
selectedTextRange.isEmpty {
|
|
544
|
+
return selectedTextRange.start
|
|
545
|
+
}
|
|
546
|
+
return super.closestPosition(to: point, within: range)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// MARK: - Input Interception: Text Insertion
|
|
550
|
+
|
|
551
|
+
/// Intercept text insertion. This is called for:
|
|
552
|
+
/// - Single character typing (including autocomplete insertions)
|
|
553
|
+
/// - Return/Enter key
|
|
554
|
+
/// - Dictation results
|
|
555
|
+
///
|
|
556
|
+
/// Instead of calling `super.insertText()` (which would modify the
|
|
557
|
+
/// underlying text storage directly), we route through Rust.
|
|
558
|
+
override func insertText(_ text: String) {
|
|
559
|
+
ensureInternalTextViewDelegate()
|
|
560
|
+
if isApplyingRustState
|
|
561
|
+
|| (!isReplayingDeferredInsertText && !deferredInsertTexts.isEmpty) {
|
|
562
|
+
enqueueDeferredInsertText(text)
|
|
563
|
+
return
|
|
564
|
+
}
|
|
565
|
+
guard editorId != 0 else {
|
|
566
|
+
super.insertText(text)
|
|
567
|
+
return
|
|
568
|
+
}
|
|
569
|
+
guard finishExternalTextCompositionBeforeInteractionIfNeeded() else { return }
|
|
570
|
+
guard flushPendingNativeTextMutationCommitIfNeeded() else { return }
|
|
571
|
+
if !isReplayingDeferredInsertText, !deferredInsertTexts.isEmpty {
|
|
572
|
+
enqueueDeferredInsertText(text)
|
|
573
|
+
return
|
|
574
|
+
}
|
|
575
|
+
guard !isCollapsedAtomBoundary(selectedUtf16Range()) else { return }
|
|
576
|
+
|
|
577
|
+
if interceptReturnInput(text) { return }
|
|
578
|
+
|
|
579
|
+
if markedTextReplacementScalarRange != nil || markedTextRange != nil {
|
|
580
|
+
let replacementRange = trackedMarkedTextReplacementRange()
|
|
581
|
+
finishTransientMarkedTextMutation()
|
|
582
|
+
_ = commitMarkedText(text, replacementRange: replacementRange)
|
|
583
|
+
return
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
let scalarPos = PositionBridge.cursorScalarOffset(in: self)
|
|
587
|
+
Self.inputLog.debug(
|
|
588
|
+
"[insertText] text=\(self.preview(text), privacy: .public) scalarPos=\(scalarPos) selection=\(self.selectionSummary(), privacy: .public) textState=\(self.textSnapshotSummary(), privacy: .public)"
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
if let selectedRange = selectedTextRange, !selectedRange.isEmpty {
|
|
592
|
+
let range = PositionBridge.textRangeToScalarRange(selectedRange, in: self)
|
|
593
|
+
performInterceptedInput {
|
|
594
|
+
let updateJSON = EditorV2Shadow.replaceTextScalar(
|
|
595
|
+
id: editorId,
|
|
596
|
+
scalarFrom: range.from,
|
|
597
|
+
scalarTo: range.to,
|
|
598
|
+
text: text
|
|
599
|
+
)
|
|
600
|
+
applyUpdateJSON(updateJSON)
|
|
601
|
+
}
|
|
602
|
+
} else {
|
|
603
|
+
performInterceptedInput {
|
|
604
|
+
insertTextInRust(text, at: scalarPos)
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
override var keyCommands: [UIKeyCommand]? {
|
|
610
|
+
[
|
|
611
|
+
UIKeyCommand(
|
|
612
|
+
input: "\r",
|
|
613
|
+
modifierFlags: [.shift],
|
|
614
|
+
action: #selector(handleHardBreakKeyCommand)
|
|
615
|
+
),
|
|
616
|
+
UIKeyCommand(
|
|
617
|
+
input: "\t",
|
|
618
|
+
modifierFlags: [],
|
|
619
|
+
action: #selector(handleIndentKeyCommand)
|
|
620
|
+
),
|
|
621
|
+
UIKeyCommand(
|
|
622
|
+
input: "\t",
|
|
623
|
+
modifierFlags: [.shift],
|
|
624
|
+
action: #selector(handleOutdentKeyCommand)
|
|
625
|
+
)
|
|
626
|
+
]
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// MARK: - Input Interception: Deletion
|
|
630
|
+
|
|
631
|
+
/// Intercept backward deletion (Backspace key).
|
|
632
|
+
///
|
|
633
|
+
/// If there's a range selection, delete the range. If it's a cursor,
|
|
634
|
+
/// delete the character (grapheme cluster) before the cursor.
|
|
635
|
+
override func deleteBackward() {
|
|
636
|
+
ensureInternalTextViewDelegate()
|
|
637
|
+
guard !isApplyingRustState else {
|
|
638
|
+
super.deleteBackward()
|
|
639
|
+
return
|
|
640
|
+
}
|
|
641
|
+
guard editorId != 0 else {
|
|
642
|
+
super.deleteBackward()
|
|
643
|
+
return
|
|
644
|
+
}
|
|
645
|
+
guard finishExternalTextCompositionBeforeInteractionIfNeeded() else { return }
|
|
646
|
+
guard flushPendingNativeTextMutationCommitIfNeeded() else { return }
|
|
647
|
+
guard !isCollapsedAtomBoundary(selectedUtf16Range()) else { return }
|
|
648
|
+
|
|
649
|
+
if markedTextReplacementScalarRange != nil || markedTextRange != nil {
|
|
650
|
+
performTransientTextMutation {
|
|
651
|
+
super.deleteBackward()
|
|
652
|
+
}
|
|
653
|
+
refreshMarkedTextCompositionText()
|
|
654
|
+
isComposing = markedTextRange != nil || markedTextReplacementScalarRange != nil
|
|
655
|
+
return
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
guard let selectedRange = selectedTextRange else { return }
|
|
659
|
+
Self.inputLog.debug(
|
|
660
|
+
"[deleteBackward] selection=\(self.selectionSummary(), privacy: .public) textState=\(self.textSnapshotSummary(), privacy: .public)"
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
if !selectedRange.isEmpty {
|
|
664
|
+
let range = PositionBridge.textRangeToScalarRange(selectedRange, in: self)
|
|
665
|
+
performInterceptedInput {
|
|
666
|
+
deleteScalarRangeInRust(from: range.from, to: range.to)
|
|
667
|
+
}
|
|
668
|
+
} else {
|
|
669
|
+
// Cursor: delete one grapheme cluster backward. The engine's caret
|
|
670
|
+
// is the authority — in an empty block UIKit's own caret is parked
|
|
671
|
+
// ahead of the block placeholder for autocapitalization and does
|
|
672
|
+
// not address the same position.
|
|
673
|
+
let cursorPos = currentLogicalScalarSelection()?.head
|
|
674
|
+
?? PositionBridge.cursorScalarOffset(in: self)
|
|
675
|
+
if cursorPos == 0 {
|
|
676
|
+
performInterceptedInput {
|
|
677
|
+
deleteBackwardAtSelectionScalarInRust(anchor: cursorPos, head: cursorPos)
|
|
678
|
+
}
|
|
679
|
+
return
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
let cursorUtf16Offset = offset(from: beginningOfDocument, to: selectedRange.start)
|
|
683
|
+
if cursorUtf16Offset <= 0 {
|
|
684
|
+
performInterceptedInput {
|
|
685
|
+
deleteBackwardAtSelectionScalarInRust(anchor: cursorPos, head: cursorPos)
|
|
686
|
+
}
|
|
687
|
+
return
|
|
688
|
+
}
|
|
689
|
+
if let marker = PositionBridge.virtualListMarker(
|
|
690
|
+
atUtf16Offset: cursorUtf16Offset,
|
|
691
|
+
in: self
|
|
692
|
+
), marker.paragraphStartUtf16 == cursorUtf16Offset {
|
|
693
|
+
performInterceptedInput {
|
|
694
|
+
deleteScalarRangeInRust(from: cursorPos - 1, to: cursorPos)
|
|
695
|
+
}
|
|
696
|
+
return
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if let deleteRange = trailingVoidBlockDeleteRangeForBackwardDelete(
|
|
700
|
+
cursorUtf16Offset: cursorUtf16Offset
|
|
701
|
+
) {
|
|
702
|
+
performInterceptedInput {
|
|
703
|
+
deleteScalarRangeInRust(from: deleteRange.from, to: deleteRange.to)
|
|
704
|
+
}
|
|
705
|
+
return
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if let deleteRange = adjacentVoidBlockDeleteRangeForBackwardDelete(
|
|
709
|
+
cursorUtf16Offset: cursorUtf16Offset,
|
|
710
|
+
cursorScalar: cursorPos
|
|
711
|
+
) {
|
|
712
|
+
performInterceptedInput {
|
|
713
|
+
deleteScalarRangeInRust(from: deleteRange.from, to: deleteRange.to)
|
|
714
|
+
}
|
|
715
|
+
return
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
if cursorUtf16Offset > 0,
|
|
719
|
+
(textStorage.string as NSString).character(at: cursorUtf16Offset - 1) == 0x200B {
|
|
720
|
+
performInterceptedInput {
|
|
721
|
+
deleteBackwardAtSelectionScalarInRust(anchor: cursorPos, head: cursorPos)
|
|
722
|
+
}
|
|
723
|
+
return
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Find the start of the previous grapheme cluster.
|
|
727
|
+
// We need to figure out how many scalars the previous grapheme occupies.
|
|
728
|
+
// Use UITextView's tokenizer to find the previous grapheme boundary.
|
|
729
|
+
guard let prevPos = position(from: selectedRange.start, offset: -1) else { return }
|
|
730
|
+
let prevScalar = PositionBridge.textViewToScalar(prevPos, in: self)
|
|
731
|
+
|
|
732
|
+
performInterceptedInput {
|
|
733
|
+
if prevScalar < cursorPos {
|
|
734
|
+
deleteScalarRangeInRust(from: prevScalar, to: cursorPos)
|
|
735
|
+
} else {
|
|
736
|
+
deleteBackwardAtSelectionScalarInRust(anchor: cursorPos, head: cursorPos)
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// MARK: - Input Interception: Replace (Autocorrect)
|
|
743
|
+
|
|
744
|
+
/// Intercept text replacement. This is called when:
|
|
745
|
+
/// - Autocorrect replaces a word
|
|
746
|
+
/// - User accepts a spelling suggestion
|
|
747
|
+
/// - Programmatic text replacement
|
|
748
|
+
///
|
|
749
|
+
/// We route the replacement through Rust to keep the document model in sync.
|
|
750
|
+
override func replace(_ range: UITextRange, withText text: String) {
|
|
751
|
+
ensureInternalTextViewDelegate()
|
|
752
|
+
guard !isApplyingRustState else {
|
|
753
|
+
super.replace(range, withText: text)
|
|
754
|
+
return
|
|
755
|
+
}
|
|
756
|
+
guard editorId != 0 else {
|
|
757
|
+
super.replace(range, withText: text)
|
|
758
|
+
return
|
|
759
|
+
}
|
|
760
|
+
guard finishExternalTextCompositionBeforeInteractionIfNeeded() else { return }
|
|
761
|
+
guard flushPendingNativeTextMutationCommitIfNeeded() else { return }
|
|
762
|
+
let replacementUtf16Range = NSRange(
|
|
763
|
+
location: offset(from: beginningOfDocument, to: range.start),
|
|
764
|
+
length: offset(from: range.start, to: range.end)
|
|
765
|
+
)
|
|
766
|
+
guard !isCollapsedAtomBoundary(replacementUtf16Range) else { return }
|
|
767
|
+
|
|
768
|
+
if interceptReturnInput(text, replacing: range) { return }
|
|
769
|
+
|
|
770
|
+
if markedTextReplacementScalarRange != nil || markedTextRange != nil {
|
|
771
|
+
let replacementRange = trackedMarkedTextReplacementRange()
|
|
772
|
+
finishTransientMarkedTextMutation()
|
|
773
|
+
_ = commitMarkedText(text, replacementRange: replacementRange)
|
|
774
|
+
return
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
let scalarRange = PositionBridge.textRangeToScalarRange(range, in: self)
|
|
778
|
+
let replacementStartUtf16 = replacementUtf16Range.location
|
|
779
|
+
let replacementEndUtf16 = NSMaxRange(replacementUtf16Range)
|
|
780
|
+
let preservesAcceptedSpace = textStorage.string == lastAuthorizedText
|
|
781
|
+
&& shouldPreserveAcceptedAutocorrectSpace(
|
|
782
|
+
authorizedText: lastAuthorizedText as NSString,
|
|
783
|
+
replacementStartUtf16: replacementStartUtf16,
|
|
784
|
+
authorizedEndUtf16: replacementEndUtf16,
|
|
785
|
+
replacementText: text,
|
|
786
|
+
rawSelectionUtf16Range: selectedUtf16Range(),
|
|
787
|
+
authorizedSelectionUtf16Range: lastAuthorizedSelectedUtf16Range,
|
|
788
|
+
acceptedCaretUtf16Offset: replacementEndUtf16
|
|
789
|
+
)
|
|
790
|
+
let replacementText = preservesAcceptedSpace ? text + " " : text
|
|
791
|
+
Self.inputLog.debug(
|
|
792
|
+
"[replace] text=\(self.preview(replacementText), privacy: .public) scalarRange=\(scalarRange.from)-\(scalarRange.to) selection=\(self.selectionSummary(), privacy: .public) textState=\(self.textSnapshotSummary(), privacy: .public)"
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
performInterceptedInput {
|
|
796
|
+
let updateJSON = EditorV2Shadow.replaceTextScalar(
|
|
797
|
+
id: editorId,
|
|
798
|
+
scalarFrom: scalarRange.from,
|
|
799
|
+
scalarTo: scalarRange.to,
|
|
800
|
+
text: replacementText
|
|
801
|
+
)
|
|
802
|
+
applyUpdateJSON(updateJSON)
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// MARK: - Composition Handling (CJK / IME)
|
|
807
|
+
|
|
808
|
+
/// Called when the input method sets marked (composing) text.
|
|
809
|
+
///
|
|
810
|
+
/// During CJK input, the user composes characters incrementally. We let
|
|
811
|
+
/// UITextView display the composing text normally (with its underline
|
|
812
|
+
/// decoration). The text is NOT sent to Rust during composition.
|
|
813
|
+
override func setMarkedText(_ markedText: String?, selectedRange: NSRange) {
|
|
814
|
+
ensureInternalTextViewDelegate()
|
|
815
|
+
guard finishExternalTextCompositionBeforeInteractionIfNeeded() else { return }
|
|
816
|
+
if markedText != nil {
|
|
817
|
+
guard flushPendingNativeTextMutationCommitIfNeeded() else { return }
|
|
818
|
+
captureMarkedTextReplacementRangeIfNeeded()
|
|
819
|
+
} else if markedTextReplacementScalarRange == nil, markedTextRange == nil {
|
|
820
|
+
guard flushPendingNativeTextMutationCommitIfNeeded() else { return }
|
|
821
|
+
}
|
|
822
|
+
isComposing = markedText != nil || markedTextReplacementScalarRange != nil
|
|
823
|
+
Self.inputLog.debug(
|
|
824
|
+
"[setMarkedText] marked=\(self.preview(markedText ?? ""), privacy: .public) nsRange=\(selectedRange.location),\(selectedRange.length) selection=\(self.selectionSummary(), privacy: .public)"
|
|
825
|
+
)
|
|
826
|
+
if markedText == nil {
|
|
827
|
+
// Some keyboard paths finalize composition by clearing marked text
|
|
828
|
+
// instead of calling unmarkText().
|
|
829
|
+
let composedText = validatedTrackedMarkedTextForCommit()
|
|
830
|
+
let replacementRange = trackedMarkedTextReplacementRange()
|
|
831
|
+
performTransientTextMutation {
|
|
832
|
+
super.setMarkedText(nil, selectedRange: selectedRange)
|
|
833
|
+
}
|
|
834
|
+
clearMarkedTextTracking()
|
|
835
|
+
if shouldCommitMarkedText(composedText, replacementRange: replacementRange) {
|
|
836
|
+
_ = commitMarkedText(composedText ?? "", replacementRange: replacementRange)
|
|
837
|
+
} else {
|
|
838
|
+
restoreAuthorizedTextAfterCancelledCompositionIfNeeded()
|
|
839
|
+
}
|
|
840
|
+
return
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
performTransientTextMutation {
|
|
844
|
+
super.setMarkedText(markedText, selectedRange: selectedRange)
|
|
845
|
+
}
|
|
846
|
+
refreshMarkedTextCompositionText(fallback: markedText)
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/// Called when composition is finalized (user selects a candidate or
|
|
850
|
+
/// presses space/enter to commit).
|
|
851
|
+
///
|
|
852
|
+
/// At this point, the composed text is final. We capture it and commit it
|
|
853
|
+
/// to Rust at the original replacement range captured before UIKit mutated
|
|
854
|
+
/// the transient text storage.
|
|
855
|
+
override func unmarkText() {
|
|
856
|
+
ensureInternalTextViewDelegate()
|
|
857
|
+
if externalTextComposition != nil {
|
|
858
|
+
guard finishExternalTextCompositionBeforeInteractionIfNeeded() else { return }
|
|
859
|
+
return
|
|
860
|
+
}
|
|
861
|
+
let composedText = currentMarkedTextForCommit()
|
|
862
|
+
let replacementRange = trackedMarkedTextReplacementRange()
|
|
863
|
+
|
|
864
|
+
finishTransientMarkedTextMutation()
|
|
865
|
+
|
|
866
|
+
if let composed = composedText, !composed.isEmpty {
|
|
867
|
+
Self.inputLog.debug(
|
|
868
|
+
"[unmarkText] composed=\(self.preview(composed), privacy: .public) replacement=\(self.previewMarkedTextReplacementRange(replacementRange), privacy: .public) selection=\(self.selectionSummary(), privacy: .public)"
|
|
869
|
+
)
|
|
870
|
+
_ = commitMarkedText(composed, replacementRange: replacementRange)
|
|
871
|
+
} else if shouldCommitMarkedText(composedText, replacementRange: replacementRange) {
|
|
872
|
+
_ = commitMarkedText(composedText ?? "", replacementRange: replacementRange)
|
|
873
|
+
} else {
|
|
874
|
+
restoreAuthorizedTextAfterCancelledCompositionIfNeeded()
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// MARK: - Paste Handling
|
|
879
|
+
|
|
880
|
+
/// Intercept paste operations to route content through Rust.
|
|
881
|
+
///
|
|
882
|
+
/// Attempts to extract HTML from the pasteboard first (for rich text paste),
|
|
883
|
+
/// falling back to plain text.
|
|
884
|
+
override func paste(_ sender: Any?) {
|
|
885
|
+
ensureInternalTextViewDelegate()
|
|
886
|
+
guard editorId != 0 else {
|
|
887
|
+
super.paste(sender)
|
|
888
|
+
return
|
|
889
|
+
}
|
|
890
|
+
guard finishExternalTextCompositionBeforeInteractionIfNeeded() else { return }
|
|
891
|
+
guard prepareForExternalEditorUpdate() else { return }
|
|
892
|
+
|
|
893
|
+
Self.inputLog.debug(
|
|
894
|
+
"[paste] selection=\(self.selectionSummary(), privacy: .public) textState=\(self.textSnapshotSummary(), privacy: .public)"
|
|
895
|
+
)
|
|
896
|
+
|
|
897
|
+
let pasteboard = UIPasteboard.general
|
|
898
|
+
|
|
899
|
+
if let htmlData = pasteboard.data(forPasteboardType: "public.html"),
|
|
900
|
+
let html = String(data: htmlData, encoding: .utf8) {
|
|
901
|
+
performInterceptedInput {
|
|
902
|
+
pasteHTML(html)
|
|
903
|
+
}
|
|
904
|
+
return
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
if let rtfData = pasteboard.data(forPasteboardType: "public.rtf") {
|
|
908
|
+
if let attrStr = try? NSAttributedString(
|
|
909
|
+
data: rtfData,
|
|
910
|
+
options: [.documentType: NSAttributedString.DocumentType.rtf],
|
|
911
|
+
documentAttributes: nil
|
|
912
|
+
) {
|
|
913
|
+
if let htmlData = try? attrStr.data(
|
|
914
|
+
from: NSRange(location: 0, length: attrStr.length),
|
|
915
|
+
documentAttributes: [.documentType: NSAttributedString.DocumentType.html]
|
|
916
|
+
), let html = String(data: htmlData, encoding: .utf8) {
|
|
917
|
+
performInterceptedInput {
|
|
918
|
+
if !pasteHTML(html, detectContentChange: true),
|
|
919
|
+
!attrStr.string.isEmpty {
|
|
920
|
+
pastePlainText(attrStr.string)
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
return
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
if let text = pasteboard.string {
|
|
929
|
+
performInterceptedInput {
|
|
930
|
+
pastePlainText(text)
|
|
931
|
+
}
|
|
932
|
+
return
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// MARK: - Private: Rust Integration
|
|
937
|
+
|
|
938
|
+
var isInterceptingInput: Bool {
|
|
939
|
+
interceptedInputDepth > 0
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// MARK: - Applying Rust State
|
|
943
|
+
|
|
944
|
+
/// Whether the document is the lone empty block whose caret the
|
|
945
|
+
/// autocapitalization nudge parks ahead of the placeholder. That block
|
|
946
|
+
/// holds exactly one caret position, which is what makes a cached scalar
|
|
947
|
+
/// safe to trust there even though the UIKit offset disagrees with it.
|
|
948
|
+
var isLoneEmptyPlaceholderBlock: Bool {
|
|
949
|
+
textStorage.length == 1
|
|
950
|
+
&& textStorage.string.unicodeScalars.elementsEqual([Self.emptyBlockPlaceholderScalar])
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
}
|