Repository: lexrus/RegExPlus Branch: master Commit: f10137f28dff Files: 164 Total size: 380.0 KB Directory structure: gitextract__mufejjy/ ├── .github/ │ └── workflow/ │ └── claude.yml ├── .gitignore ├── .swift-version ├── .swiftlint.yml ├── AGENTS.md ├── CLAUDE.md ├── LICENSE ├── README.md ├── RegEx+/ │ ├── About/ │ │ └── AboutView.swift │ ├── AppDelegate.swift │ ├── Assets.xcassets/ │ │ ├── AccentColor.colorset/ │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── AppIconForAboutView.imageset/ │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj/ │ │ └── LaunchScreen.storyboard │ ├── CheatSheet/ │ │ └── CheatSheetView.swift │ ├── CoreData+CloudKit/ │ │ ├── DataManager.swift │ │ ├── RegEx.swift │ │ └── RegExFetch.swift │ ├── Editor/ │ │ ├── EditorView.swift │ │ ├── EditorViewModel.swift │ │ └── RegExFlowView.swift │ ├── HomeView.swift │ ├── Info.plist │ ├── Library/ │ │ ├── LibraryItemView.swift │ │ ├── LibraryView+Data.swift │ │ └── LibraryView.swift │ ├── Localizable.xcstrings │ ├── Preview Content/ │ │ └── Preview Assets.xcassets/ │ │ └── Contents.json │ ├── RegEx+.entitlements │ ├── RegEx.xcdatamodeld/ │ │ ├── .xccurrentversion │ │ └── RegEx.xcdatamodel/ │ │ └── contents │ ├── SceneDelegate.swift │ ├── Views/ │ │ ├── ActivityViewController.swift │ │ ├── RegExSyntaxView.swift │ │ ├── RegExTextView/ │ │ │ ├── MatchesTextView.swift │ │ │ ├── RegExSyntaxHighlighter.swift │ │ │ ├── RegExTextView.swift │ │ │ ├── ShortcutKeys.swift │ │ │ └── String+NSRange.swift │ │ ├── SafariView.swift │ │ └── SearchView.swift │ ├── de.lproj/ │ │ └── CheatSheet.plist │ ├── en.lproj/ │ │ └── CheatSheet.plist │ ├── es.lproj/ │ │ └── CheatSheet.plist │ ├── fr.lproj/ │ │ └── CheatSheet.plist │ ├── it.lproj/ │ │ └── CheatSheet.plist │ ├── ja.lproj/ │ │ └── CheatSheet.plist │ ├── ko.lproj/ │ │ └── CheatSheet.plist │ ├── nl.lproj/ │ │ └── CheatSheet.plist │ ├── pl.lproj/ │ │ └── CheatSheet.plist │ ├── zh-Hans.lproj/ │ │ └── CheatSheet.plist │ └── zh-Hant.lproj/ │ └── CheatSheet.plist ├── RegEx+.xcodeproj/ │ └── project.pbxproj ├── fastlane/ │ ├── Deliverfile │ ├── Fastfile │ └── metadata/ │ ├── copyright.txt │ ├── de-DE/ │ │ ├── apple_tv_privacy_policy.txt │ │ ├── description.txt │ │ ├── keywords.txt │ │ ├── marketing_url.txt │ │ ├── name.txt │ │ ├── privacy_url.txt │ │ ├── promotional_text.txt │ │ ├── release_notes.txt │ │ ├── subtitle.txt │ │ └── support_url.txt │ ├── en-US/ │ │ ├── apple_tv_privacy_policy.txt │ │ ├── description.txt │ │ ├── keywords.txt │ │ ├── marketing_url.txt │ │ ├── name.txt │ │ ├── privacy_url.txt │ │ ├── promotional_text.txt │ │ ├── release_notes.txt │ │ ├── subtitle.txt │ │ └── support_url.txt │ ├── es-ES/ │ │ ├── apple_tv_privacy_policy.txt │ │ ├── description.txt │ │ ├── keywords.txt │ │ ├── marketing_url.txt │ │ ├── name.txt │ │ ├── privacy_url.txt │ │ ├── promotional_text.txt │ │ ├── release_notes.txt │ │ ├── subtitle.txt │ │ └── support_url.txt │ ├── fr-FR/ │ │ ├── apple_tv_privacy_policy.txt │ │ ├── description.txt │ │ ├── keywords.txt │ │ ├── marketing_url.txt │ │ ├── name.txt │ │ ├── privacy_url.txt │ │ ├── promotional_text.txt │ │ ├── release_notes.txt │ │ ├── subtitle.txt │ │ └── support_url.txt │ ├── it/ │ │ ├── apple_tv_privacy_policy.txt │ │ ├── description.txt │ │ ├── keywords.txt │ │ ├── marketing_url.txt │ │ ├── name.txt │ │ ├── privacy_url.txt │ │ ├── promotional_text.txt │ │ ├── release_notes.txt │ │ ├── subtitle.txt │ │ └── support_url.txt │ ├── ja/ │ │ ├── apple_tv_privacy_policy.txt │ │ ├── description.txt │ │ ├── keywords.txt │ │ ├── marketing_url.txt │ │ ├── name.txt │ │ ├── privacy_url.txt │ │ ├── promotional_text.txt │ │ ├── release_notes.txt │ │ ├── subtitle.txt │ │ └── support_url.txt │ ├── nl-NL/ │ │ ├── apple_tv_privacy_policy.txt │ │ ├── description.txt │ │ ├── keywords.txt │ │ ├── marketing_url.txt │ │ ├── name.txt │ │ ├── privacy_url.txt │ │ ├── promotional_text.txt │ │ ├── release_notes.txt │ │ ├── subtitle.txt │ │ └── support_url.txt │ ├── pl/ │ │ ├── apple_tv_privacy_policy.txt │ │ ├── description.txt │ │ ├── keywords.txt │ │ ├── marketing_url.txt │ │ ├── name.txt │ │ ├── privacy_url.txt │ │ ├── promotional_text.txt │ │ ├── release_notes.txt │ │ ├── subtitle.txt │ │ └── support_url.txt │ ├── primary_category.txt │ ├── primary_first_sub_category.txt │ ├── primary_second_sub_category.txt │ ├── secondary_category.txt │ ├── secondary_first_sub_category.txt │ ├── secondary_second_sub_category.txt │ ├── zh-Hans/ │ │ ├── apple_tv_privacy_policy.txt │ │ ├── description.txt │ │ ├── keywords.txt │ │ ├── marketing_url.txt │ │ ├── name.txt │ │ ├── privacy_url.txt │ │ ├── promotional_text.txt │ │ ├── release_notes.txt │ │ ├── subtitle.txt │ │ └── support_url.txt │ └── zh-Hant/ │ ├── apple_tv_privacy_policy.txt │ ├── description.txt │ ├── keywords.txt │ ├── marketing_url.txt │ ├── name.txt │ ├── privacy_url.txt │ ├── promotional_text.txt │ ├── release_notes.txt │ ├── subtitle.txt │ └── support_url.txt └── mise.toml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflow/claude.yml ================================================ name: Claude Code on: issue_comment: types: [created] pull_request_review_comment: types: [created] issues: types: [opened, assigned] pull_request_review: types: [submitted] jobs: claude: if: | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) runs-on: ubuntu-latest permissions: contents: write pull-requests: write issues: write id-token: write actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 1 - name: Run Claude Code id: claude uses: anthropics/claude-code-action@beta with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} # This is an optional setting that allows Claude to read CI results on PRs additional_permissions: | actions: read # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4.1) # model: "claude-opus-4-1-20250805" model: ${{ vars.ANTHROPIC_MODEL }} # Optional: Customize the trigger phrase (default: @claude) # trigger_phrase: "/claude" # Optional: Trigger when specific user is assigned to an issue # assignee_trigger: "claude-bot" # Optional: Allow Claude to run specific commands # allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)" # Optional: Add custom instructions for Claude to customize its behavior for your project # custom_instructions: | # Follow our coding standards # Ensure all new code has tests # Use TypeScript for new files # Optional: Custom environment variables for Claude claude_env: | ANTHROPIC_BASE_URL: ${{ vars.ANTHROPIC_BASE_URL }} ================================================ FILE: .gitignore ================================================ # Created by https://www.gitignore.io/api/xcode,macos,fastlane # Edit at https://www.gitignore.io/?templates=xcode,macos,fastlane ### fastlane ### # fastlane - A streamlined workflow tool for Cocoa deployment # # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control # fastlane specific fastlane/report.xml fastlane/Appfile fastlane/metadata/**/review_information/ fastlane/keys # deliver temporary files fastlane/Preview.html # snapshot generated screenshots fastlane/screenshots # scan temporary files fastlane/test_output ### macOS ### # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Xcode ### # Xcode # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## User settings xcuserdata/ ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint *.xccheckout ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) build/ DerivedData/ *.moved-aside *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 ## Xcode Patch *.xcodeproj/* !*.xcodeproj/project.pbxproj !*.xcodeproj/xcshareddata/ !*.xcworkspace/contents.xcworkspacedata /*.gcno ### Xcode Patch ### **/xcshareddata/WorkspaceSettings.xcsettings # End of https://www.gitignore.io/api/xcode,macos,fastlane ================================================ FILE: .swift-version ================================================ 5.9 ================================================ FILE: .swiftlint.yml ================================================ disabled_rules: - todo - trailing_whitespace - colon - identifier_name - comma - vertical_whitespace - type_name - trailing_comma - multiple_closures_with_trailing_closure excluded: - Carthage - Pods line_length: - 200 - 220 function_body_length: - 200 - 300 type_body_length: - 300 - 500 file_length: - 500 - 700 ================================================ FILE: AGENTS.md ================================================ # Repository Guidelines ## Project Structure & Module Organization `RegEx+/` hosts the SwiftUI app: `HomeView.swift` drives navigation, `Views/`, `Editor/`, and `Library/` contain feature areas, and `CheatSheet/` holds localized regex tips backed by `RegEx.xcdatamodeld`. Assets and previews live in `Assets.xcassets` and `Preview Content/`. Localizations reside in per-language `.lproj` folders alongside `Localizable.xcstrings`. Use the Xcode project at `RegEx+.xcodeproj`; the `Build/` directory is derived output and should stay untracked. `fastlane/` stores App Store metadata flows, and `mise.toml` defines repeatable automation tasks. ## Build, Test, and Development Commands - `open RegEx+.xcodeproj` — launch the workspace in Xcode for iterative SwiftUI development. - `xcodebuild -scheme "RegEx+" -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 15' build` — verify the iOS target from the command line. - `xcodebuild test -scheme "RegEx+" -destination 'platform=macOS'` — run XCTest targets (create or enable them before CI). - `mise run sc2tc` — refresh Traditional Chinese cheat-sheet resources via OpenCC. - `mise run pull-metadata` / `mise run push-metadata` — sync App Store metadata with Fastlane. ## Coding Style & Naming Conventions Follow idiomatic Swift 5.9: four-space indentation, `UpperCamelCase` for types and `lowerCamelCase` for functions, properties, and Core Data entities. Prefer SwiftUI modifiers over imperative UIKit. Strings must be localized by adding keys to `Localizable.xcstrings` and the matching `.lproj` plist. SwiftLint runs as an Xcode build phase; ensure `swiftlint` is installed (e.g., `brew install swiftlint`) before pushing. ## Testing Guidelines Author tests with XCTest and snapshot SwiftUI previews where appropriate. Group specs by feature (e.g., `LibraryViewTests.swift`) and name methods `test__()`. Run `xcodebuild test` against both Catalyst and iOS destinations when the change affects shared logic. Aim to cover regex evaluation, Core Data persistence, and localization fallbacks; flag gaps in PRs if coverage is impractical. ## Commit & Pull Request Guidelines Git history follows conventional prefixes (`feat:`, `fix:`, `chore:`). Keep subject lines under 72 characters and describe what changed and why. For pull requests, include a concise summary, affected platforms, and simulator or macOS screenshots when UI shifts. Link related issues, note localization or metadata follow-up steps, and confirm that SwiftLint and targeted builds/tests succeed locally. Avoid committing Fastlane API keys or other secrets under `fastlane/keys/`; use mocked or encrypted references instead. ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Build Commands - **Build project**: Use Xcode GUI or `xcodebuild -project RegEx+.xcodeproj -scheme RegEx+ build` - **Run SwiftLint**: Automatically runs during build via build phase, or manually with `swiftlint` (requires installation via Homebrew) - **Localization conversion**: `mise run sc2tc` (converts Simplified Chinese to Traditional Chinese using OpenCC) ## Architecture Overview RegEx+ is a SwiftUI-based Regular Expression tool that supports both iOS and macOS via Mac Catalyst. The app uses Core Data with CloudKit for data persistence and synchronization. ### Core Architecture Components - **SwiftUI App Structure**: Uses scene-based architecture with `AppDelegate.swift` and `SceneDelegate.swift` - **Navigation**: Master-detail navigation with `HomeView` as root, `LibraryView` as master, and `EditorView` as detail - **Data Layer**: Core Data + CloudKit integration via `NSPersistentCloudKitContainer` for automatic sync - **Custom Text Editing**: Specialized `RegExTextView` with syntax highlighting and live matching ### Key Modules 1. **CoreData+CloudKit**: Data persistence and cloud sync - `DataManager.swift`: Singleton managing Core Data stack and CloudKit integration - `RegEx.swift`: Core Data model for regex entries - `RegExFetch.swift`: Fetch request definitions 2. **Editor**: Main regex editing interface - `EditorView.swift`: SwiftUI view for regex editing - `EditorViewModel.swift`: Business logic and state management 3. **Library**: Regex collection management - `LibraryView.swift`: Master list of saved regexes - `LibraryItemView.swift`: Individual regex list items - `LibraryView+Data.swift`: Data manipulation extensions 4. **Views/RegExTextView**: Custom text editing components - `RegExTextView.swift`: UIKit-based text editor wrapper - `RegExSyntaxHighlighter.swift`: Syntax highlighting engine - `MatchesTextView.swift`: Display matching results - `String+NSRange.swift`: String range utilities 5. **CheatSheet**: Reference documentation - Localized plist files for regex syntax reference ### Platform Support - **Multi-platform**: iOS, iPadOS, and macOS (Mac Catalyst) - **Navigation adaptivity**: Automatically switches between single/double column navigation based on device - **Internationalization**: English, Simplified Chinese, Traditional Chinese - **CloudKit**: Automatic data sync across devices ### Data Model The app stores regex patterns with metadata in Core Data, automatically synced via CloudKit: - Name, pattern, test string, flags - Creation/modification dates - CloudKit integration for cross-device sync ### Development Notes - Uses `#if targetEnvironment(macCatalyst)` for platform-specific code - SwiftLint integration via build phase - Traditional Chinese localization generated from Simplified Chinese via OpenCC - Custom UIKit text view integration within SwiftUI for advanced text editing features ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright © 2020 Lex Tang, https://lex.sh Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # RegEx+ [![Swift 5.9](https://img.shields.io/badge/swift-5.9-ED523F.svg?style=flat)](https://swift.org/download/) [![@lexrus](https://img.shields.io/badge/contact-@lexrus-336699.svg?style=flat)](https://twitter.com/lexrus) [AppStore](https://apps.apple.com/us/app/regex/id1511763524) > A Regular Expression tool built with **SwiftUI**. The App Store version is no longer open source as the project has transitioned to agentic coding workflows. ![HeroImage](https://github.com/lexrus/RegExPlus/assets/219689/739896bf-c843-46b9-82f9-e1462562fe4b) ## Features - [x] Universal SwiftUI app for macOS and iOS - [x] Regex editor with live match highlighting - [x] Substitution template preview with copy-to-clipboard support - [x] Share a regular expression from the editor - [x] Built-in regular expression cheat sheet - [x] CloudKit-backed sync via Core Data - [x] Localized into: - [x] English - [x] Simplified Chinese - [x] Traditional Chinese - [x] Spanish - [x] German - [x] Japanese - [x] French - [x] Italian - [x] Korean - [x] Dutch - [x] Polish ## License This code is distributed under the terms and conditions of the MIT license. ================================================ FILE: RegEx+/About/AboutView.swift ================================================ // // AboutView.swift // RegEx+ // // Created by Lex on 2020/5/24. // Copyright © 2020 Lex.sh. All rights reserved. // import SwiftUI import StoreKit import AppAboutView struct AboutView: View { var body: some View { AppAboutView.fromMainBundle( appIcon: Image(.appIconForAboutView), feedbackEmail: "lexrus@gmail.com", appStoreID: "1511763524", privacyPolicy: URL(string: "https://lex.sh/regexplus/privacypolicy")!, copyrightText: "©2026 lex.sh", appsShowcaseURL: URL(string: "https://lex.sh/apps/apps.json") ) .background(Color.init(white: 0.5, opacity: 0.1)) .navigationBarTitleDisplayMode(.inline) .navigationBarTitle("RegEx+") } } #Preview { AboutView() } ================================================ FILE: RegEx+/AppDelegate.swift ================================================ // // AppDelegate.swift // RegEx+ // // Created by Lex on 2020/4/21. // Copyright © 2020 Lex.sh. All rights reserved. // import UIKit import CoreData import CloudKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } // MARK: UISceneSession Lifecycle func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } override func buildMenu(with builder: UIMenuBuilder) { super.buildMenu(with: builder) // Ensure that the builder is modifying the menu bar system. guard builder.system == UIMenuSystem.main else { return } builder.remove(menu: .help) } } ================================================ FILE: RegEx+/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "color" : { "color-space" : "srgb", "components" : { "alpha" : "1.000", "blue" : "1.000", "green" : "0.400", "red" : "0.000" } }, "idiom" : "universal" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "color" : { "color-space" : "srgb", "components" : { "alpha" : "1.000", "blue" : "0.900", "green" : "0.300", "red" : "0.000" } }, "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: RegEx+/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "filename" : "icon_40pt.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "filename" : "icon_20pt@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "filename" : "icon_29pt@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "filename" : "icon_29pt@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "filename" : "icon_40pt@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "filename" : "icon_60pt@2x.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "filename" : "icon_60pt@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "filename" : "icon_60pt@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "filename" : "icon_20pt.png", "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "filename" : "icon_40pt.png", "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "filename" : "icon_29pt.png", "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "filename" : "icon_29pt@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "filename" : "icon_40pt.png", "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "filename" : "icon_40pt@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "filename" : "icon_76pt.png", "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "filename" : "icon_76pt@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "filename" : "icon_83.5@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "filename" : "Icon.png", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" }, { "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { "filename" : "mac128.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { "filename" : "mac256.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { "filename" : "mac256.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { "filename" : "mac512.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { "filename" : "mac512.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { "filename" : "mac.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: RegEx+/Assets.xcassets/AppIconForAboutView.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "filename" : "AppIconForAboutView.png", "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: RegEx+/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: RegEx+/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: RegEx+/CheatSheet/CheatSheetView.swift ================================================ // // CheatSheetView.swift // RegEx+ // // Created by Lex on 2020/4/21. // Copyright © 2020 Lex.sh. All rights reserved. // import SwiftUI // Official documentation of NSRegularExpression private let kNSRegularExpressionDocumentLink = "https://developer.apple.com/documentation/foundation/nsregularexpression" struct CheatSheetView: View { @State var showingSafari = false @State var metacharacters: [CheatSheetPlist.Item] = [] @State var operators: [CheatSheetPlist.Item] = [] var body: some View { List { Section(header: Text("Metacharacters")) { ForEach(metacharacters, id: \.exp) { RowView(title: $0.exp, content: $0.des) } } Section(header: Text("Operators")) { ForEach(operators, id: \.exp) { RowView(title: $0.exp, content: $0.des) } } } .navigationBarTitle("Cheat Sheet") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { safariButton } } .onAppear(perform: loadPlist) } private func loadPlist() { guard let url = Bundle.main.url(forResource: "CheatSheet", withExtension: "plist") else { assertionFailure("Missing CheatSheet.plist!") return } let plistDecoder = PropertyListDecoder() do { let data = try Data(contentsOf: url) let dict = try plistDecoder.decode(CheatSheetPlist.self, from: data) metacharacters = dict.metacharacters operators = dict.operators } catch { print(error.localizedDescription) } } private var safariButton: some View { let url = URL(string: kNSRegularExpressionDocumentLink)! return Button(action: { #if targetEnvironment(macCatalyst) UIApplication.shared.open(url) #else showingSafari.toggle() #endif }) { Image(systemName: "safari") .imageScale(.large) .padding(EdgeInsets(top: 8, leading: 24, bottom: 8, trailing: 0)) } .sheet(isPresented: $showingSafari, content: { SafariView(url: url) }) } } struct CheatSheetView_Previews: PreviewProvider { static var previews: some View { NavigationView { CheatSheetView() .navigationBarTitle("Cheat Sheet") } } } private struct RowView: View { var title: String var content: String var body: some View { VStack(alignment: .leading, spacing: 3) { Text(title) .font(.headline) .foregroundColor(.accentColor) Text(content) .font(.subheadline) } .padding(.vertical, 6) } } struct CheatSheetPlist: Decodable { struct Item: Decodable, Hashable { var exp: String var des: String } var metacharacters: [Item] var operators: [Item] } ================================================ FILE: RegEx+/CoreData+CloudKit/DataManager.swift ================================================ // // DataManager.swift // RegEx+ // // Created by Lex on 2020/5/3. // Copyright © 2020 Lex.sh. All rights reserved. // import CoreData import CloudKit class DataManager { static let shared = DataManager() lazy var persistentContainer: NSPersistentCloudKitContainer = { let container = NSPersistentCloudKitContainer(name: "RegEx") guard let description = container.persistentStoreDescriptions.first else { fatalError("No Descriptions found") } description.setOption(true as NSObject, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey) container.viewContext.automaticallyMergesChangesFromParent = true container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump container.loadPersistentStores(completionHandler: { (_, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) // NotificationCenter.default.addObserver(self, selector: #selector(self.processUpdate), name: .NSPersistentStoreRemoteChange, object: nil) return container }() // MARK: - Initialize CloudKit schema func initializeCloudKitSchema() { do { try persistentContainer.initializeCloudKitSchema(options: NSPersistentCloudKitContainerSchemaInitializationOptions.printSchema) } catch { print(error.localizedDescription) } } // MARK: - Core Data Saving support func saveContext() { let context = persistentContainer.viewContext guard context.hasChanges else { return } do { try context.save() } catch { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") } } @objc func processUpdate(notification: NSNotification) { operationQueue.addOperation { let context = self.persistentContainer.newBackgroundContext() context.performAndWait { let items: [RegEx] do { try items = context.fetch(RegEx.fetchAllRegEx()) } catch { let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") } items.forEach { print("NAME: \($0.name) !!!!") } if context.hasChanges { do { try context.save() } catch { let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") } } } } } private lazy var operationQueue: OperationQueue = { var queue = OperationQueue() queue.maxConcurrentOperationCount = 1 return queue }() } ================================================ FILE: RegEx+/CoreData+CloudKit/RegEx.swift ================================================ // // RegEx+CoreDataClass.swift // RegEx+ // // Created by Lex on 2020/5/3. // Copyright © 2020 Lex.sh. All rights reserved. // // import Foundation import CoreData @objc(RegEx) public class RegEx: NSManagedObject { @NSManaged public var name: String @NSManaged public var raw: String @NSManaged public var sample: String @NSManaged public var substitution: String @NSManaged public var createdAt: Date @NSManaged public var updatedAt: Date @NSManaged public var allowCommentsAndWhitespace: Bool @NSManaged public var anchorsMatchLines: Bool @NSManaged public var caseInsensitive: Bool @NSManaged public var dotMatchesLineSeparators: Bool @NSManaged public var ignoreMetacharacters: Bool @NSManaged public var useUnicodeWordBoundaries: Bool @NSManaged public var useUnixLineSeparators: Bool convenience init(name: String = "Untitled") { self.init() self.name = name self.raw = "" self.sample = "" self.substitution = "" self.createdAt = Date() self.updatedAt = Date() } public var regularExpressionOptions: NSRegularExpression.Options { var options: NSRegularExpression.Options = [] if allowCommentsAndWhitespace { options.insert(.allowCommentsAndWhitespace) } if anchorsMatchLines { options.insert(.anchorsMatchLines) } if caseInsensitive { options.insert(.caseInsensitive) } if dotMatchesLineSeparators { options.insert(.dotMatchesLineSeparators) } if ignoreMetacharacters { options.insert(.ignoreMetacharacters) } if useUnicodeWordBoundaries { options.insert(.useUnicodeWordBoundaries) } if useUnixLineSeparators { options.insert(.useUnixLineSeparators) } return options } public var flagOptions: String { "" + (caseInsensitive ? "i" : "") + (allowCommentsAndWhitespace ? "x" : "") + (dotMatchesLineSeparators ? "." : "") + (anchorsMatchLines ? "m" : "") + (useUnicodeWordBoundaries ? "w" : "") } public override var description: String { "/\(raw)/\(flagOptions)" } public func isEqual(to object: RegEx) -> Bool { name == object.name && regularExpressionOptions == object.regularExpressionOptions && raw == object.raw && sample == object.sample && substitution == object.substitution && allowCommentsAndWhitespace == object.allowCommentsAndWhitespace && anchorsMatchLines == object.anchorsMatchLines && caseInsensitive == object.caseInsensitive && dotMatchesLineSeparators == object.dotMatchesLineSeparators && ignoreMetacharacters == object.ignoreMetacharacters && useUnicodeWordBoundaries == object.useUnicodeWordBoundaries && useUnixLineSeparators == object.useUnixLineSeparators } } ================================================ FILE: RegEx+/CoreData+CloudKit/RegExFetch.swift ================================================ // // RegExFetch.swift // RegEx+ // // Created by Lex on 2020/5/3. // Copyright © 2020 Lex.sh. All rights reserved. // import CoreData extension RegEx { @nonobjc public class func fetchRequest() -> NSFetchRequest { NSFetchRequest(entityName: "RegEx") } @nonobjc public class func fetchAllRegEx() -> NSFetchRequest { let req: NSFetchRequest = RegEx.fetchRequest() req.sortDescriptors = [ NSSortDescriptor(key: "createdAt", ascending: false), NSSortDescriptor(key: "updatedAt", ascending: false) ] return req } @nonobjc public class func fetch(byID ID: NSManagedObjectID) -> NSFetchRequest { let req: NSFetchRequest = RegEx.fetchRequest() req.predicate = NSPredicate(format: "self.objectID IN %@", ID) return req } } ================================================ FILE: RegEx+/Editor/EditorView.swift ================================================ // // EditorView.swift // RegEx+ // // Created by Lex on 2020/4/21. // Copyright © 2020 Lex.sh. All rights reserved. // import SwiftUI struct EditorView: View, Equatable { static func == (lhs: EditorView, rhs: EditorView) -> Bool { lhs.regEx.objectID == rhs.regEx.objectID } let regEx: RegEx @StateObject private var viewModel = EditorViewModel() @State private var isSharePresented = false @State private var copyButtonText = "Copy" @State private var isFlowViewVisible = false init(regEx: RegEx) { self.regEx = regEx } var body: some View { Group { if let regExBinding = Binding($viewModel.regEx) { List { Section(header: Text("Name")) { TextField("Name", text: regExBinding.name) .font(.headline) } RegExTextViewSection(regEx: regExBinding) Section(header: FlowViewHeaderView(isVisible: $isFlowViewVisible)) { if isFlowViewVisible { RegExFlowView(pattern: regExBinding.wrappedValue.raw) .frame(minHeight: 80) } } Section(header: SampleHeaderView(count: viewModel.matches.count)) { MatchesTextView( "$56.78 $90.12", text: regExBinding.sample, matches: $viewModel.matches ) .equatable() .padding(kTextFieldPadding) } SubstitutionSection( regExBinding: regExBinding, substitutionResult: viewModel.substitutionResult, copyButtonText: copyButtonText, copyAction: copyToClipboard ) } .navigationTitle(regExBinding.name) .toolbar { #if !targetEnvironment(macCatalyst) ToolbarItemGroup(placement: .navigationBarTrailing) { shareButton.padding() cheatSheetButton().padding(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 0)) } #endif } .gesture(dismissKeyboardDesture) .listStyle(InsetGroupedListStyle()) .onDisappear(perform: { viewModel.updateLastModified() DataManager.shared.saveContext() }) } else { Text("Loading...") .navigationTitle("RegEx+") } } .onAppear { viewModel.configure(with: regEx) } } private func copyToClipboard() { UIPasteboard.general.string = viewModel.substitutionResult copyButtonText = "Copied" DispatchQueue.main.asyncAfter(deadline: .now() + 2) { copyButtonText = "Copy" } } // https://stackoverflow.com/questions/56491386/how-to-hide-keyboard-when-using-swiftui private var dismissKeyboardDesture: some Gesture { DragGesture().onChanged { _ in UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } } private var shareButton: some View { Button(action: { self.isSharePresented = true }) { Image(systemName: "square.and.arrow.up") .imageScale(.large) } .accessibilityLabel("Share") .accessibilityHint("Share this regular expression") .sheet(isPresented: $isSharePresented) { if let regEx = viewModel.regEx { ActivityViewController(activityItems: [regEx.description]) } } } } private func cheatSheetButton() -> some View { #if targetEnvironment(macCatalyst) ZStack { Image(systemName: "wand.and.stars") .imageScale(.large) .foregroundColor(.accentColor) NavigationLink(destination: CheatSheetView()) { EmptyView() } .opacity(0) } .accessibilityLabel("Cheat Sheet") .accessibilityHint("View regular expression reference guide") #else NavigationLink(destination: CheatSheetView()) { Image(systemName: "wand.and.stars") .imageScale(.large) .foregroundColor(.accentColor) } .accessibilityLabel("Cheat Sheet") .accessibilityHint("View regular expression reference guide") #endif } private struct RegExTextViewSection: View { @Binding var regEx: RegEx @State private var isOptionsVisible = false var body: some View { Section(header: Text("Regular Expression")) { #if targetEnvironment(macCatalyst) HStack { RegExTextView( "Type RegEx here", text: $regEx.raw, showShortcutBar: false, highlightingMode: .regularExpression(regEx.regularExpressionOptions) ) .equatable() .padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 5)) cheatSheetButton() .frame(width: 20) } #else RegExTextView( "Type RegEx here", text: $regEx.raw, showShortcutBar: true, highlightingMode: .regularExpression(regEx.regularExpressionOptions) ) .padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 5)) #endif Button(action: { isOptionsVisible.toggle() }) { HStack { Text("Options") if !isOptionsVisible { Spacer() VStack(alignment: .trailing) { if regEx.caseInsensitive { Text("Case Insensitive") } if regEx.allowCommentsAndWhitespace { Text("Allow Comments and Whitespace") } if regEx.ignoreMetacharacters { Text("Ignore Metacharacters") } if regEx.anchorsMatchLines { Text("Anchors Match Lines") } if regEx.dotMatchesLineSeparators { Text("Dot Matches Line Separators") } if regEx.useUnixLineSeparators { Text("Use Unix Line Separators") } if regEx.useUnicodeWordBoundaries { Text("Use Unicode Word Boundaries") } } .font(.footnote) } } .foregroundColor(isOptionsVisible ? .secondary : .accentColor) } if isOptionsVisible { Toggle("Case Insensitive", isOn: $regEx.caseInsensitive) Toggle("Allow Comments and Whitespace", isOn: $regEx.allowCommentsAndWhitespace) Toggle("Ignore Metacharacters", isOn: $regEx.ignoreMetacharacters) Toggle("Anchors Match Lines", isOn: $regEx.anchorsMatchLines) Toggle("Dot Matches Line Separators", isOn: $regEx.dotMatchesLineSeparators) Toggle("Use Unix Line Separators", isOn: $regEx.useUnixLineSeparators) Toggle("Use Unicode Word Boundaries", isOn: $regEx.useUnicodeWordBoundaries) } } } } private struct SampleFooterView: View { var count: Int private var matchesString: String { return self.count == 1 ? "1 match" : "\(count) matches" } var body: some View { Text(matchesString) } } private struct SampleHeaderView: View { var count: Int var body: some View { HStack { Text("Sample Text") if count > 0 { Spacer() Text(count == 1 ? "1 match" : "\(count) matches") .font(.footnote) .foregroundColor(Color.secondary) .padding(EdgeInsets(top: 1, leading: 6, bottom: 1, trailing: 6)) .overlay( RoundedRectangle(cornerRadius: 20) .stroke(Color.secondary, lineWidth: 1) ) } } .frame(minHeight: 20) } } private let kTextFieldPadding = EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 5) #if DEBUG struct EditorView_Previews: PreviewProvider { private static var regEx: RegEx = { var r: RegEx = RegEx(context: DataManager.shared.persistentContainer.viewContext) r.name = "Dollars" r.raw = #"\$?((\d+)\.?(\d\d)?)"# r.sample = "$100.00 12.50 $10" r.substitution = "$3" return r }() static var previews: some View { Group { NavigationView { EditorView(regEx: regEx) } .environment(\.sizeCategory, .extraLarge) .previewLayout(.device) .previewDevice("iPhone 11") NavigationView { EditorView(regEx: regEx) } .previewDevice("iPhone 11") .preferredColorScheme(.dark) .environment(\.sizeCategory, .large) } } } #endif private struct SubstitutionSection: View { @Binding var regExBinding: RegEx let substitutionResult: String let copyButtonText: String let copyAction: () -> Void var body: some View { Section(header: Text("Substitution Template")) { #if targetEnvironment(macCatalyst) TextField("Price: $$$1\\.$2\\n", text: $regExBinding.substitution) .padding(kTextFieldPadding) #else RegExTextView( "Price: $$$1\\.$2\\n", text: $regExBinding.substitution, showShortcutBar: true, highlightingMode: .plainText ) .padding(kTextFieldPadding) #endif } if !regExBinding.substitution.isEmpty { Section(header: Text("Substitution Result")) { HStack { Text(substitutionResult) .padding(kTextFieldPadding) if !substitutionResult.isEmpty { Spacer() Button(action: copyAction) { Text("\(copyButtonText)") .font(.footnote) .foregroundColor(Color.accentColor) .padding(EdgeInsets(top: 1, leading: 6, bottom: 1, trailing: 6)) .overlay( RoundedRectangle(cornerRadius: 20) .stroke(Color.accentColor, lineWidth: 1) ) } .accessibilityLabel("Copy substitution result") .accessibilityHint("Copies the substitution result to clipboard") } } } } } } private struct FlowViewHeaderView: View { @Binding var isVisible: Bool var body: some View { Button(action: { withAnimation { isVisible.toggle() } }) { HStack { Text("Flow Diagram") Spacer() Image(systemName: isVisible ? "chevron.down" : "chevron.right") .font(.caption) .foregroundColor(.secondary) } } .foregroundColor(isVisible ? .primary : .accentColor) } } ================================================ FILE: RegEx+/Editor/EditorViewModel.swift ================================================ // // RegExEditorViewModel.swift // RegEx+ // // Created by Lex on 2020/4/25. // Copyright © 2020 Lex.sh. All rights reserved. // import Foundation import Combine class EditorViewModel : ObservableObject, Equatable { @Published var regEx: RegEx? @Published var matches = [NSTextCheckingResult]() @Published var substitutionResult = "" private var cancellables = Set() init() { self.regEx = nil setupBindings() } func configure(with regEx: RegEx) { self.regEx = regEx } private func setupBindings() { let optionsObservable = $regEx .compactMap { $0 } .map(\.regularExpressionOptions) let regExObservable = $regEx .compactMap { $0 } .map(\.raw) .throttle(for: 0.2, scheduler: RunLoop.main, latest: true) .removeDuplicates() .combineLatest(optionsObservable) .compactMap { (raw, options) in try? NSRegularExpression(pattern: raw, options: options) } let sampleObservable = $regEx .compactMap { $0 } .map(\.sample) .throttle(for: 0.2, scheduler: RunLoop.main, latest: true) .removeDuplicates() let substitutionObservalbe = $regEx .compactMap { $0 } .map(\.substitution) .throttle(for: 0.2, scheduler: RunLoop.main, latest: true) .removeDuplicates() let subAndSampleObservable = substitutionObservalbe.combineLatest(sampleObservable) .map { ($0.0, $0.1) } regExObservable .combineLatest(sampleObservable) .sink { [weak self] (reg: NSRegularExpression, sample: String) in let range = NSRange(location: 0, length: sample.count) self?.matches = reg.matches(in: sample, options: [], range: range) } .store(in: &cancellables) regExObservable .combineLatest(subAndSampleObservable) .map { ($0, $1.0, $1.1) } .sink { [weak self] (reg: NSRegularExpression, sub: String, sample: String) in let range = NSRange(location: 0, length: sample.count) self?.substitutionResult = reg.stringByReplacingMatches( in: sample, options: [], range: range, withTemplate: sub ) } .store(in: &cancellables) } // Keep the old initializer for compatibility convenience init(regEx: RegEx) { self.init() self.regEx = regEx } func updateLastModified() { if let regEx, regEx.hasChanges { regEx.updatedAt = Date() } } static func == (lhs: EditorViewModel, rhs: EditorViewModel) -> Bool { if let lr = lhs.regEx, let rr = rhs.regEx { return lr.isEqual(to: rr) == true && lhs.substitutionResult == rhs.substitutionResult && lhs.matches == rhs.matches } return false } } ================================================ FILE: RegEx+/Editor/RegExFlowView.swift ================================================ // // RegExFlowView.swift // RegEx+ // // Created by Lex on 2026/4/11. // Copyright © 2020 Lex.sh. All rights reserved. // // swiftlint:disable file_length type_body_length cyclomatic_complexity import SwiftUI import _RegexParser struct RegExFlowView: View { let pattern: String private var diagram: FlowComponent { var builder = FlowDiagramBuilder(source: pattern) return builder.build() } private var breakdown: FlowPatternBreakdown { FlowPatternBreakdownBuilder(source: pattern).build() } var body: some View { VStack(alignment: .leading, spacing: 12) { if pattern.isEmpty { Text("Enter a regular expression to see the flow diagram") .foregroundStyle(.secondary) .font(.footnote) .padding() } else { ScrollView(.horizontal, showsIndicators: true) { HStack { Spacer(minLength: 0) FlowDiagramView(component: diagram) .padding(.vertical, 4) Spacer(minLength: 0) } .frame(maxWidth: .infinity) } if breakdown.hasItems { PatternBreakdownView(breakdown: breakdown) .padding(.top, 4) } } } .frame(maxWidth: .infinity, alignment: .leading) .padding(.vertical, 4) } } private struct FlowDiagramBuilder { let source: String private var captureGroupIndex = 0 init(source: String) { self.source = source } mutating func build() -> FlowComponent { let ast = parseWithRecovery(source, .traditional) var components = [FlowComponent]() if let globalOptions = ast.globalOptions { components.append(contentsOf: globalOptions.options.map { option in .node( FlowNode( style: .directive, label: sourceText(for: option.location) ?? "Options" ) ) }) } let root = component(from: ast.root) switch root { case .sequence(let children): components.append(contentsOf: children) case .empty: break default: components.append(root) } return normalizedSequence(components) } private mutating func component(from node: AST.Node) -> FlowComponent { switch node { case .alternation(let alternation): var branches = [[FlowComponent]]() for child in alternation.children { branches.append(branch(from: child)) } return .alternation(branches) case .concatenation(let concatenation): var children = [FlowComponent]() for child in concatenation.children { if let component = semanticComponent(from: child) { children.append(component) } } return normalizedSequence(children) case .group(let group): let groupKind = group.kind.value return .group( FlowGroup( style: style(for: groupKind), title: title(for: groupKind, captureReference: nextCaptureReference(for: groupKind)), content: component(from: group.child) ) ) case .conditional(let conditional): return .group( FlowGroup( style: .assertion, title: "Conditional \(conditionLabel(for: conditional.condition))", content: .alternation([ branch(from: conditional.trueBranch), branch(from: conditional.falseBranch) ]) ) ) case .quantification(let quantification): return .quantified( component(from: quantification.child), quantifier(for: quantification) ) case .quote(let quote): return .node( FlowNode( style: .literal, label: sourceText(for: quote.location) ?? quote.literal ) ) case .trivia: return .empty case .interpolation(let interpolation): return .node( FlowNode( style: .directive, label: sourceText(for: interpolation.location) ?? interpolation.contents ) ) case .atom(let atom): return .node(flowNode(for: atom)) case .customCharacterClass(let characterClass): return .node( FlowNode( style: .characterClass, label: sourceText(for: characterClass.location) ?? "[...]" ) ) case .absentFunction(let absentFunction): return .node( FlowNode( style: .special, label: sourceText(for: absentFunction.location) ?? "(?~...)" ) ) case .empty: return .empty } } private mutating func semanticComponent(from node: AST.Node) -> FlowComponent? { let component = component(from: node) if case .empty = component { return nil } return component } private mutating func branch(from node: AST.Node) -> [FlowComponent] { let resolved = component(from: node) switch resolved { case .sequence(let children): return children.isEmpty ? [.empty] : children case .empty: return [.empty] default: return [resolved] } } private func normalizedSequence(_ components: [FlowComponent]) -> FlowComponent { let flattened = components.flatMap { component -> [FlowComponent] in switch component { case .sequence(let children): return children case .empty: return [] default: return [component] } } let compacted = mergeContinuousLiteralNodes(in: flattened) switch compacted.count { case 0: return .empty case 1: return compacted[0] default: return .sequence(compacted) } } private func mergeContinuousLiteralNodes(in components: [FlowComponent]) -> [FlowComponent] { var merged = [FlowComponent]() for component in components { guard case .node(let node) = component, node.style == .literal else { merged.append(component) continue } if let last = merged.last, case .node(let previous) = last, previous.style == .literal { merged.removeLast() merged.append( .node( FlowNode( style: .literal, label: previous.label + node.label ) ) ) } else { merged.append(component) } } return merged } private func quantifier(for quantification: AST.Quantification) -> FlowQuantifier { let label: String if let sourceLabel = sourceText( from: quantification.amount.location.start, to: quantification.location.end ) { label = sourceLabel } else { let amount: String switch quantification.amount.value { case .zeroOrMore: amount = "*" case .oneOrMore: amount = "+" case .zeroOrOne: amount = "?" case .exactly(let number): amount = "{\(number.value ?? 0)}" case .nOrMore(let number): amount = "{\(number.value ?? 0),}" case .upToN(let number): amount = "{,\(number.value ?? 0)}" case .range(let lower, let upper): amount = "{\(lower.value ?? 0),\(upper.value ?? 0)}" } label = amount + quantification.kind.value.rawValue } return FlowQuantifier( label: label, isOptional: isOptional(quantification.amount.value) ) } private func isOptional(_ amount: AST.Quantification.Amount) -> Bool { switch amount { case .zeroOrMore, .zeroOrOne, .upToN: return true case .range(let lower, _): return (lower.value ?? 0) == 0 case .exactly, .nOrMore, .oneOrMore: return false } } private func flowNode(for atom: AST.Atom) -> FlowNode { let label = sourceText(for: atom.location) ?? fallbackLabel(for: atom) switch atom.kind { case .dot: return FlowNode(style: .wildcard, label: "Any char") case .caretAnchor: return FlowNode(style: .anchor, label: "^ Start") case .dollarAnchor: return FlowNode(style: .anchor, label: "$ End") case .property, .escaped: return FlowNode(style: escapedStyle(for: atom), label: label) case .backreference, .subpattern: return FlowNode(style: .special, label: label) case .callout, .backtrackingDirective, .changeMatchingOptions: return FlowNode(style: .directive, label: label) case .invalid: return FlowNode(style: .invalid, label: label) case .char, .scalar, .scalarSequence, .keyboardControl, .keyboardMeta, .keyboardMetaControl, .namedCharacter: return FlowNode(style: .literal, label: label) } } private func escapedStyle(for atom: AST.Atom) -> FlowNode.Style { guard case .escaped(let builtin) = atom.kind else { return .characterClass } switch builtin { case .wordBoundary, .notWordBoundary: return .assertion case .startOfSubject, .endOfSubjectBeforeNewline, .endOfSubject, .firstMatchingPositionInSubject: return .anchor case .alarm, .escape, .formfeed, .newline, .carriageReturn, .tab, .backspace: return .literal default: return .characterClass } } private func fallbackLabel(for atom: AST.Atom) -> String { switch atom.kind { case .char(let character): return String(character) case .scalar(let scalar): return String(scalar.value) case .scalarSequence(let sequence): return sequence.scalarValues.map(String.init).joined() case .property: return "Property" case .escaped(let builtin): return "\\\(builtin.character)" case .keyboardControl(let character): return "\\C-\(character)" case .keyboardMeta(let character): return "\\M-\(character)" case .keyboardMetaControl(let character): return "\\M-\\C-\(character)" case .namedCharacter(let name): return "\\N{\(name)}" case .dot: return "." case .caretAnchor: return "^" case .dollarAnchor: return "$" case .backreference: return "Backreference" case .subpattern: return "Subpattern" case .callout: return "Callout" case .backtrackingDirective: return "Directive" case .changeMatchingOptions: return "Options" case .invalid: return "Invalid" } } private func style(for kind: AST.Group.Kind) -> FlowNode.Style { switch kind { case .capture, .namedCapture, .balancedCapture: return .capturingGroup case .lookahead, .negativeLookahead, .nonAtomicLookahead, .lookbehind, .negativeLookbehind, .nonAtomicLookbehind: return .assertion case .changeMatchingOptions: return .directive case .scriptRun, .atomicScriptRun: return .special case .nonCapture, .nonCaptureReset, .atomicNonCapturing: return .grouping } } private func title(for kind: AST.Group.Kind, captureReference: String?) -> String { let suffix = captureReference.map { " \($0)" } ?? "" switch kind { case .capture: return "Group\(suffix)" case .namedCapture(let name): return "Group <\(name.value)>\(suffix)" case .balancedCapture(let balanced): let current = balanced.name?.value ?? "" return "Group <\(current)-\(balanced.priorName.value)>\(suffix)" case .nonCapture: return "Group" case .nonCaptureReset: return "Branch Reset Group" case .atomicNonCapturing: return "Atomic Group" case .lookahead: return "Lookahead" case .negativeLookahead: return "Negative Lookahead" case .nonAtomicLookahead: return "Non-atomic Lookahead" case .lookbehind: return "Lookbehind" case .negativeLookbehind: return "Negative Lookbehind" case .nonAtomicLookbehind: return "Non-atomic Lookbehind" case .scriptRun: return "Script Run" case .atomicScriptRun: return "Atomic Script Run" case .changeMatchingOptions: return "Scoped Options" } } private mutating func nextCaptureReference(for kind: AST.Group.Kind) -> String? { switch kind { case .capture, .namedCapture, .balancedCapture: captureGroupIndex += 1 return "$\(captureGroupIndex)" default: return nil } } private func conditionLabel(for condition: AST.Conditional.Condition) -> String { switch condition.kind { case .groupMatched: return "if group matched" case .recursionCheck: return "if recursion" case .groupRecursionCheck: return "if group recursion" case .defineGroup: return "define group" case .pcreVersionCheck: return "if PCRE version" case .group: return "if nested pattern" } } private func sourceText(for location: SourceLocation) -> String? { sourceText(from: location.start, to: location.end) } private func sourceText(from start: String.Index, to end: String.Index) -> String? { guard start >= source.startIndex, end <= source.endIndex, start <= end else { return nil } return String(source[start.. FlowPatternBreakdown { let ast = parseWithRecovery(source, .traditional) let catalog = CheatSheetCatalog.shared var collector = FlowPatternBreakdownCollector(source: source, catalog: catalog) if let globalOptions = ast.globalOptions { globalOptions.options.forEach { option in collector.collectGlobalOption(option) } } collector.collect(from: ast.root) return collector.result } } private struct FlowPatternBreakdown { let metacharacters: [FlowCheatSheetMatch] let operators: [FlowCheatSheetMatch] var hasItems: Bool { !metacharacters.isEmpty || !operators.isEmpty } } private struct FlowCheatSheetMatch: Hashable { let id: String let title: String let description: String } private enum FlowCheatSheetKey: String { case alarm case startOfInput case wordBoundary case backspaceInSet case notWordBoundary case controlCharacter case decimalDigit case notDecimalDigit case escapeCharacter case quoteEnd case formFeed case previousMatchEnd case newline case namedCharacter case unicodeProperty case unicodePropertyInverted case quoteStart case carriageReturn case whitespace case notWhitespace case tab case unicodeScalar4 case unicodeScalar8 case wordCharacter case notWordCharacter case hexScalarBraced case hexScalar2 case graphemeCluster case endOfInputBeforeNewline case endOfInput case backreference case octalScalar case customCharacterClass case wildcard case lineStart case lineEnd case escapedLiteral case alternation case zeroOrMore case oneOrMore case zeroOrOne case exactlyN case nOrMore case range case zeroOrMoreReluctant case oneOrMoreReluctant case zeroOrOneReluctant case exactlyNReluctant case nOrMoreReluctant case rangeReluctant case zeroOrMorePossessive case oneOrMorePossessive case zeroOrOnePossessive case exactlyNPossessive case nOrMorePossessive case rangePossessive case capturingGroup case nonCapturingGroup case atomicGroup case commentGroup case lookahead case negativeLookahead case lookbehind case negativeLookbehind case scopedOptionChange case inlineOptionChange } private struct CheatSheetCatalog { static let shared = CheatSheetCatalog.load() let metacharacters: [FlowCheatSheetKey: CheatSheetPlist.Item] let operators: [FlowCheatSheetKey: CheatSheetPlist.Item] func metacharacter(for key: FlowCheatSheetKey) -> CheatSheetPlist.Item? { metacharacters[key] } func operatorItem(for key: FlowCheatSheetKey) -> CheatSheetPlist.Item? { operators[key] } private static func load() -> CheatSheetCatalog { let plist = CheatSheetPlist.localizedCheatSheet ?? CheatSheetPlist(metacharacters: [], operators: []) let metacharacters = Dictionary( uniqueKeysWithValues: plist.metacharacters.compactMap { item -> (FlowCheatSheetKey, CheatSheetPlist.Item)? in guard let key = metacharacterKey(for: item.exp, description: item.des) else { return nil } return (key, item) } ) let operators = Dictionary( uniqueKeysWithValues: plist.operators.compactMap { item -> (FlowCheatSheetKey, CheatSheetPlist.Item)? in guard let key = operatorKey(for: item.exp) else { return nil } return (key, item) } ) return CheatSheetCatalog(metacharacters: metacharacters, operators: operators) } private static func metacharacterKey(for expression: String, description: String) -> FlowCheatSheetKey? { switch expression { case "\\a": return .alarm case "\\A": return .startOfInput case "\\b, outside of a [Set]": return .wordBoundary case "\\b, within a [Set]": return .backspaceInSet case "\\B": return .notWordBoundary case "\\cX": return .controlCharacter case "\\d": return .decimalDigit case "\\D": return .notDecimalDigit case "\\e": return .escapeCharacter case "\\E": return .quoteEnd case "\\f": return .formFeed case "\\G": return .previousMatchEnd case "\\n": return description.contains("Back Reference") ? .backreference : .newline case "\\N{UNICODE CHARACTER NAME}": return .namedCharacter case "\\p{UNICODE PROPERTY NAME}": return .unicodeProperty case "\\P{UNICODE PROPERTY NAME}": return .unicodePropertyInverted case "\\Q": return .quoteStart case "\\r": return .carriageReturn case "\\s": return .whitespace case "\\S": return .notWhitespace case "\\t": return .tab case "\\uhhhh": return .unicodeScalar4 case "\\Uhhhhhhhh": return .unicodeScalar8 case "\\w": return .wordCharacter case "\\W": return .notWordCharacter case "\\x{hhhh}": return .hexScalarBraced case "\\xhh": return .hexScalar2 case "\\X": return .graphemeCluster case "\\Z": return .endOfInputBeforeNewline case "\\z": return .endOfInput case "\\0ooo": return .octalScalar case "[pattern]": return .customCharacterClass case ".": return .wildcard case "^": return .lineStart case "$": return .lineEnd case "\\": return .escapedLiteral default: return nil } } private static func operatorKey(for expression: String) -> FlowCheatSheetKey? { switch expression { case "|": return .alternation case "*": return .zeroOrMore case "+": return .oneOrMore case "?": return .zeroOrOne case "{n}": return .exactlyN case "{n,}": return .nOrMore case "{n,m}": return .range case "*?": return .zeroOrMoreReluctant case "+?": return .oneOrMoreReluctant case "??": return .zeroOrOneReluctant case "{n}?": return .exactlyNReluctant case "{n,}?": return .nOrMoreReluctant case "{n,m}?": return .rangeReluctant case "*+": return .zeroOrMorePossessive case "++": return .oneOrMorePossessive case "?+": return .zeroOrOnePossessive case "{n}+": return .exactlyNPossessive case "{n,}+": return .nOrMorePossessive case "{n,m}+": return .rangePossessive case "(...)": return .capturingGroup case "(?:...)": return .nonCapturingGroup case "(?>...)": return .atomicGroup case "(?# ... )": return .commentGroup case "(?= ... )": return .lookahead case "(?! ... )": return .negativeLookahead case "(?<= ... )": return .lookbehind case "(?() private var seenOperators = Set() init(source: String, catalog: CheatSheetCatalog) { self.source = source self.catalog = catalog } var result: FlowPatternBreakdown { FlowPatternBreakdown(metacharacters: metacharacters, operators: operators) } mutating func collect(from node: AST.Node) { switch node { case .alternation(let alternation): addOperator(.alternation) alternation.children.forEach { collect(from: $0) } case .concatenation(let concatenation): concatenation.children.forEach { collect(from: $0) } case .group(let group): collect(group) case .conditional(let conditional): collect(condition: conditional.condition) collect(from: conditional.trueBranch) collect(from: conditional.falseBranch) case .quantification(let quantification): addOperator(quantifierSignature(for: quantification)) collect(from: quantification.child) case .quote: addMetacharacter(.quoteStart) addMetacharacter(.quoteEnd) case .trivia(let trivia): if sourceText(for: trivia.location)?.hasPrefix("(?#") == true { addOperator(.commentGroup) } case .interpolation: break case .atom(let atom): collect(atom) case .customCharacterClass(let characterClass): addMetacharacter(.customCharacterClass) characterClass.members.forEach { collect(characterClassMember: $0) } case .absentFunction, .empty: break } } mutating func collectGlobalOption(_ option: AST.GlobalMatchingOption) { addOperator(.inlineOptionChange, display: sourceText(for: option.location)) } private mutating func collect(_ group: AST.Group) { switch group.kind.value { case .capture, .namedCapture, .balancedCapture: addOperator(.capturingGroup) case .nonCapture, .nonCaptureReset: addOperator(.nonCapturingGroup) case .atomicNonCapturing: addOperator(.atomicGroup) case .lookahead, .nonAtomicLookahead: addOperator(.lookahead) case .negativeLookahead: addOperator(.negativeLookahead) case .lookbehind, .nonAtomicLookbehind: addOperator(.lookbehind) case .negativeLookbehind: addOperator(.negativeLookbehind) case .changeMatchingOptions: addOperator(.scopedOptionChange, display: sourceText(for: group.location)) case .scriptRun, .atomicScriptRun: break } collect(from: group.child) } private mutating func collect(condition: AST.Conditional.Condition) { if case .group(let group) = condition.kind { collect(group) } } private mutating func collect(_ atom: AST.Atom) { let text = sourceText(for: atom.location) switch atom.kind { case .char: if text?.hasPrefix("\\") == true { addMetacharacter(.escapedLiteral, display: text) } case .scalar: if let text { if text.hasPrefix("\\u") { addMetacharacter(.unicodeScalar4, display: text) } else if text.hasPrefix("\\U") { addMetacharacter(.unicodeScalar8, display: text) } else if text.hasPrefix("\\x{") { addMetacharacter(.hexScalarBraced, display: text) } else if text.hasPrefix("\\x") { addMetacharacter(.hexScalar2, display: text) } else if text.hasPrefix("\\0") { addMetacharacter(.octalScalar, display: text) } } case .scalarSequence: addMetacharacter(.hexScalarBraced, display: text) case .property(let property): addMetacharacter(property.isInverted ? .unicodePropertyInverted : .unicodeProperty, display: text) case .escaped(let builtin): collectEscapedBuiltin(builtin, display: text) case .keyboardControl: addMetacharacter(.controlCharacter, display: text) case .keyboardMeta, .keyboardMetaControl: break case .namedCharacter: addMetacharacter(.namedCharacter, display: text) case .dot: addMetacharacter(.wildcard, display: text) case .caretAnchor: addMetacharacter(.lineStart, display: text) case .dollarAnchor: addMetacharacter(.lineEnd, display: text) case .backreference: addMetacharacter(.backreference, display: text) case .subpattern: break case .callout, .backtrackingDirective: break case .changeMatchingOptions: addOperator(.inlineOptionChange, display: text) case .invalid: break } } private mutating func collectEscapedBuiltin(_ builtin: AST.Atom.EscapedBuiltin, display: String?) { switch builtin { case .alarm: addMetacharacter(.alarm, display: display) case .escape: addMetacharacter(.escapeCharacter, display: display) case .formfeed: addMetacharacter(.formFeed, display: display) case .newline: addMetacharacter(.newline, display: display) case .carriageReturn: addMetacharacter(.carriageReturn, display: display) case .tab: addMetacharacter(.tab, display: display) case .decimalDigit: addMetacharacter(.decimalDigit, display: display) case .notDecimalDigit: addMetacharacter(.notDecimalDigit, display: display) case .whitespace: addMetacharacter(.whitespace, display: display) case .notWhitespace: addMetacharacter(.notWhitespace, display: display) case .wordCharacter: addMetacharacter(.wordCharacter, display: display) case .notWordCharacter: addMetacharacter(.notWordCharacter, display: display) case .graphemeCluster: addMetacharacter(.graphemeCluster, display: display) case .wordBoundary: addMetacharacter(.wordBoundary, display: display) case .notWordBoundary: addMetacharacter(.notWordBoundary, display: display) case .startOfSubject: addMetacharacter(.startOfInput, display: display) case .endOfSubjectBeforeNewline: addMetacharacter(.endOfInputBeforeNewline, display: display) case .endOfSubject: addMetacharacter(.endOfInput, display: display) case .firstMatchingPositionInSubject: addMetacharacter(.previousMatchEnd, display: display) case .backspace: addMetacharacter(.backspaceInSet, display: display) case .singleDataUnit, .horizontalWhitespace, .notHorizontalWhitespace, .notNewline, .newlineSequence, .verticalTab, .notVerticalTab, .resetStartOfMatch, .trueAnychar, .textSegment, .notTextSegment: break } } private mutating func collect(characterClassMember member: AST.CustomCharacterClass.Member) { switch member { case .custom(let characterClass): addMetacharacter(.customCharacterClass) characterClass.members.forEach { collect(characterClassMember: $0) } case .range(let range): collect(range.lhs) collect(range.rhs) case .atom(let atom): collect(atom) case .quote: addMetacharacter(.quoteStart) addMetacharacter(.quoteEnd) case .trivia: break case .setOperation(let lhs, _, let rhs): lhs.forEach { collect(characterClassMember: $0) } rhs.forEach { collect(characterClassMember: $0) } } } private func quantifierSignature(for quantification: AST.Quantification) -> FlowCheatSheetKey { switch (quantification.amount.value, quantification.kind.value) { case (.zeroOrMore, .eager): return .zeroOrMore case (.oneOrMore, .eager): return .oneOrMore case (.zeroOrOne, .eager): return .zeroOrOne case (.exactly, .eager): return .exactlyN case (.nOrMore, .eager): return .nOrMore case (.range, .eager), (.upToN, .eager): return .range case (.zeroOrMore, .reluctant): return .zeroOrMoreReluctant case (.oneOrMore, .reluctant): return .oneOrMoreReluctant case (.zeroOrOne, .reluctant): return .zeroOrOneReluctant case (.exactly, .reluctant): return .exactlyNReluctant case (.nOrMore, .reluctant): return .nOrMoreReluctant case (.range, .reluctant), (.upToN, .reluctant): return .rangeReluctant case (.zeroOrMore, .possessive): return .zeroOrMorePossessive case (.oneOrMore, .possessive): return .oneOrMorePossessive case (.zeroOrOne, .possessive): return .zeroOrOnePossessive case (.exactly, .possessive): return .exactlyNPossessive case (.nOrMore, .possessive): return .nOrMorePossessive case (.range, .possessive), (.upToN, .possessive): return .rangePossessive } } private mutating func addMetacharacter(_ key: FlowCheatSheetKey, display: String? = nil) { guard let item = catalog.metacharacter(for: key), seenMetacharacters.insert(key.rawValue).inserted else { return } metacharacters.append( FlowCheatSheetMatch( id: key.rawValue, title: display ?? item.exp, description: item.des ) ) } private mutating func addOperator(_ key: FlowCheatSheetKey, display: String? = nil) { guard let item = catalog.operatorItem(for: key), seenOperators.insert(key.rawValue).inserted else { return } operators.append( FlowCheatSheetMatch( id: key.rawValue, title: display ?? item.exp, description: item.des ) ) } private func sourceText(for location: SourceLocation) -> String? { guard location.start >= source.startIndex, location.end <= source.endIndex, location.start <= location.end else { return nil } return String(source[location.start.. 0 { Spacer(minLength: 6) Image(systemName: "arrow.right") .font(.caption2.weight(.semibold)) .foregroundStyle(FlowPalette.connector) .frame(width: 14) Spacer(minLength: 6) } FlowComponentView(component: component) } } .frame(maxWidth: .infinity, alignment: .center) } } private struct AlternationView: View { let branches: [[FlowComponent]] var body: some View { VStack(alignment: .center, spacing: 8) { ForEach(Array(branches.enumerated()), id: \.offset) { index, branch in if index > 0 { FlowAlternationDivider() } FlowSequenceView(components: branch) } } .frame(maxWidth: .infinity, alignment: .center) .padding(.vertical, 2) } } private struct GroupView: View { let group: FlowGroup let borderStyle: FlowBorderStyle var body: some View { VStack(alignment: .center, spacing: 8) { Text(group.title.uppercased()) .font(.caption.weight(.bold)) .tracking(1.6) .foregroundStyle(titleColor) .multilineTextAlignment(.center) .frame(maxWidth: .infinity, alignment: .center) VStack(alignment: .center, spacing: 8) { FlowComponentView(component: group.content) } .frame(maxWidth: .infinity, alignment: .center) } .padding(.horizontal, 10) .padding(.vertical, 10) .overlay( RoundedRectangle(cornerRadius: 18, style: .continuous) .stroke(borderColor, style: borderStyle.strokeStyle(lineWidth: 1.5)) ) } private var borderColor: Color { styleColor(for: group.style).opacity(0.28) } private var titleColor: Color { styleColor(for: group.style) } } private struct QuantifiedFlowView: View { let component: FlowComponent let quantifier: FlowQuantifier let borderStyle: FlowBorderStyle var body: some View { VStack(spacing: 2) { FlowComponentView(component: component, borderStyle: borderStyle) Text(quantifier.label) .font(.caption2.monospaced()) .foregroundStyle(quantifierColor) } } private var quantifierColor: Color { if quantifier.label.contains("?") { return FlowPalette.quantifierSecondary } return FlowPalette.quantifierPrimary } } private struct NodeView: View { let node: FlowNode let borderStyle: FlowBorderStyle var body: some View { Text(node.label) .font(.system(size: 13, weight: .semibold, design: .monospaced)) .lineLimit(1) .foregroundStyle(labelColor) .frame(minWidth: 40, minHeight: 40) .padding(.horizontal, 8) .background( RoundedRectangle(cornerRadius: 10, style: .continuous) .fill(fillColor) .overlay( RoundedRectangle(cornerRadius: 10, style: .continuous) .stroke(borderColor, style: borderStyle.strokeStyle(lineWidth: 1)) ) ) .fixedSize() } private var fillColor: Color { if isEndpoint { return FlowPalette.endpointFill } return FlowPalette.nodeFill } private var borderColor: Color { if isEndpoint { return FlowPalette.endpointBorder } return color.opacity(0.28) } private var labelColor: Color { FlowPalette.ink } private var isEndpoint: Bool { node.style == .anchor && (node.label.contains("Start") || node.label.contains("End")) } private var color: Color { styleColor(for: node.style) } } private enum FlowBorderStyle { case solid case dashed func strokeStyle(lineWidth: CGFloat) -> StrokeStyle { switch self { case .solid: return StrokeStyle(lineWidth: lineWidth) case .dashed: return StrokeStyle(lineWidth: lineWidth, dash: [6, 4]) } } } private extension FlowComponent { var supportsBorderStyling: Bool { switch self { case .node, .group: return true case .sequence, .alternation, .quantified, .empty: return false } } } private struct FlowAlternationDivider: View { var body: some View { HStack(spacing: 10) { Rectangle() .fill(FlowPalette.divider) .frame(height: 1) Text("OR") .font(.caption.weight(.bold)) .tracking(1.4) .foregroundStyle(FlowPalette.secondaryText) Rectangle() .fill(FlowPalette.divider) .frame(height: 1) } .padding(.horizontal, 6) } } private func styleColor(for style: FlowNode.Style) -> Color { switch style { case .literal: return FlowPalette.literal case .characterClass: return FlowPalette.characterClass case .capturingGroup: return FlowPalette.group case .grouping: return FlowPalette.grouping case .assertion: return FlowPalette.assertion case .anchor: return FlowPalette.anchor case .wildcard: return FlowPalette.wildcard case .directive: return FlowPalette.directive case .special: return FlowPalette.special case .invalid: return FlowPalette.invalid } } private enum FlowPalette { static let nodeFill = Color(uiColor: .secondarySystemBackground) static let endpointFill = Color.accentColor.opacity(0.18) static let endpointBorder = Color.accentColor.opacity(0.45) static let connector = Color.secondary.opacity(0.45) static let divider = Color.secondary.opacity(0.22) static let ink = Color.primary static let secondaryText = Color.secondary static let sectionLabel = Color.accentColor static let tokenBoxFill = Color(uiColor: .secondarySystemBackground) static let tokenBoxBorder = Color.accentColor.opacity(0.20) static let quantifierPrimary = Color(red: 0.267, green: 0.553, blue: 0.942) static let quantifierSecondary = Color(red: 0.000, green: 0.620, blue: 0.592) static let literal = Color(red: 0.430, green: 0.620, blue: 0.920) static let characterClass = Color(red: 0.336, green: 0.700, blue: 0.650) static let group = Color(red: 0.290, green: 0.560, blue: 0.900) static let grouping = Color(red: 0.500, green: 0.620, blue: 0.860) static let assertion = Color(red: 0.396, green: 0.690, blue: 0.880) static let anchor = Color(red: 0.420, green: 0.560, blue: 0.840) static let wildcard = Color(red: 0.290, green: 0.700, blue: 0.820) static let directive = Color(red: 0.510, green: 0.620, blue: 0.920) static let special = Color(red: 0.470, green: 0.650, blue: 0.850) static let invalid = Color(red: 0.650, green: 0.690, blue: 0.760) } private extension CheatSheetPlist { static let localizedCheatSheet: CheatSheetPlist? = { guard let url = Bundle.main.url(forResource: "CheatSheet", withExtension: "plist"), let data = try? Data(contentsOf: url) else { return nil } return try? PropertyListDecoder().decode(CheatSheetPlist.self, from: data) }() } // swiftlint:enable file_length type_body_length cyclomatic_complexity ================================================ FILE: RegEx+/HomeView.swift ================================================ // // TabView.swift // RegEx+ // // Created by Lex on 2020/4/21. // Copyright © 2020 Lex.sh. All rights reserved. // import SwiftUI struct HomeView: View { @Environment(\.managedObjectContext) var managedObjectContext var body: some View { if #available(iOS 16.0, macOS 13.0, *) { NavigationSplitView { LibraryView() } detail: { Text(verbatim: "RegEx+") .font(.largeTitle) } } else { NavigationView { LibraryView() Text(verbatim: "RegEx+") .font(.largeTitle) } .currentDeviceNavigationViewStyle() } } } private extension View { func currentDeviceNavigationViewStyle() -> AnyView { #if targetEnvironment(macCatalyst) return AnyView( navigationViewStyle(DoubleColumnNavigationViewStyle()) ) #else if UIDevice.current.userInterfaceIdiom == .pad { return AnyView(navigationViewStyle(DoubleColumnNavigationViewStyle())) } else { return AnyView(navigationViewStyle(DefaultNavigationViewStyle())) } #endif } } struct ContentView_Previews: PreviewProvider { static var previews: some View { HomeView() } } ================================================ FILE: RegEx+/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName RegEx+ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption LSApplicationCategoryType public.app-category.developer-tools LSRequiresIPhoneOS UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UISceneConfigurations UIWindowSceneSessionRoleApplication UISceneConfigurationName Default Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).SceneDelegate UIBackgroundModes remote-notification UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities armv7 UIStatusBarTintParameters UINavigationBar Style UIBarStyleDefault Translucent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: RegEx+/Library/LibraryItemView.swift ================================================ // // LibraryItemView.swift // RegEx+ // // Created by Lex on 2020/5/3. // Copyright © 2020 Lex.sh. All rights reserved. // import SwiftUI import CoreData struct LibraryItemView: View, Equatable { @ObservedObject var regEx: RegEx var body: some View { NavigationLink { EditorView(regEx: regEx) .equatable() .id(regEx.objectID) } label: { VStack(alignment: .leading, spacing: 4) { Text(regEx.name) .font(.headline) if !regEx.raw.isEmpty { Text(regEx.raw) .font(.subheadline) .foregroundColor(.secondary) .lineLimit(1) } } .frame(minHeight: 50, maxHeight: 200) .paddingVertical() } .isDetailLink(true) } static func == (lhs: LibraryItemView, rhs: LibraryItemView) -> Bool { lhs.regEx.objectID == rhs.regEx.objectID && lhs.regEx.name == rhs.regEx.name && lhs.regEx.raw == rhs.regEx.raw } } private extension View { func paddingVertical() -> AnyView { #if targetEnvironment(macCatalyst) AnyView(padding(.vertical)) #else AnyView(self) #endif } } struct LibraryItemView_Previews: PreviewProvider { private static var regEx: RegEx = { var r: RegEx = RegEx(context: DataManager.shared.persistentContainer.viewContext) r.name = "Dollars" r.raw = #"\$?((\d+)\.?(\d\d)?)"# r.sample = "$100.00 12.50 $10" r.substitution = "$3" return r }() static var previews: some View { NavigationView { List { LibraryItemView(regEx: regEx) LibraryItemView(regEx: regEx) LibraryItemView(regEx: regEx) } } .navigationTitle(Text(verbatim: "Test")) } } ================================================ FILE: RegEx+/Library/LibraryView+Data.swift ================================================ // // LibraryView+Data.swift // RegEx+ // // Created by Lex on 2020/5/3. // Copyright © 2020 Lex.sh. All rights reserved. // import CoreData extension LibraryView { func deleteRegEx(indexSet: IndexSet) { let source = indexSet.first! let regEx = regExItems[source] managedObjectContext.delete(regEx) save() } func addRegEx(withSample: Bool) { let regEx = RegEx(context: managedObjectContext) if withSample, let randomItem = sampleData().randomElement() { regEx.name = randomItem.name regEx.raw = randomItem.raw regEx.sample = randomItem.sample regEx.allowCommentsAndWhitespace = randomItem.allowComments regEx.createdAt = Date() } else { regEx.name = NSLocalizedString("Untitled", comment: "Default item name") regEx.raw = "" regEx.createdAt = Date() } save() editMode = .inactive } private func save() { DataManager.shared.saveContext() } private func sampleData() -> [SampleItem] { return [ SampleItem("Dollars", raw: #"(\$[\d]+)\.?(\d{2})?"#), SampleItem("Hex", raw: #"#?([a-f0-9]{6}|[a-f0-9]{3})"#, sample: "#336699\n#F2A\nFF9933"), SampleItem("Allow Comments", raw: #"(\$[\d]+) # Dollars symbol and digits"#, allowComments: true), SampleItem("Roman Numeral", raw: #"M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})"#), SampleItem("Email", raw: #"([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})"#, sample: "ive@apple.com"), SampleItem("HTML
  • tag", raw: #"
  • (.*?)
  • "#, sample: "
  • iPhone
  • \n
  • iPad
  • "), ] } } private struct SampleItem { let name: String let raw: String let sample: String let allowComments: Bool init(_ name: String, raw: String, sample: String = "", allowComments: Bool = false) { self.name = name self.raw = raw self.sample = sample self.allowComments = allowComments } } ================================================ FILE: RegEx+/Library/LibraryView.swift ================================================ // // LibraryView.swift // RegEx+ // // Created by Lex on 2020/4/21. // Copyright © 2020 Lex.sh. All rights reserved. // import SwiftUI import CoreData struct LibraryView: View, Equatable { static func == (lhs: LibraryView, rhs: LibraryView) -> Bool { lhs.regExItems.map(\.objectID).hashValue == rhs.regExItems.map(\.objectID).hashValue } @Environment(\.managedObjectContext) var managedObjectContext @FetchRequest(fetchRequest: RegEx.fetchAllRegEx()) var regExItems: FetchedResults @State private var searchTerm = "" @State var editMode = EditMode.inactive private var filteredItems: [RegEx] { if searchTerm.isEmpty { return Array(regExItems) } return regExItems.filter { item in item.name.localizedCaseInsensitiveContains(searchTerm) || item.raw.localizedCaseInsensitiveContains(searchTerm) } } var body: some View { VStack(alignment: .leading) { SearchView(text: $searchTerm) .padding(.horizontal) if regExItems.isEmpty { VStack(alignment: .center) { Text("Your RegEx+ library is empty") .font(.subheadline) .foregroundStyle(.secondary) Button { addRegEx(withSample: true) } label: { Text("Create a sample") } .buttonStyle(.bordered) } .frame(maxWidth: .greatestFiniteMagnitude, maxHeight: .greatestFiniteMagnitude) } else { List { ForEach(filteredItems, id: \.objectID) { LibraryItemView(regEx: $0).equatable() } .onDelete(perform: deleteRegEx) } .currentDeviceListStyle() .environment(\.editMode, $editMode) } } .navigationTitle("RegEx+") .setNavigationItems(libraryView: self) } var editButton: some View { Button(action: { editMode = editMode.isEditing ? .inactive : .active }, label: { Text(editMode.isEditing ? "Done" : "Edit") }) } var aboutButton: some View { NavigationLink(destination: AboutView()) { Image(systemName: "info.circle") .imageScale(.large) } } var addButton: some View { Button { addRegEx(withSample: false) } label: { Image(systemName: "plus.circle.fill") .imageScale(.large) } } } private extension View { @ViewBuilder func currentDeviceListStyle() -> some View { #if targetEnvironment(macCatalyst) self.listStyle(.plain) .padding(.horizontal) #else if #available(iOS 14.0, *) { self.listStyle(.insetGrouped) } else { self.listStyle(.grouped) } #endif } } private extension View { @ViewBuilder func setNavigationItems(libraryView: LibraryView) -> some View { self.toolbar { ToolbarItem(placement: .topBarLeading) { libraryView.editButton } #if targetEnvironment(macCatalyst) ToolbarItem(placement: .topBarTrailing) { HStack { libraryView.aboutButton libraryView.addButton } } #else ToolbarItem(placement: .topBarTrailing) { libraryView.aboutButton } ToolbarItem(placement: .topBarTrailing) { libraryView.addButton .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 0)) } #endif } } } #if DEBUG struct LibraryView_Previews: PreviewProvider { static var previews: some View { NavigationView { LibraryView() .environment(\.managedObjectContext, DataManager.shared.persistentContainer.viewContext) } } } #endif ================================================ FILE: RegEx+/Localizable.xcstrings ================================================ { "sourceLanguage" : "en", "strings" : { "%@" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "%@" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "%@" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "%@" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "%@" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "%@" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "%@" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "%@" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "%@" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "%@" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "%@" } } } }, "%lld matches" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "%lld Treffer" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "%lld coincidencias" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "%lld correspondances" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "%lld corrispondenze" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "%lld マッチ" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "%lld개 일치" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "%lld resultaten" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "%lld dopasowań" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "%lld 次匹配" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "%lld 次匹配" } } } }, "1 match" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "1 Treffer" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "1 coincidencia" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "1 correspondance" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "1 corrispondenza" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "1マッチ" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "1개 일치" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "1 resultaat" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "1 dopasowanie" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "一次匹配" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "一次匹配" } } } }, "Allow Comments and Whitespace" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Kommentare und Leerzeichen erlauben" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Permitir comentarios y espacios en blanco" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Autoriser les commentaires et les espaces" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Consenti commenti e spazi" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "コメントと空白を許可" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "주석 및 공백 허용" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Reacties en witruimte toestaan" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Zezwalaj na komentarze i białe znaki" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "允许注释和空格" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "允許註釋和空格" } } } }, "Anchors Match Lines" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Anker passen zu Zeilen" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Anclas coinciden con líneas" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Les ancres correspondent aux lignes" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Ancore corrispondono alle righe" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "アンカーで行をマッチ" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "앵커로 라인 일치" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Ankers komen overeen met regels" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Kotwice dopasowują linie" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "锚点匹配行" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "錨點匹配行" } } } }, "Case Insensitive" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Groß-/Kleinschreibung ignorieren" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Insensible a mayúsculas/minúsculas" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Insensible à la casse" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Ignora maiuscole/minuscole" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "大文字小文字を区別しない" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "대소문자 구분 안 함" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Hoofdletterongevoelig" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Ignoruj wielkość liter" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "不区分大小写" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "不區分大小寫" } } } }, "Cheat Sheet" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Spickzettel" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Hoja de Referencia" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Antisèche" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Guida di Riferimento" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "チートシート" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "치트 시트" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Spiekbriefje" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Ściąga" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "小抄" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "小抄" } } } }, "Copies the substitution result to clipboard" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Ersetzungsergebnis in Zwischenablage kopieren" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Copia el resultado de sustitución al portapapeles" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Copie le résultat de substitution dans le presse-papiers" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Copia il risultato della sostituzione negli appunti" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "置換結果をクリップボードにコピー" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "치환 결과를 클립보드에 복사" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Kopieert het vervangingsresultaat naar het klembord" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Kopiuje wynik podstawienia do schowka" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "将替换结果复制到剪贴板" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "將替換結果複製到剪貼板" } } } }, "Copy substitution result" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Ersetzungsergebnis kopieren" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Copiar resultado de sustitución" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Copier le résultat de substitution" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Copia risultato di sostituzione" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "置換結果をコピー" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "치환 결과 복사" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Vervangingsresultaat kopiëren" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Kopiuj wynik podstawienia" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "复制替换结果" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "複製替換結果" } } } }, "Create a sample" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Beispiel erstellen" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Crear un ejemplo" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Créer un exemple" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Crea un esempio" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "サンプルを作成" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "샘플 만들기" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Voorbeeld maken" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Utwórz przykład" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "新建样例" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "新建樣例" } } } }, "Done" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Fertig" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Hecho" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Terminé" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Fatto" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "完了" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "완료" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Gereed" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Gotowe" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "完成" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "完成" } } } }, "Dot Matches Line Separators" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Punkt entspricht Zeilentrenner" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Punto coincide con separadores de línea" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Le point correspond aux séparateurs de ligne" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Il punto corrisponde ai separatori di riga" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "ドットで改行文字をマッチ" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "점이 줄 구분 기호와 일치" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Punt komt overeen met regelscheidingstekens" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Kropka dopasowuje separatory linii" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "点符号匹配行分隔符" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "點符號匹配行分隔符" } } } }, "Edit" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Bearbeiten" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Editar" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Modifier" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Modifica" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "編集" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "편집" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Bewerk" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Edytuj" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "编辑" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "編輯" } } } }, "Enter a regular expression to see the flow diagram" : { }, "Flow Diagram" : { }, "Ignore Metacharacters" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Metazeichen ignorieren" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Ignorar metacaracteres" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Ignorer les métacaractères" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Ignora metacaratteri" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "メタ文字を無視" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "메타 문자 무시" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Metatekens negeren" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Ignoruj metaznaki" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "忽略元字符" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "忽略元字符" } } } }, "Insert %@ into text" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "%@ in Text einfügen" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Insertar %@ en texto" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Insérer %@ dans le texte" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Inserisci %@ nel testo" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "%@をテキストに挿入" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "%@를 텍스트에 삽입" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Voeg %@ in tekst in" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Wstaw %@ do tekstu" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "将%@插入文本" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "將%@插入文字" } } } }, "Loading..." : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Laden..." } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Cargando..." } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Chargement..." } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Caricamento..." } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "読み込み中..." } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "로드 중..." } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Laden..." } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Ładowanie..." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "加载中..." } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "載入中..." } } } }, "Metacharacters" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Metazeichen" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Metacaracteres" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Métacaractères" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Metacaratteri" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "メタ文字" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "메타 문자" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Metatekens" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Metaznaki" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "元字符" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "元字符" } } } }, "Name" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Name" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Nombre" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Nom" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Nome" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "名前" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "이름" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Naam" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Nazwa" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "名字" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "名字" } } } }, "Operators" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Operatoren" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Operadores" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Opérateurs" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Operatori" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "演算子" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "연산자" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Operatoren" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Operatory" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "操作符" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "操作符" } } } }, "Options" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Optionen" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Opciones" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Options" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Opzioni" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "オプション" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "옵션" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Opties" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Opcje" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "选项" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "選項" } } } }, "OR" : { }, "Price: $$$1\\.$2\\n" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Preis: $$$1\\.$2\\n" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Precio: $$$1\\.$2\\n" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Prix : $$$1\\.$2\\n" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Prezzo: $$$1\\.$2\\n" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "価格: $$$1\\.$2\\n" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "가격: $$$1\\.$2\\n" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Prijs: $$$1\\.$2\\n" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Cena: $$$1\\.$2\\n" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "价格: $$$1\\.$2\\n" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "價格: $$$1\\.$2\\n" } } } }, "RegEx+" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "RegEx+" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "RegEx+" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "RegEx+" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "RegEx+" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "RegEx+" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "RegEx+" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "RegEx+" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "RegEx+" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "RegEx+" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "RegEx+" } } } }, "Regular Expression" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Regulärer Ausdruck" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Expresión Regular" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Expression Régulière" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Espressione Regolare" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "正規表現" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "정규 표현식" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Reguliere expressie" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Wyrażenie regularne" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "正则表达式" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "正則表達式" } } } }, "Sample Text" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Beispieltext" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Texto de Ejemplo" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Texte d'Exemple" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Testo di Esempio" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "サンプルテキスト" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "샘플 텍스트" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Voorbeeldtekst" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Przykładowy tekst" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "示例文字" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "示例文字" } } } }, "Search..." : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Suchen..." } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Buscar..." } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Rechercher..." } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Cerca..." } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "検索..." } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "검색..." } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Zoeken..." } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Szukaj..." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "搜索..." } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "搜索..." } } } }, "Share" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Teilen" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Compartir" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Partager" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Condividi" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "共有" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "공유" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Deel" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Udostępnij" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "分享" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "分享" } } } }, "Share this regular expression" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Diesen regulären Ausdruck teilen" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Compartir esta expresión regular" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Partager cette expression régulière" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Condividi questa espressione regolare" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "この正規表現を共有" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "이 정규 표현식 공유" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Deel deze reguliere expressie" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Udostępnij to wyrażenie regularne" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "分享这个正则表达式" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "分享這個正則表達式" } } } }, "Substitution Result" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Ersetzungsergebnis" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Resultado de Sustitución" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Résultat de Substitution" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Risultato di Sostituzione" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "置換結果" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "치환 결과" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Vervangingsresultaat" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Wynik podstawienia" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "替换结果" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "替換結果" } } } }, "Substitution Template" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Ersetzungsvorlage" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Plantilla de Sustitución" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Modèle de Substitution" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Modello di Sostituzione" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "置換テンプレート" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "치환 템플릿" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Vervangingssjabloon" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Szablon podstawienia" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "替换模板" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "替換模板" } } } }, "Untitled" : { "comment" : "Default item name", "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Ohne Titel" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Sin título" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Sans titre" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Senza titolo" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "無題" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "제목 없음" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Naamloos" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Bez tytułu" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "未命名" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "未命名" } } } }, "Use Unicode Word Boundaries" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Unicode-Wortgrenzen verwenden" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Usar límites de palabra Unicode" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Utiliser les limites de mots Unicode" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Usa limiti di parola Unicode" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "Unicode単語境界を使用" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "유니코드 단어 경계 사용" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Gebruik Unicode-woordgrenzen" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Użyj granic słów Unicode" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "使用 Unicode 字符边界" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "使用 Unicode 字符邊界" } } } }, "Use Unix Line Separators" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Unix-Zeilentrenner verwenden" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Usar separadores de línea Unix" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Utiliser les séparateurs de ligne Unix" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Usa separatori di riga Unix" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "Unix改行文字を使用" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "Unix 줄 구분 기호 사용" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Gebruik Unix-regelscheidingstekens" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Użyj separatorów linii Unix" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "使用 Unix 换行符" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "使用 Unix 換行符" } } } }, "View regular expression reference guide" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Regulärer Ausdruck-Referenzhandbuch anzeigen" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Ver guía de referencia de expresiones regulares" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Voir le guide de référence des expressions régulières" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "Visualizza la guida di riferimento delle espressioni regolari" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "正規表現リファレンスガイドを表示" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "정규 표현식 참조 가이드 보기" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Bekijk de referentiegids voor reguliere expressies" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Zobacz przewodnik po wyrażeniach regularnych" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "查看正则表达式参考指南" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "查看正則表達式參考指南" } } } }, "Your RegEx+ library is empty" : { "localizations" : { "de" : { "stringUnit" : { "state" : "translated", "value" : "Ihre RegEx+ Bibliothek ist leer" } }, "es" : { "stringUnit" : { "state" : "translated", "value" : "Tu biblioteca RegEx+ está vacía" } }, "fr" : { "stringUnit" : { "state" : "translated", "value" : "Votre bibliothèque RegEx+ est vide" } }, "it" : { "stringUnit" : { "state" : "translated", "value" : "La tua libreria RegEx+ è vuota" } }, "ja" : { "stringUnit" : { "state" : "translated", "value" : "RegEx+ライブラリが空です" } }, "ko" : { "stringUnit" : { "state" : "translated", "value" : "RegEx+ 라이브러리가 비어 있습니다" } }, "nl" : { "stringUnit" : { "state" : "translated", "value" : "Je RegEx+ bibliotheek is leeg" } }, "pl" : { "stringUnit" : { "state" : "translated", "value" : "Twoja biblioteka RegEx+ jest pusta" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "你的 RegEx+ 仓库是空的" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "你的 RegEx+ 倉庫是空的" } } } } }, "version" : "1.0" } ================================================ FILE: RegEx+/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: RegEx+/RegEx+.entitlements ================================================ aps-environment development com.apple.developer.icloud-container-identifiers iCloud.RegExCatalyst com.apple.developer.icloud-services CloudKit com.apple.security.app-sandbox com.apple.security.network.client ================================================ FILE: RegEx+/RegEx.xcdatamodeld/.xccurrentversion ================================================ _XCCurrentVersionName RegEx.xcdatamodel ================================================ FILE: RegEx+/RegEx.xcdatamodeld/RegEx.xcdatamodel/contents ================================================ ================================================ FILE: RegEx+/SceneDelegate.swift ================================================ // // SceneDelegate.swift // RegEx+ // // Created by Lex on 2020/4/21. // Copyright © 2020 Lex.sh. All rights reserved. // import UIKit import SwiftUI class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). // Create the SwiftUI view that provides the window contents. let viewContext = DataManager.shared.persistentContainer.viewContext let contentView = HomeView() .environment(\.managedObjectContext, viewContext) // Use a UIHostingController as window root view controller. if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = UIHostingController(rootView: contentView) self.window = window window.makeKeyAndVisible() #if targetEnvironment(macCatalyst) if let titlebar = windowScene.titlebar { titlebar.titleVisibility = .visible titlebar.toolbarStyle = .unified } #endif } } func sceneDidDisconnect(_ scene: UIScene) { // Called as the scene is being released by the system. // This occurs shortly after the scene enters the background, or when its session is discarded. // Release any resources associated with this scene that can be re-created the next time the scene connects. // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). } func sceneDidBecomeActive(_ scene: UIScene) { // Called when the scene has moved from an inactive state to an active state. // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. } func sceneWillResignActive(_ scene: UIScene) { // Called when the scene will move from an active state to an inactive state. // This may occur due to temporary interruptions (ex. an incoming phone call). } func sceneWillEnterForeground(_ scene: UIScene) { // Called as the scene transitions from the background to the foreground. // Use this method to undo the changes made on entering the background. } func sceneDidEnterBackground(_ scene: UIScene) { // Called as the scene transitions from the foreground to the background. // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } } ================================================ FILE: RegEx+/Views/ActivityViewController.swift ================================================ // // ActivityViewController.swift // RegEx+ // // Created by Lex on 2020/5/16. // Copyright © 2020 Lex.sh. All rights reserved. // import UIKit import SwiftUI struct ActivityViewController: UIViewControllerRepresentable { var activityItems: [Any] var applicationActivities: [UIActivity]? func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIActivityViewController { let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities) return controller } func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext) {} } ================================================ FILE: RegEx+/Views/RegExSyntaxView.swift ================================================ // // RegExSyntaxView.swift // RegExPro // // Created by Lex on 2020/4/23. // Copyright © 2020 Lex.sh. All rights reserved. // import SwiftUI import Combine import UIKit private struct UITextViewWrapper: UIViewRepresentable { typealias UIViewType = UITextView @Binding var text: String @Binding var calculatedHeight: CGFloat var onDone: (() -> Void)? func makeUIView(context: UIViewRepresentableContext) -> UITextView { let tv = UITextView() tv.delegate = context.coordinator tv.isEditable = true tv.font = UIFont.preferredFont(forTextStyle: .body) tv.isSelectable = true tv.isUserInteractionEnabled = true tv.isScrollEnabled = false tv.backgroundColor = UIColor.clear tv.textContainerInset = .zero tv.textContainer.lineFragmentPadding = 0 if nil != onDone { tv.returnKeyType = .done } tv.textStorage.delegate = syntaxHighlighter tv.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) return tv } func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext) { if uiView.text != self.text { uiView.text = self.text } if uiView.window != nil, !uiView.isFirstResponder { uiView.becomeFirstResponder() } UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight) } fileprivate static func recalculateHeight(view: UIView, result: Binding) { let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude)) if result.wrappedValue != newSize.height { DispatchQueue.main.async { result.wrappedValue = newSize.height // !! must be called asynchronously } } } func makeCoordinator() -> Coordinator { return Coordinator(text: $text, height: $calculatedHeight, onDone: onDone) } final class Coordinator: NSObject, UITextViewDelegate { var text: Binding var calculatedHeight: Binding var onDone: (() -> Void)? init(text: Binding, height: Binding, onDone: (() -> Void)? = nil) { self.text = text self.calculatedHeight = height self.onDone = onDone } func textViewDidChange(_ uiView: UITextView) { text.wrappedValue = uiView.text UITextViewWrapper.recalculateHeight(view: uiView, result: calculatedHeight) } func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { if let onDone = self.onDone, text == "\n" { textView.resignFirstResponder() onDone() return false } return true } } private let syntaxHighlighter = RegExSyntaxHighlighter() } struct RegExTextView: View { private var placeholder: String private var onCommit: (() -> Void)? @Binding private var text: String private var internalText: Binding { Binding(get: { self.text }) { self.text = $0 self.showingPlaceholder = $0.isEmpty } } @State private var dynamicHeight: CGFloat = 100 @State private var showingPlaceholder = false init (_ placeholder: String = "", text: Binding, onCommit: (() -> Void)? = nil) { self.placeholder = placeholder self.onCommit = onCommit self._text = text self._showingPlaceholder = State(initialValue: self.text.isEmpty) } var body: some View { UITextViewWrapper(text: self.internalText, calculatedHeight: $dynamicHeight, onDone: onCommit) .frame(minHeight: dynamicHeight, maxHeight: dynamicHeight) .background(placeholderView, alignment: .topLeading) } var placeholderView: some View { Group { if showingPlaceholder { Text(placeholder).foregroundColor(.gray) .padding(.leading, 4) .padding(.top, 8) } } } } #if DEBUG struct MultilineTextField_Previews: PreviewProvider { static var test: String = "" static var testBinding = Binding(get: { test }, set: { test = $0 }) static var previews: some View { VStack(alignment: .leading) { Text("Description:") RegExTextView("Enter some text here", text: testBinding, onCommit: { print("Final text: \(test)") }) .overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.black)) Text("Something static here...") Spacer() } .padding() } } #endif class RegExSyntaxHighlighter: NSObject, NSTextStorageDelegate { var fontSize: CGFloat = 16 func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorage.EditActions, range editedRange: NSRange, changeInLength delta: Int) { textStorage.addAttributes([ .font: UIFont.systemFont(ofSize: fontSize), .foregroundColor: UIColor.black ], range: NSRange(location: 0, length: textStorage.length)) textStorage.string.ranges(of: #"\\[$$\w]"#, options: .regularExpression).forEach { range in textStorage.addAttributes([ .foregroundColor: UIColor.red ], range: textStorage.string.nsRange(from: range)) } textStorage.string.ranges(of: #"[\(\)]"#, options: .regularExpression).forEach { range in textStorage.addAttributes([ .foregroundColor: UIColor(red: 0, green: 0.5, blue: 0.2, alpha: 1) ], range: textStorage.string.nsRange(from: range)) } textStorage.string.ranges(of: #"(?:\{)[\d,]+(?:\})"#, options: .regularExpression).forEach { range in textStorage.addAttributes([ .font: UIFont.boldSystemFont(ofSize: fontSize), .foregroundColor: UIColor(red: 0, green: 0.3, blue: 0, alpha: 1) ], range: textStorage.string.nsRange(from: range)) } textStorage.string.ranges(of: #"[\?\*\.]"#, options: .regularExpression).forEach { range in textStorage.addAttributes([ .font: UIFont.boldSystemFont(ofSize: fontSize), .foregroundColor: UIColor(red: 0, green: 0.3, blue: 0, alpha: 1) ], range: textStorage.string.nsRange(from: range)) } textStorage.string.ranges(of: #"[\^\[\$\]]"#, options: .regularExpression).forEach { range in textStorage.addAttributes([ .foregroundColor: UIColor(red: 0, green: 0, blue: 0.8, alpha: 1) ], range: textStorage.string.nsRange(from: range)) } } } ================================================ FILE: RegEx+/Views/RegExTextView/MatchesTextView.swift ================================================ // // MatchesTextView.swift // RegEx+ // // Created by Lex on 2020/5/2. // Copyright © 2020 Lex.sh. All rights reserved. // import UIKit import SwiftUI private struct MatchesTextViewWrapper: UIViewRepresentable { typealias UIViewType = UITextView @Binding var text: String @Binding var calculatedHeight: CGFloat var matches: [NSTextCheckingResult] var onDone: (() -> Void)? func makeUIView(context: UIViewRepresentableContext) -> UITextView { let tv = UITextView() tv.delegate = context.coordinator tv.isEditable = true tv.font = UIFont.preferredFont(forTextStyle: .body) tv.isSelectable = true tv.isUserInteractionEnabled = true tv.isScrollEnabled = false tv.backgroundColor = UIColor.clear tv.textContainerInset = .zero tv.textContainer.lineFragmentPadding = 0 if nil != onDone { tv.returnKeyType = .done } tv.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) return tv } func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext) { if uiView.attributedText.string != text { uiView.attributedText = NSAttributedString(string: text) } if !text.isEmpty { uiView.textStorage.setAttributes([ .font: UIFont.preferredFont(forTextStyle: .body), .foregroundColor: UIColor.label ], range: NSRange(location: 0, length: uiView.text.count)) matches.forEach { result in for index in 0.. uiView.attributedText.length { return } uiView.textStorage.setAttributes([ .font: UIFont.preferredFont(forTextStyle: .body), .foregroundColor: UIColor.systemBlue ], range: range) } } } MatchesTextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight) } fileprivate static func recalculateHeight(view: UIView, result: Binding) { let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude)) if result.wrappedValue != newSize.height { DispatchQueue.main.async { result.wrappedValue = newSize.height // !! must be called asynchronously } } } func makeCoordinator() -> Coordinator { return Coordinator(text: $text, height: $calculatedHeight, onDone: onDone) } final class Coordinator: NSObject, UITextViewDelegate { var text: Binding var calculatedHeight: Binding var onDone: (() -> Void)? init(text: Binding, height: Binding, onDone: (() -> Void)? = nil) { self.text = text self.calculatedHeight = height self.onDone = onDone } func textViewDidChange(_ uiView: UITextView) { text.wrappedValue = uiView.text MatchesTextViewWrapper.recalculateHeight(view: uiView, result: calculatedHeight) } func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { if let onDone, text == "\n" { textView.resignFirstResponder() onDone() return false } return true } } } struct MatchesTextView: View, Equatable { static func == (lhs: MatchesTextView, rhs: MatchesTextView) -> Bool { lhs.text == rhs.text && lhs.matches == rhs.matches } private var placeholder: String private var onCommit: (() -> Void)? @Binding private var text: String private var internalText: Binding { Binding(get: { self.text }) { self.text = $0 self.showingPlaceholder = $0.isEmpty } } @Binding private var matches: [NSTextCheckingResult] @State private var dynamicHeight: CGFloat = 100 @State private var showingPlaceholder = false init (_ placeholder: String = "", text: Binding, matches: Binding<[NSTextCheckingResult]>, onCommit: (() -> Void)? = nil) { self.placeholder = placeholder self.onCommit = onCommit _matches = matches _text = text _showingPlaceholder = State(initialValue: self.text.isEmpty) } var body: some View { MatchesTextViewWrapper( text: internalText, calculatedHeight: $dynamicHeight, matches: matches, onDone: onCommit ) .frame(minHeight: dynamicHeight, maxHeight: dynamicHeight) .background(placeholderView, alignment: .topLeading) } var placeholderView: some View { Group { if showingPlaceholder { VStack { Text(placeholder) .foregroundColor(.secondary) } } } } } #if DEBUG struct MatchesTextView_Previews: PreviewProvider { static var test = "^(\\d+)\\.(\\d{2}) (\\d+)\\.(\\d{2}) (\\d+)\\.(\\d{2}) (\\d+)\\.(\\d{2})" static var testBinding = Binding(get: { test }, set: { test = $0 }) static var matches = [NSTextCheckingResult]() static var matchesBinding = Binding<[NSTextCheckingResult]>(get: { matches }, set: { matches = $0 }) static var previews: some View { VStack(alignment: .leading) { Text("Description:") MatchesTextView("Enter some text here", text: testBinding, matches: matchesBinding) { print("Final text: \(test)") } .overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.black)) Spacer() } .padding() } } #endif ================================================ FILE: RegEx+/Views/RegExTextView/RegExSyntaxHighlighter.swift ================================================ // // RegExSyntaxHighlighter.swift // RegEx+ // // Created by Lex on 2020/5/2. // Copyright © 2020 Lex.sh. All rights reserved. // import Foundation import UIKit import _RegexParser enum RegExTextHighlightingMode: Equatable { case plainText case regularExpression(NSRegularExpression.Options) var signature: Int { switch self { case .plainText: return 0 case .regularExpression(let options): return Int(truncatingIfNeeded: options.rawValue) ^ 0x51A9 } } } fileprivate extension NSMutableAttributedString { func applyAttributes(_ attributes: [NSAttributedString.Key: Any], to range: Range) { let nsRange = NSRange(range, in: string) addAttributes(attributes, range: nsRange) } } final class RegExSyntaxHighlighter: NSObject, NSTextStorageDelegate { weak var textStorage: NSTextStorage? var highlightingMode: RegExTextHighlightingMode = .regularExpression([]) private var lastHighlightSignature: Int? func highlightRegularExpression(force: Bool = false) { guard let textStorage else { return } let signature = highlightSignature(for: textStorage.string) if !force, signature == lastHighlightSignature { return } let string = textStorage.string let attributed = NSMutableAttributedString(attributedString: textStorage) let fullRange = NSRange(location: 0, length: attributed.length) attributed.removeAttribute(.foregroundColor, range: fullRange) attributed.removeAttribute(.underlineStyle, range: fullRange) attributed.removeAttribute(.underlineColor, range: fullRange) attributed.addAttribute(.foregroundColor, value: UIColor.label, range: fullRange) semanticTokens(for: string).sorted(by: tokenSort).forEach { token in attributed.applyAttributes(token.attributes, to: token.range) } textStorage.setAttributedString(attributed) lastHighlightSignature = signature } func textStorage( _ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorage.EditActions, range editedRange: NSRange, changeInLength delta: Int ) { self.textStorage = textStorage highlightRegularExpression() } } private extension RegExSyntaxHighlighter { struct HighlightToken { let range: Range let attributes: [NSAttributedString.Key: Any] let priority: Int } struct TokenStyle { let color: UIColor let priority: Int } enum HighlightColor { static let quantifier = UIColor.systemGreen static let quantifierRange = UIColor.systemPurple static let structural = UIColor.systemPink static let characterClass = UIColor.systemTeal static let escape = UIColor.systemOrange static let comment = UIColor.systemGray static let directive = UIColor.systemPurple static let error = UIColor.systemRed } enum HighlightStyle { static let comment = TokenStyle(color: HighlightColor.comment, priority: 10) static let structural = TokenStyle(color: HighlightColor.structural, priority: 20) static let delimiter = TokenStyle(color: HighlightColor.structural, priority: 30) static let characterClass = TokenStyle(color: HighlightColor.characterClass, priority: 30) static let quantifier = TokenStyle(color: HighlightColor.quantifier, priority: 40) static let quantifierRange = TokenStyle(color: HighlightColor.quantifierRange, priority: 40) static let escape = TokenStyle(color: HighlightColor.escape, priority: 40) static let directive = TokenStyle(color: HighlightColor.directive, priority: 40) } func tokenSort(lhs: HighlightToken, rhs: HighlightToken) -> Bool { if lhs.priority != rhs.priority { return lhs.priority < rhs.priority } if lhs.range.lowerBound != rhs.range.lowerBound { return lhs.range.lowerBound < rhs.range.lowerBound } return lhs.range.upperBound < rhs.range.upperBound } func highlightSignature(for text: String) -> Int { var hasher = Hasher() hasher.combine(text) hasher.combine(highlightingMode.signature) return hasher.finalize() } func semanticTokens(for source: String) -> [HighlightToken] { guard !source.isEmpty else { return [] } switch highlightingMode { case .plainText: return [] case .regularExpression(let options): guard !options.contains(.ignoreMetacharacters) else { return [] } let ast = parseWithRecovery(source, syntaxOptions(for: options)) var tokens = [HighlightToken]() if let globalOptions = ast.globalOptions { globalOptions.options.forEach { option in addColorToken(for: option.location, in: source, style: HighlightStyle.directive, to: &tokens) } } collectTokens(from: ast.root, in: source, into: &tokens) ast.diags.diags.forEach { diagnostic in addUnderlineToken(for: diagnostic.location, in: source, color: HighlightColor.error, priority: 100, to: &tokens) } return tokens } } func syntaxOptions(for options: NSRegularExpression.Options) -> SyntaxOptions { var syntax: SyntaxOptions = .traditional if options.contains(.allowCommentsAndWhitespace) { syntax.formUnion(.extendedSyntax) } return syntax } // swiftlint:disable:next cyclomatic_complexity func collectTokens(from node: AST.Node, in source: String, into tokens: inout [HighlightToken]) { switch node { case .alternation(let alternation): collectTokens(from: alternation, in: source, into: &tokens) case .concatenation(let concatenation): concatenation.children.forEach { child in collectTokens(from: child, in: source, into: &tokens) } case .group(let group): collectTokens(from: group, in: source, into: &tokens) case .conditional(let conditional): collectTokens(from: conditional, in: source, into: &tokens) case .quantification(let quantification): collectTokens(from: quantification, in: source, into: &tokens) case .quote(let quote): addColorToken(for: quote.location, in: source, style: HighlightStyle.escape, to: &tokens) case .trivia(let trivia): addColorToken(for: trivia.location, in: source, style: HighlightStyle.comment, to: &tokens) case .interpolation(let interpolation): addColorToken(for: interpolation.location, in: source, style: HighlightStyle.directive, to: &tokens) case .atom(let atom): collectTokens(from: atom, in: source, into: &tokens) case .customCharacterClass(let characterClass): collectTokens(from: characterClass, in: source, into: &tokens) case .absentFunction(let absentFunction): addColorToken(for: absentFunction.start, in: source, style: HighlightStyle.delimiter, to: &tokens) collectTokens(from: absentFunction, in: source, into: &tokens) case .empty: break } } func collectTokens(from alternation: AST.Alternation, in source: String, into tokens: inout [HighlightToken]) { alternation.pipes.forEach { pipe in addColorToken(for: pipe, in: source, style: HighlightStyle.structural, to: &tokens) } alternation.children.forEach { child in collectTokens(from: child, in: source, into: &tokens) } } func collectTokens(from group: AST.Group, in source: String, into tokens: inout [HighlightToken]) { addDelimitedToken(parent: group.location, child: group.child.location, in: source, style: HighlightStyle.delimiter, to: &tokens) collectTokens(from: group.child, in: source, into: &tokens) } func collectTokens(from conditional: AST.Conditional, in source: String, into tokens: inout [HighlightToken]) { addColorToken(for: conditional.location.start ..< conditional.condition.location.end, in: source, style: HighlightStyle.delimiter, to: &tokens) if let pipe = conditional.pipe { addColorToken(for: pipe, in: source, style: HighlightStyle.structural, to: &tokens) } addColorToken(for: conditional.falseBranch.location.end ..< conditional.location.end, in: source, style: HighlightStyle.delimiter, to: &tokens) collectTokens(from: conditional.trueBranch, in: source, into: &tokens) collectTokens(from: conditional.falseBranch, in: source, into: &tokens) } func collectTokens(from quantification: AST.Quantification, in source: String, into tokens: inout [HighlightToken]) { collectTokens(from: quantification.child, in: source, into: &tokens) addColorToken(for: quantification.amount.location, in: source, style: style(for: quantification.amount.value), to: &tokens) addColorToken(for: quantification.kind.location, in: source, style: HighlightStyle.quantifier, to: &tokens) } func collectTokens(from atom: AST.Atom, in source: String, into tokens: inout [HighlightToken]) { switch atom.kind { case .dot: addColorToken(for: atom.location, in: source, style: HighlightStyle.quantifier, to: &tokens) case .caretAnchor, .dollarAnchor: addColorToken(for: atom.location, in: source, style: HighlightStyle.characterClass, to: &tokens) case .char: if let range = sourceRange(for: atom.location, in: source), source[range].hasPrefix("\\") { tokens.append( HighlightToken( range: range, attributes: [.foregroundColor: HighlightStyle.escape.color], priority: HighlightStyle.escape.priority ) ) } case .scalar, .scalarSequence, .property, .escaped, .keyboardControl, .keyboardMeta, .keyboardMetaControl, .namedCharacter, .backreference, .subpattern: addColorToken(for: atom.location, in: source, style: HighlightStyle.escape, to: &tokens) case .callout, .backtrackingDirective, .changeMatchingOptions: addColorToken(for: atom.location, in: source, style: HighlightStyle.directive, to: &tokens) case .invalid: break } } func collectTokens(from characterClass: AST.CustomCharacterClass, in source: String, into tokens: inout [HighlightToken]) { addColorToken(for: characterClass.start.location, in: source, style: HighlightStyle.characterClass, to: &tokens) if let closingRange = sourceRange(for: lastChildEnd(in: characterClass.members, defaultingTo: characterClass.start.location.end) ..< characterClass.location.end, in: source) { tokens.append( HighlightToken( range: closingRange, attributes: [.foregroundColor: HighlightStyle.characterClass.color], priority: HighlightStyle.characterClass.priority ) ) } characterClass.members.forEach { member in collectTokens(from: member, in: source, into: &tokens) } } func collectTokens(from absentFunction: AST.AbsentFunction, in source: String, into tokens: inout [HighlightToken]) { let closingStart: String.Index switch absentFunction.kind { case .repeater(let node), .stopper(let node): collectTokens(from: node, in: source, into: &tokens) closingStart = node.location.end case .expression(let absentee, let pipe, let expression): collectTokens(from: absentee, in: source, into: &tokens) addColorToken(for: pipe, in: source, style: HighlightStyle.structural, to: &tokens) collectTokens(from: expression, in: source, into: &tokens) closingStart = expression.location.end case .clearer: closingStart = absentFunction.start.end } addColorToken(for: closingStart ..< absentFunction.location.end, in: source, style: HighlightStyle.delimiter, to: &tokens) } func collectTokens(from member: AST.CustomCharacterClass.Member, in source: String, into tokens: inout [HighlightToken]) { switch member { case .custom(let characterClass): collectTokens(from: characterClass, in: source, into: &tokens) case .range(let range): collectTokens(from: range.lhs, in: source, into: &tokens) collectTokens(from: range.rhs, in: source, into: &tokens) addColorToken(for: range.dashLoc, in: source, style: HighlightStyle.characterClass, to: &tokens) range.trivia.forEach { trivia in addColorToken(for: trivia.location, in: source, style: HighlightStyle.comment, to: &tokens) } case .atom(let atom): collectTokens(from: atom, in: source, into: &tokens) case .quote(let quote): addColorToken(for: quote.location, in: source, style: HighlightStyle.escape, to: &tokens) case .trivia(let trivia): addColorToken(for: trivia.location, in: source, style: HighlightStyle.comment, to: &tokens) case .setOperation(let lhs, let operation, let rhs): lhs.forEach { collectTokens(from: $0, in: source, into: &tokens) } addColorToken(for: operation.location, in: source, style: HighlightStyle.characterClass, to: &tokens) rhs.forEach { collectTokens(from: $0, in: source, into: &tokens) } } } func addDelimitedToken( parent: SourceLocation, child: SourceLocation, in source: String, style: TokenStyle, to tokens: inout [HighlightToken] ) { addColorToken(for: parent.start ..< child.start, in: source, style: style, to: &tokens) addColorToken(for: child.end ..< parent.end, in: source, style: style, to: &tokens) } func addColorToken( for location: SourceLocation, in source: String, style: TokenStyle, to tokens: inout [HighlightToken] ) { addColorToken(for: location.range, in: source, style: style, to: &tokens) } func addColorToken( for range: Range, in source: String, style: TokenStyle, to tokens: inout [HighlightToken] ) { guard let range = sourceRange(for: range, in: source) else { return } tokens.append( HighlightToken( range: range, attributes: [.foregroundColor: style.color], priority: style.priority ) ) } func addUnderlineToken( for location: SourceLocation, in source: String, color: UIColor, priority: Int, to tokens: inout [HighlightToken] ) { guard let range = sourceRange(for: location, in: source) else { return } tokens.append( HighlightToken( range: range, attributes: [ .underlineStyle: NSUnderlineStyle.single.rawValue, .underlineColor: color ], priority: priority ) ) } func sourceRange(for location: SourceLocation, in source: String) -> Range? { sourceRange(for: location.range, in: source) } func sourceRange(for range: Range, in source: String) -> Range? { guard range.lowerBound >= source.startIndex, range.upperBound <= source.endIndex, range.lowerBound <= range.upperBound else { return nil } return range } func lastChildEnd(in members: [AST.CustomCharacterClass.Member], defaultingTo fallback: String.Index) -> String.Index { members.last?.location.end ?? fallback } func style(for amount: AST.Quantification.Amount) -> TokenStyle { switch amount { case .exactly, .nOrMore, .upToN, .range: return HighlightStyle.quantifierRange case .zeroOrMore, .oneOrMore, .zeroOrOne: return HighlightStyle.quantifier } } } ================================================ FILE: RegEx+/Views/RegExTextView/RegExTextView.swift ================================================ // // RegExSyntaxView.swift // RegEx+ // // Created by Lex on 2020/4/23. // Copyright © 2020 Lex.sh. All rights reserved. // import SwiftUI import Combine import UIKit // Credit to: Asperi https://stackoverflow.com/users/12299030/asperi // https://stackoverflow.com/a/58639072/1209135 struct UITextViewWrapper: UIViewRepresentable { typealias UIViewType = UITextView @Binding var text: String @Binding var calculatedHeight: CGFloat var onDone: (() -> Void)? @Binding var coordinator: Coordinator? var showShortcutBar: Bool var highlightingMode: RegExTextHighlightingMode private let syntaxHighlighter = RegExSyntaxHighlighter() func makeUIView(context: UIViewRepresentableContext) -> UITextView { let tv = UITextView() tv.delegate = context.coordinator syntaxHighlighter.highlightingMode = highlightingMode tv.textStorage.delegate = syntaxHighlighter let font = UIFont.preferredFont(forTextStyle: .body) tv.isEditable = true tv.font = font.withSize(font.pointSize + 2) tv.isSelectable = true tv.autocorrectionType = .no tv.spellCheckingType = .no tv.keyboardType = .emailAddress tv.isUserInteractionEnabled = true tv.isScrollEnabled = false tv.backgroundColor = UIColor.clear tv.textContainerInset = .zero tv.textContainer.lineFragmentPadding = 0 if nil != onDone { tv.returnKeyType = .done } context.coordinator.textView = tv coordinator = context.coordinator #if !targetEnvironment(macCatalyst) // Set up input accessory view for shortcut bar on iOS if showShortcutBar { let accessoryView = ShortcutKeysAccessoryView() accessoryView.textView = tv accessoryView.coordinator = context.coordinator tv.inputAccessoryView = accessoryView } #endif return tv } func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext) { uiView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) uiView.textContainer.lineBreakMode = .byCharWrapping if uiView.text != text { uiView.text = text } syntaxHighlighter.textStorage = uiView.textStorage syntaxHighlighter.highlightingMode = highlightingMode syntaxHighlighter.highlightRegularExpression(force: true) // Calculate height synchronously since we're already on the main thread UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight) } fileprivate static func recalculateHeight(view: UIView, result: Binding) { let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude)) if result.wrappedValue != newSize.height { result.wrappedValue = newSize.height } } func makeCoordinator() -> Coordinator { return Coordinator(text: $text, height: $calculatedHeight, onDone: onDone) } final class Coordinator: NSObject, UITextViewDelegate { var text: Binding var calculatedHeight: Binding var onDone: (() -> Void)? weak var textView: UITextView? init(text: Binding, height: Binding, onDone: (() -> Void)? = nil) { self.text = text self.calculatedHeight = height self.onDone = onDone super.init() } func textViewDidChange(_ uiView: UITextView) { // Only update if text actually changed if text.wrappedValue != uiView.text { text.wrappedValue = uiView.text UITextViewWrapper.recalculateHeight(view: uiView, result: calculatedHeight) } } func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { if let onDone = self.onDone, text == "\n" { textView.resignFirstResponder() onDone() return false } return true } } } struct RegExTextView: View, Equatable { private var placeholder: String private var onCommit: (() -> Void)? private var showShortcutBar: Bool private var highlightingMode: RegExTextHighlightingMode @Binding private var text: String private var internalText: Binding { Binding(get: { self.text }) { self.text = $0 self.showingPlaceholder = $0.isEmpty } } @State private var dynamicHeight: CGFloat = 20 @State private var showingPlaceholder = false @State private var coordinator: UITextViewWrapper.Coordinator? init ( _ placeholder: String = "", text: Binding, onCommit: (() -> Void)? = nil, showShortcutBar: Bool = false, highlightingMode: RegExTextHighlightingMode = .regularExpression([]) ) { self.placeholder = placeholder self.onCommit = onCommit self._text = text self.showShortcutBar = showShortcutBar self.highlightingMode = highlightingMode self._showingPlaceholder = State(initialValue: text.wrappedValue.isEmpty) } var body: some View { UITextViewWrapper( text: internalText, calculatedHeight: $dynamicHeight, onDone: onCommit, coordinator: $coordinator, showShortcutBar: showShortcutBar, highlightingMode: highlightingMode ) .frame(minHeight: dynamicHeight, maxHeight: dynamicHeight) .background(placeholderView, alignment: .topLeading) } var placeholderView: some View { Group { if showingPlaceholder { VStack { Text(placeholder) .foregroundColor(.secondary) } } } } static func == (lhs: RegExTextView, rhs: RegExTextView) -> Bool { lhs.text == rhs.text && lhs.highlightingMode == rhs.highlightingMode } } #if DEBUG struct RegExTextView_Previews: PreviewProvider { static var test = #"^(\d+)\.(\d{2}) (\d+)\.(\d{2}) (\d+)\.(\d{2}) (\d+)\.(\d{2})"# static var testBinding = Binding(get: { test }, set: { test = $0 }) static var previews: some View { Group { VStack(alignment: .leading) { RegExTextView("Enter some text here", text: testBinding, onCommit: { print("Final text: \(test)") }, showShortcutBar: true) .overlay( RoundedRectangle(cornerRadius: 4).stroke(Color.black) ) } VStack(alignment: .leading) { RegExTextView("Enter some text here", text: testBinding, onCommit: { print("Final text: \(test)") }, showShortcutBar: false) .overlay( RoundedRectangle(cornerRadius: 4).stroke(Color.black) ) } } } } #endif ================================================ FILE: RegEx+/Views/RegExTextView/ShortcutKeys.swift ================================================ // // ShortcutKeys.swift // RegEx+ // // Created by Lex on 8/10/25. // Copyright © 2025 Lex.sh. All rights reserved. // import UIKit extension UIFont { func withWeight(_ weight: UIFont.Weight) -> UIFont { let descriptor = fontDescriptor.addingAttributes([ .traits: [UIFontDescriptor.TraitKey.weight: weight] ]) return UIFont(descriptor: descriptor, size: pointSize) } } enum ShortcutKey: String, CaseIterable { case leftArrow = "←" case rightArrow = "→" case asterisk = "*" case question = "?" case openBracket = "[" case closeBracket = "]" case openParen = "(" case closeParen = ")" case backslash = "\\" case dollar = "$" case caret = "^" case dot = "." case plus = "+" case minus = "-" case pipe = "|" case openBrace = "{" case closeBrace = "}" var displayText: String { return self.rawValue } var accessibilityLabel: String { switch self { case .asterisk: return "Asterisk" case .question: return "Question mark" case .openBracket: return "Open bracket" case .closeBracket: return "Close bracket" case .openParen: return "Open parenthesis" case .closeParen: return "Close parenthesis" case .backslash: return "Backslash" case .dollar: return "Dollar sign" case .caret: return "Caret" case .dot: return "Dot" case .plus: return "Plus" case .minus: return "Minus" case .pipe: return "Pipe" case .openBrace: return "Open brace" case .closeBrace: return "Close brace" case .leftArrow: return "Left arrow" case .rightArrow: return "Right arrow" } } var isCharKey: Bool { switch self { case .leftArrow, .rightArrow: false default: true } } } private let keyWidth: CGFloat = 35 private let keyHeight: CGFloat = 40 private let keyHorizontalMargin: CGFloat = 6 private let keyVerticalSpacing: CGFloat = 8 class ShortcutKeysUIKitView: UIView { private let onKeyTapped: (ShortcutKey) -> Void private let scrollView = UIScrollView() private let contentStackView = UIStackView() init(onKeyTapped: @escaping (ShortcutKey) -> Void) { self.onKeyTapped = onKeyTapped super.init(frame: .zero) setupView() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupView() { backgroundColor = .systemBackground let separatorTop = UIView() separatorTop.backgroundColor = .separator separatorTop.translatesAutoresizingMaskIntoConstraints = false let mainStackView = UIStackView() mainStackView.axis = .horizontal mainStackView.alignment = .center mainStackView.spacing = keyHorizontalMargin mainStackView.translatesAutoresizingMaskIntoConstraints = false let arrowKeysStackView = UIStackView() arrowKeysStackView.axis = .horizontal arrowKeysStackView.spacing = keyHorizontalMargin arrowKeysStackView.translatesAutoresizingMaskIntoConstraints = false let arrowKeys = ShortcutKey.allCases.filter { !$0.isCharKey } for key in arrowKeys { let button = createKeyButton(for: key, isBold: true) arrowKeysStackView.addArrangedSubview(button) } let separatorVertical = UIView() separatorVertical.backgroundColor = .separator separatorVertical.translatesAutoresizingMaskIntoConstraints = false scrollView.showsHorizontalScrollIndicator = false scrollView.alwaysBounceVertical = false scrollView.translatesAutoresizingMaskIntoConstraints = false contentStackView.axis = .horizontal contentStackView.spacing = keyHorizontalMargin contentStackView.translatesAutoresizingMaskIntoConstraints = false let charKeys = ShortcutKey.allCases.filter(\.isCharKey) for key in charKeys { let button = createKeyButton(for: key, isBold: false) contentStackView.addArrangedSubview(button) } scrollView.addSubview(contentStackView) mainStackView.addArrangedSubview(arrowKeysStackView) mainStackView.addArrangedSubview(separatorVertical) mainStackView.addArrangedSubview(scrollView) addSubview(separatorTop) addSubview(mainStackView) NSLayoutConstraint.activate([ separatorTop.topAnchor.constraint(equalTo: topAnchor), separatorTop.leadingAnchor.constraint(equalTo: leadingAnchor), separatorTop.trailingAnchor.constraint(equalTo: trailingAnchor), separatorTop.heightAnchor.constraint(equalToConstant: 0.5), mainStackView.topAnchor.constraint(equalTo: separatorTop.bottomAnchor, constant: keyVerticalSpacing), mainStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: keyHorizontalMargin), mainStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -keyHorizontalMargin), mainStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -keyVerticalSpacing), separatorVertical.widthAnchor.constraint(equalToConstant: 1), separatorVertical.heightAnchor.constraint(equalToConstant: keyHeight), contentStackView.topAnchor.constraint(equalTo: scrollView.topAnchor), contentStackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), contentStackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), contentStackView.heightAnchor.constraint(equalTo: scrollView.heightAnchor) ]) } private func createKeyButton(for key: ShortcutKey, isBold: Bool) -> UIButton { let button = UIButton(type: .system) button.setTitle(key.displayText, for: .normal) button.titleLabel?.font = isBold ? UIFont.preferredFont(forTextStyle: .title2).withWeight(.bold) : UIFont.preferredFont(forTextStyle: .title2) button.setTitleColor(.label, for: .normal) button.backgroundColor = .secondarySystemFill button.layer.cornerRadius = 8 button.translatesAutoresizingMaskIntoConstraints = false button.accessibilityLabel = key.accessibilityLabel if key.isCharKey { button.accessibilityHint = "Insert \(key.accessibilityLabel.lowercased()) into text" } else { button.accessibilityHint = key.accessibilityLabel } button.addTarget(self, action: #selector(keyTapped(_:)), for: .touchUpInside) button.tag = ShortcutKey.allCases.firstIndex(of: key) ?? 0 NSLayoutConstraint.activate([ button.widthAnchor.constraint(equalToConstant: keyWidth), button.heightAnchor.constraint(equalToConstant: keyHeight) ]) return button } @objc private func keyTapped(_ sender: UIButton) { let key = ShortcutKey.allCases[sender.tag] onKeyTapped(key) } } class ShortcutKeysAccessoryView: UIView { weak var textView: UITextView? weak var coordinator: UITextViewWrapper.Coordinator? private var shortcutKeysView: ShortcutKeysUIKitView? override init(frame: CGRect) { super.init(frame: frame) setupView() } required init?(coder: NSCoder) { super.init(coder: coder) setupView() } private func setupView() { let shortcutKeysView = ShortcutKeysUIKitView { [weak self] key in self?.handleKeyTapped(key) } shortcutKeysView.translatesAutoresizingMaskIntoConstraints = false addSubview(shortcutKeysView) self.shortcutKeysView = shortcutKeysView translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ shortcutKeysView.topAnchor.constraint(equalTo: topAnchor), shortcutKeysView.leadingAnchor.constraint(equalTo: leadingAnchor), shortcutKeysView.trailingAnchor.constraint(equalTo: trailingAnchor), shortcutKeysView.bottomAnchor.constraint(equalTo: bottomAnchor), heightAnchor.constraint(equalToConstant: keyHeight + keyVerticalSpacing * 2 + 1) ]) } private func handleKeyTapped(_ key: ShortcutKey) { guard let textView = textView else { return } let selectedRange = textView.selectedRange // Handle cursor movement keys if key == .leftArrow { let newPosition = max(0, selectedRange.location - 1) textView.selectedRange = NSRange(location: newPosition, length: 0) return } else if key == .rightArrow { let textLength = textView.text?.count ?? 0 let newPosition = min(textLength, selectedRange.location + 1) textView.selectedRange = NSRange(location: newPosition, length: 0) return } // Handle text insertion keys let currentText = textView.text ?? "" let newText = (currentText as NSString).replacingCharacters(in: selectedRange, with: key.rawValue) textView.text = newText let newCursorPosition = selectedRange.location + key.rawValue.count textView.selectedRange = NSRange(location: newCursorPosition, length: 0) // Update the binding through coordinator if let coordinator = coordinator { coordinator.text.wrappedValue = newText } // Trigger syntax highlighting if available if let textStorage = textView.textStorage.delegate as? RegExSyntaxHighlighter { textStorage.textStorage = textView.textStorage textStorage.highlightRegularExpression() } // Notify delegate of text change to trigger height recalculation if let delegate = textView.delegate { delegate.textViewDidChange?(textView) } } } ================================================ FILE: RegEx+/Views/RegExTextView/String+NSRange.swift ================================================ // // String+NSRange.swift // RegEx+ // // Created by Lex on 2020/5/2. // Copyright © 2020 Lex.sh. All rights reserved. // import Foundation extension String { func nsRange(from range: Range) -> NSRange { let startPos = self.distance(from: self.startIndex, to: range.lowerBound) let endPos = self.distance(from: self.startIndex, to: range.upperBound) return NSRange(location: startPos, length: endPos - startPos) } } extension String { func ranges(of substring: String, options: CompareOptions = [], locale: Locale? = nil) -> [Range] { var ranges: [Range] = [] while let range = range(of: substring, options: options, range: (ranges.last?.upperBound ?? self.startIndex)..) -> SFSafariViewController { return SFSafariViewController(url: url) } func updateUIViewController(_ safariViewController: SFSafariViewController, context: UIViewControllerRepresentableContext) { } } #if DEBUG struct SafariView_Previews: PreviewProvider { static var previews: some View { SafariView(url: URL(string: "https://www.apple.com")!) } } #endif ================================================ FILE: RegEx+/Views/SearchView.swift ================================================ // // SearchView.swift // RegEx+ // // Created by Lex on 2020/10/4. // Copyright © 2020 Lex.sh. All rights reserved. // import SwiftUI struct SearchView: View { @Binding var text: String @State private var isEditing = false private var cornerRadius: CGFloat { #if targetEnvironment(macCatalyst) 5 #else 18 #endif } var body: some View { TextField("Search...", text: $text) .padding(.horizontal, 32) .padding(.vertical, 5) .background(Color(.systemGray6)) .cornerRadius(cornerRadius) .overlay( HStack { Image(systemName: "magnifyingglass") .foregroundColor(.gray) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) .padding(.leading, 10) if isEditing && !text.isEmpty { Button(action: { self.text = "" }) { Image(systemName: "multiply.circle.fill") .foregroundColor(.gray) .padding(.trailing, 8) } .buttonStyle(.borderless) } } ) .disableAutocorrection(true) .onTapGesture { self.isEditing = true } } } struct SearchView_Previews: PreviewProvider { static var previews: some View { VStack { SearchView(text: .constant("")) .preferredColorScheme(.dark) } .padding() .background(Color.white) } } ================================================ FILE: RegEx+/de.lproj/CheatSheet.plist ================================================ metacharacters exp \a des Entspricht einem BELL, \u0007 exp \A des Entspricht dem Anfang der Eingabe. Unterscheidet sich von ^ dadurch, dass \A nicht nach einem Zeilenumbruch innerhalb der Eingabe entspricht. exp \b, outside of a [Set] des Entspricht, wenn die aktuelle Position eine Wortgrenze ist. Grenzen treten bei Übergängen zwischen Wort- (\w) und Nicht-Wort-Zeichen (\W) auf, wobei kombinierende Zeichen ignoriert werden. Für bessere Wortgrenzen siehe useUnicodeWordBoundaries. exp \b, within a [Set] des Entspricht einem BACKSPACE, \u0008. exp \B des Entspricht, wenn die aktuelle Position keine Wortgrenze ist. exp \cX des Entspricht einem Control-X-Zeichen exp \d des Entspricht jedem Zeichen der Unicode-Kategorie Nd (Number, Decimal Digit) exp \D des Entspricht jedem Zeichen, das keine Dezimalziffer ist exp \e des Entspricht einem ESCAPE, \u001B exp \E des Beendet eine \Q ... \E-Quotierung exp \f des Entspricht einem FORM FEED, \u000C exp \G des Entspricht, wenn die aktuelle Position am Ende der vorherigen Übereinstimmung ist exp \n des Entspricht einem LINE FEED, \u000A exp \N{UNICODE CHARACTER NAME} des Entspricht dem benannten Zeichen exp \p{UNICODE PROPERTY NAME} des Entspricht jedem Zeichen mit der angegebenen Unicode-Eigenschaft exp \P{UNICODE PROPERTY NAME} des Entspricht jedem Zeichen ohne die angegebene Unicode-Eigenschaft exp \Q des Quotiert alle folgenden Zeichen bis \E exp \r des Entspricht einem CARRIAGE RETURN, \u000D exp \s des Entspricht einem Leerzeichen. Leerzeichen sind definiert als [\t\n\f\r\p{Z}] exp \S des Entspricht einem Nicht-Leerzeichen exp \t des Entspricht einem HORIZONTAL TABULATION, \u0009 exp \uhhhh des Entspricht dem Zeichen mit dem Hexadezimalwert hhhh exp \Uhhhhhhhh des Entspricht dem Zeichen mit dem Hexadezimalwert hhhhhhhh. Genau acht Hexadezimalziffern müssen angegeben werden, obwohl der größte Unicode-Codepunkt \U0010ffff ist exp \w des Entspricht einem Wortzeichen. Wortzeichen sind [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}] exp \W des Entspricht einem Nicht-Wortzeichen exp \x{hhhh} des Entspricht dem Zeichen mit dem Hexadezimalwert hhhh. Ein bis sechs Hexadezimalziffern können angegeben werden exp \xhh des Entspricht dem Zeichen mit dem zweistelligen Hexadezimalwert hh exp \X des Entspricht einem Graphem-Cluster exp \Z des Entspricht, wenn die aktuelle Position am Ende der Eingabe ist, aber vor dem abschließenden Zeilenterminator, falls einer existiert exp \z des Entspricht, wenn die aktuelle Position am Ende der Eingabe ist exp \n des Rückverweis. Entspricht dem, was die n-te Erfassungsgruppe erfasst hat. n muss eine Zahl ≥ 1 und ≤ der Gesamtzahl der Erfassungsgruppen im Muster sein exp \0ooo des Entspricht einem Oktalzeichen. ooo sind ein bis drei Oktalziffern. 0377 ist das größte erlaubte Oktalzeichen. Die führende Null ist erforderlich; sie unterscheidet Oktalkonstanten von Rückverweisen exp [pattern] des Entspricht einem beliebigen Zeichen aus dem Muster exp . des Entspricht einem beliebigen Zeichen exp ^ des Entspricht am Anfang einer Zeile exp $ des Entspricht am Ende einer Zeile exp \ des Quotiert das folgende Zeichen. Zeichen, die quotiert werden müssen, um als Literale behandelt zu werden, sind * ? + [ ( ) { } ^ $ | \ . / operators exp | des Alternative. A|B entspricht entweder A oder B exp * des Entspricht 0 oder mehrmals. Entspricht so oft wie möglich exp + des Entspricht 1 oder mehrmals. Entspricht so oft wie möglich exp ? des Entspricht null oder einmal. Bevorzugt einmal exp {n} des Entspricht genau n-mal exp {n,} des Entspricht mindestens n-mal. Entspricht so oft wie möglich exp {n,m} des Entspricht zwischen n und m Mal. Entspricht so oft wie möglich, aber nicht mehr als m-mal exp *? des Entspricht 0 oder mehrmals. Entspricht so wenig wie möglich exp +? des Entspricht 1 oder mehrmals. Entspricht so wenig wie möglich exp ?? des Entspricht null oder einmal. Bevorzugt null exp {n}? des Entspricht genau n-mal exp {n,}? des Entspricht mindestens n-mal, aber nicht mehr als für eine Gesamtmuster-Übereinstimmung erforderlich exp {n,m}? des Entspricht zwischen n und m Mal. Entspricht so wenig wie möglich, aber nicht weniger als n-mal exp *+ des Entspricht 0 oder mehrmals. Entspricht so oft wie möglich beim ersten Auftreten, wiederholt nicht mit weniger, auch wenn die Gesamtübereinstimmung fehlschlägt (Possessive Match) exp ++ des Entspricht 1 oder mehrmals. Possessive Match exp ?+ des Entspricht null oder einmal. Possessive Match exp {n}+ des Entspricht genau n-mal exp {n,}+ des Entspricht mindestens n-mal. Possessive Match exp {n,m}+ des Entspricht zwischen n und m Mal. Possessive Match exp (...) des Erfassungsklammern. Der Bereich der Eingabe, der dem geklammerten Teilausdruck entspricht, ist nach der Übereinstimmung verfügbar exp (?:...) des Nicht-erfassende Klammern. Gruppiert das enthaltene Muster, erfasst aber nicht den übereinstimmenden Text. Etwas effizienter als erfassende Klammern exp (?>...) des Atomare Klammern. Die erste Übereinstimmung des geklammerten Teilausdrucks ist die einzige, die versucht wird; wenn sie nicht zu einer Gesamtmuster-Übereinstimmung führt, geht die Suche zu einer Position vor "(?>" zurück exp (?# ... ) des Freiformatkommentar (?# Kommentar ) exp (?= ... ) des Vorausschau-Zusicherung. Wahr, wenn das geklammerte Muster an der aktuellen Eingabeposition entspricht, aber die Eingabeposition nicht voranschreitet exp (?! ... ) des Negative Vorausschau-Zusicherung. Wahr, wenn das geklammerte Muster an der aktuellen Eingabeposition nicht entspricht. Schreitet die Eingabeposition nicht voran exp (?<= ... ) des Rückblick-Zusicherung. Wahr, wenn das geklammerte Muster dem Text vor der aktuellen Eingabeposition entspricht, wobei das letzte Zeichen der Übereinstimmung das Eingabezeichen unmittelbar vor der aktuellen Position ist. Verändert die Eingabeposition nicht. Die Länge möglicher Zeichenketten, die vom Rückblick-Muster erfasst werden, darf nicht unbegrenzt sein (keine * oder + Operatoren) exp (?<! ... ) des Negative Rückblick-Zusicherung. Wahr, wenn das geklammerte Muster nicht dem Text vor der aktuellen Eingabeposition entspricht, wobei das letzte Zeichen der Übereinstimmung das Eingabezeichen unmittelbar vor der aktuellen Position wäre. Verändert die Eingabeposition nicht. Die Länge möglicher Zeichenketten, die vom Rückblick-Muster erfasst werden, darf nicht unbegrenzt sein (keine * oder + Operatoren) exp (?ismwx-ismwx: ... ) des Flag-Einstellungen. Wertet den geklammerten Ausdruck mit den angegebenen aktivierten oder -deaktivierten Flags aus. Die Flags sind in Flag-Optionen definiert exp (?ismwx-ismwx) des Flag-Einstellungen. Ändert die Flag-Einstellungen. Änderungen gelten für den Teil des Musters nach der Einstellung. Zum Beispiel ändert (?i) zu einer groß-/kleinschreibungsunabhängigen Übereinstimmung. Die Flags sind in Flag-Optionen definiert ================================================ FILE: RegEx+/en.lproj/CheatSheet.plist ================================================ metacharacters exp \a des Match a BELL, \u0007 exp \A des Match at the beginning of the input. Differs from ^ in that \A will not match after a new line within the input. exp \b, outside of a [Set] des Match if the current position is a word boundary. Boundaries occur at the transitions between word (\w) and non-word (\W) characters, with combining marks ignored. For better word boundaries, see useUnicodeWordBoundaries. exp \b, within a [Set] des Match a BACKSPACE, \u0008. exp \B des Match if the current position is not a word boundary. exp \cX des Match a control-X character exp \d des Match any character with the Unicode General Category of Nd (Number, Decimal Digit.) exp \D des Match any character that is not a decimal digit. exp \e des Match an ESCAPE, \u001B. exp \E des Terminates a \Q ... \E quoted sequence. exp \f des Match a FORM FEED, \u000C. exp \G des Match if the current position is at the end of the previous match. exp \n des Match a LINE FEED, \u000A. exp \N{UNICODE CHARACTER NAME} des Match the named character. exp \p{UNICODE PROPERTY NAME} des Match any character with the specified Unicode Property. exp \P{UNICODE PROPERTY NAME} des Match any character not having the specified Unicode Property. exp \Q des Quotes all following characters until \E. exp \r des Match a CARRIAGE RETURN, \u000D. exp \s des Match a white space character. White space is defined as [\t\n\f\r\p{Z}]. exp \S des Match a non-white space character. exp \t des Match a HORIZONTAL TABULATION, \u0009. exp \uhhhh des Match the character with the hex value hhhh. exp \Uhhhhhhhh des Match the character with the hex value hhhhhhhh. Exactly eight hex digits must be provided, even though the largest Unicode code point is \U0010ffff. exp \w des Match a word character. Word characters are [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}]. exp \W des Match a non-word character. exp \x{hhhh} des Match the character with hex value hhhh. From one to six hex digits may be supplied. exp \xhh des Match the character with two digit hex value hh. exp \X des Match a Grapheme Cluster. exp \Z des Match if the current position is at the end of input, but before the final line terminator, if one exists. exp \z des Match if the current position is at the end of input. exp \n des Back Reference. Match whatever the nth capturing group matched. n must be a number ≥ 1 and ≤ total number of capture groups in the pattern. exp \0ooo des Match an Octal character. ooo is from one to three octal digits. 0377 is the largest allowed Octal character. The leading zero is required; it distinguishes Octal constants from back references. exp [pattern] des Match any one character from the pattern. exp . des Match any character. exp ^ des Match at the beginning of a line. exp $ des Match at the end of a line. exp \ des Quotes the following character. Characters that must be quoted to be treated as literals are * ? + [ ( ) { } ^ $ | \ . / operators exp | des Alternation. A|B matches either A or B. exp * des Match 0 or more times. Match as many times as possible. exp + des Match 1 or more times. Match as many times as possible. exp ? des Match zero or one times. Prefer one. exp {n} des Match exactly n times. exp {n,} des Match at least n times. Match as many times as possible. exp {n,m} des Match between n and m times. Match as many times as possible, but not more than m. exp *? des Match 0 or more times. Match as few times as possible. exp +? des Match 1 or more times. Match as few times as possible. exp ?? des Match zero or one times. Prefer zero. exp {n}? des Match exactly n times. exp {n,}? des Match at least n times, but no more than required for an overall pattern match. exp {n,m}? des Match between n and m times. Match as few times as possible, but not less than n. exp *+ des Match 0 or more times. Match as many times as possible when first encountered, do not retry with fewer even if overall match fails (Possessive Match). exp ++ des Match 1 or more times. Possessive match. exp ?+ des Match zero or one times. Possessive match. exp {n}+ des Match exactly n times. exp {n,}+ des Match at least n times. Possessive Match. exp {n,m}+ des Match between n and m times. Possessive Match. exp (...) des Capturing parentheses. Range of input that matched the parenthesized subexpression is available after the match. exp (?:...) des Non-capturing parentheses. Groups the included pattern, but does not provide capturing of matching text. Somewhat more efficient than capturing parentheses. exp (?>...) des Atomic-match parentheses. First match of the parenthesized subexpression is the only one tried; if it does not lead to an overall pattern match, back up the search for a match to a position before the "(?>" exp (?# ... ) des Free-format comment (?# comment ). exp (?= ... ) des Look-ahead assertion. True if the parenthesized pattern matches at the current input position, but does not advance the input position. exp (?! ... ) des Negative look-ahead assertion. True if the parenthesized pattern does not match at the current input position. Does not advance the input position. exp (?<= ... ) des Look-behind assertion. True if the parenthesized pattern matches text preceding the current input position, with the last character of the match being the input character just before the current position. Does not alter the input position. The length of possible strings matched by the look-behind pattern must not be unbounded (no * or + operators. exp (?<! ... ) des Negative Look-behind assertion. True if the parenthesized pattern does not match text preceding the current input position, with the last character of the match being the input character just before the current position. Does not alter the input position. The length of possible strings matched by the look-behind pattern must not be unbounded (no * or + operators. exp (?ismwx-ismwx: ... ) des Flag settings. Evaluate the parenthesized expression with the specified flags enabled or -disabled. The flags are defined in Flag Options. exp (?ismwx-ismwx) des Flag settings. Change the flag settings. Changes apply to the portion of the pattern following the setting. For example, (?i) changes to a case insensitive match.The flags are defined in Flag Options. ================================================ FILE: RegEx+/es.lproj/CheatSheet.plist ================================================ metacharacters exp \a des Coincide con un BELL, \u0007 exp \A des Coincide al principio de la entrada. Se diferencia de ^ en que \A no coincidirá después de una nueva línea dentro de la entrada. exp \b, outside of a [Set] des Coincide si la posición actual es un límite de palabra. Los límites ocurren en las transiciones entre caracteres de palabra (\w) y no-palabra (\W), ignorando las marcas de combinación. Para mejores límites de palabra, consulte useUnicodeWordBoundaries. exp \b, within a [Set] des Coincide con un BACKSPACE, \u0008. exp \B des Coincide si la posición actual no es un límite de palabra. exp \cX des Coincide con un carácter control-X exp \d des Coincide con cualquier carácter con la Categoría General Unicode de Nd (Number, Decimal Digit.) exp \D des Coincide con cualquier carácter que no sea un dígito decimal. exp \e des Coincide con un ESCAPE, \u001B. exp \E des Termina una secuencia entrecomillada \Q ... \E. exp \f des Coincide con un FORM FEED, \u000C. exp \G des Coincide si la posición actual está al final de la coincidencia anterior. exp \n des Coincide con un LINE FEED, \u000A. exp \N{UNICODE CHARACTER NAME} des Coincide con el carácter nombrado. exp \p{UNICODE PROPERTY NAME} des Coincide con cualquier carácter con la Propiedad Unicode especificada. exp \P{UNICODE PROPERTY NAME} des Coincide con cualquier carácter que no tenga la Propiedad Unicode especificada. exp \Q des Entrecomilla todos los caracteres siguientes hasta \E. exp \r des Coincide con un CARRIAGE RETURN, \u000D. exp \s des Coincide con un carácter de espacio en blanco. El espacio en blanco se define como [\t\n\f\r\p{Z}]. exp \S des Coincide con un carácter que no sea espacio en blanco. exp \t des Coincide con un HORIZONTAL TABULATION, \u0009. exp \uhhhh des Coincide con el carácter con el valor hexadecimal hhhh. exp \Uhhhhhhhh des Coincide con el carácter con el valor hexadecimal hhhhhhhh. Deben proporcionarse exactamente ocho dígitos hexadecimales, aunque el punto de código Unicode más grande sea \U0010ffff. exp \w des Coincide con un carácter de palabra. Los caracteres de palabra son [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}]. exp \W des Coincide con un carácter que no sea de palabra. exp \x{hhhh} des Coincide con el carácter con valor hexadecimal hhhh. Se pueden proporcionar de uno a seis dígitos hexadecimales. exp \xhh des Coincide con el carácter con valor hexadecimal de dos dígitos hh. exp \X des Coincide con un Clúster de Grafemas. exp \Z des Coincide si la posición actual está al final de la entrada, pero antes del terminador de línea final, si existe uno. exp \z des Coincide si la posición actual está al final de la entrada. exp \n des Referencia hacia atrás. Coincide con lo que sea que el n-ésimo grupo de captura haya coincidido. n debe ser un número ≥ 1 y ≤ número total de grupos de captura en el patrón. exp \0ooo des Coincide con un carácter Octal. ooo es de uno a tres dígitos octales. 0377 es el carácter Octal más grande permitido. El cero inicial es requerido; distingue las constantes Octales de las referencias hacia atrás. exp [pattern] des Coincide con cualquier carácter del patrón. exp . des Coincide con cualquier carácter. exp ^ des Coincide al principio de una línea. exp $ des Coincide al final de una línea. exp \ des Entrecomilla el siguiente carácter. Los caracteres que deben entrecomillarse para ser tratados como literales son * ? + [ ( ) { } ^ $ | \ . / operators exp | des Alternancia. A|B coincide con A o B. exp * des Coincide 0 o más veces. Coincide tantas veces como sea posible. exp + des Coincide 1 o más veces. Coincide tantas veces como sea posible. exp ? des Coincide cero o una vez. Prefiere una. exp {n} des Coincide exactamente n veces. exp {n,} des Coincide al menos n veces. Coincide tantas veces como sea posible. exp {n,m} des Coincide entre n y m veces. Coincide tantas veces como sea posible, pero no más de m. exp *? des Coincide 0 o más veces. Coincide tan pocas veces como sea posible. exp +? des Coincide 1 o más veces. Coincide tan pocas veces como sea posible. exp ?? des Coincide cero o una vez. Prefiere cero. exp {n}? des Coincide exactamente n veces. exp {n,}? des Coincide al menos n veces, pero no más de lo requerido para una coincidencia general del patrón. exp {n,m}? des Coincide entre n y m veces. Coincide tan pocas veces como sea posible, pero no menos de n. exp *+ des Coincide 0 o más veces. Coincide tantas veces como sea posible al encontrarse por primera vez, no reintenta con menos aunque la coincidencia general falle (Coincidencia Posesiva). exp ++ des Coincide 1 o más veces. Coincidencia posesiva. exp ?+ des Coincide cero o una vez. Coincidencia posesiva. exp {n}+ des Coincide exactamente n veces. exp {n,}+ des Coincide al menos n veces. Coincidencia Posesiva. exp {n,m}+ des Coincide entre n y m veces. Coincidencia Posesiva. exp (...) des Paréntesis de captura. El rango de entrada que coincidió con la subexpresión entre paréntesis está disponible después de la coincidencia. exp (?:...) des Paréntesis de no captura. Agrupa el patrón incluido, pero no proporciona captura del texto coincidente. Algo más eficiente que los paréntesis de captura. exp (?>...) des Paréntesis de coincidencia atómica. La primera coincidencia de la subexpresión entre paréntesis es la única que se intenta; si no lleva a una coincidencia general del patrón, retrocede la búsqueda de una coincidencia a una posición antes del "(?>" exp (?# ... ) des Comentario de formato libre (?# comentario ). exp (?= ... ) des Aserción de búsqueda hacia adelante. Verdadero si el patrón entre paréntesis coincide en la posición de entrada actual, pero no avanza la posición de entrada. exp (?! ... ) des Aserción negativa de búsqueda hacia adelante. Verdadero si el patrón entre paréntesis no coincide en la posición de entrada actual. No avanza la posición de entrada. exp (?<= ... ) des Aserción de búsqueda hacia atrás. Verdadero si el patrón entre paréntesis coincide con el texto que precede a la posición de entrada actual, siendo el último carácter de la coincidencia el carácter de entrada justo antes de la posición actual. No altera la posición de entrada. La longitud de las posibles cadenas coincidentes del patrón de búsqueda hacia atrás no debe ser ilimitada (sin operadores * o +). exp (?<! ... ) des Aserción negativa de búsqueda hacia atrás. Verdadero si el patrón entre paréntesis no coincide con el texto que precede a la posición de entrada actual, siendo el último carácter de la coincidencia el carácter de entrada justo antes de la posición actual. No altera la posición de entrada. La longitud de las posibles cadenas coincidentes del patrón de búsqueda hacia atrás no debe ser ilimitada (sin operadores * o +). exp (?ismwx-ismwx: ... ) des Configuración de banderas. Evalúa la expresión entre paréntesis con las banderas especificadas habilitadas o -deshabilitadas. Las banderas se definen en Opciones de Banderas. exp (?ismwx-ismwx) des Configuración de banderas. Cambia la configuración de las banderas. Los cambios se aplican a la porción del patrón que sigue a la configuración. Por ejemplo, (?i) cambia a una coincidencia insensible a mayúsculas y minúsculas. Las banderas se definen en Opciones de Banderas. ================================================ FILE: RegEx+/fr.lproj/CheatSheet.plist ================================================ metacharacters exp \a des Correspond à un BELL, \u0007 exp \A des Correspond au début de l'entrée. Diffère de ^ en ce que \A ne correspond pas après une nouvelle ligne dans l'entrée. exp \b, outside of a [Set] des Correspond si la position actuelle est une limite de mot. Les limites se produisent aux transitions entre les caractères de mot (\w) et de non-mot (\W), les marques combinées étant ignorées. Pour de meilleures limites de mots, voir useUnicodeWordBoundaries. exp \b, within a [Set] des Correspond à un BACKSPACE, \u0008. exp \B des Correspond si la position actuelle n'est pas une limite de mot. exp \cX des Correspond à un caractère control-X exp \d des Correspond à tout caractère de la catégorie générale Unicode Nd (Number, Decimal Digit). exp \D des Correspond à tout caractère qui n'est pas un chiffre décimal. exp \e des Correspond à un ESCAPE, \u001B. exp \E des Termine une séquence citée \Q ... \E. exp \f des Correspond à un FORM FEED, \u000C. exp \G des Correspond si la position actuelle est à la fin de la correspondance précédente. exp \n des Correspond à un LINE FEED, \u000A. exp \N{UNICODE CHARACTER NAME} des Correspond au caractère nommé. exp \p{UNICODE PROPERTY NAME} des Correspond à tout caractère avec la propriété Unicode spécifiée. exp \P{UNICODE PROPERTY NAME} des Correspond à tout caractère n'ayant pas la propriété Unicode spécifiée. exp \Q des Cite tous les caractères suivants jusqu'à \E. exp \r des Correspond à un CARRIAGE RETURN, \u000D. exp \s des Correspond à un caractère d'espacement. L'espace est défini comme [\t\n\f\r\p{Z}]. exp \S des Correspond à un caractère non-espace. exp \t des Correspond à une TABULATION HORIZONTALE, \u0009. exp \uhhhh des Correspond au caractère avec la valeur hexadécimale hhhh. exp \Uhhhhhhhh des Correspond au caractère avec la valeur hexadécimale hhhhhhhh. Exactement huit chiffres hexadécimaux doivent être fournis, même si le plus grand point de code Unicode est \U0010ffff. exp \w des Correspond à un caractère de mot. Les caractères de mot sont [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}]. exp \W des Correspond à un caractère non-mot. exp \x{hhhh} des Correspond au caractère avec la valeur hexadécimale hhhh. De un à six chiffres hexadécimaux peuvent être fournis. exp \xhh des Correspond au caractère avec la valeur hexadécimale à deux chiffres hh. exp \X des Correspond à un groupe de graphèmes. exp \Z des Correspond si la position actuelle est à la fin de l'entrée, mais avant le terminateur de ligne final, s'il en existe un. exp \z des Correspond si la position actuelle est à la fin de l'entrée. exp \n des Référence arrière. Correspond à ce que le nième groupe de capture a correspondu. n doit être un nombre ≥ 1 et ≤ nombre total de groupes de capture dans le motif. exp \0ooo des Correspond à un caractère octal. ooo va de un à trois chiffres octaux. 0377 est le plus grand caractère octal autorisé. Le zéro initial est requis ; il distingue les constantes octales des références arrière. exp [pattern] des Correspond à n'importe quel caractère du motif. exp . des Correspond à n'importe quel caractère. exp ^ des Correspond au début d'une ligne. exp $ des Correspond à la fin d'une ligne. exp \ des Cite le caractère suivant. Les caractères qui doivent être cités pour être traités comme des littéraux sont * ? + [ ( ) { } ^ $ | \ . / operators exp | des Alternance. A|B correspond à A ou B. exp * des Correspond 0 fois ou plus. Correspond autant de fois que possible. exp + des Correspond 1 fois ou plus. Correspond autant de fois que possible. exp ? des Correspond zéro ou une fois. Préfère une fois. exp {n} des Correspond exactement n fois. exp {n,} des Correspond au moins n fois. Correspond autant de fois que possible. exp {n,m} des Correspond entre n et m fois. Correspond autant de fois que possible, mais pas plus de m fois. exp *? des Correspond 0 fois ou plus. Correspond aussi peu de fois que possible. exp +? des Correspond 1 fois ou plus. Correspond aussi peu de fois que possible. exp ?? des Correspond zéro ou une fois. Préfère zéro. exp {n}? des Correspond exactement n fois. exp {n,}? des Correspond au moins n fois, mais pas plus que nécessaire pour une correspondance globale du motif. exp {n,m}? des Correspond entre n et m fois. Correspond aussi peu de fois que possible, mais pas moins de n fois. exp *+ des Correspond 0 fois ou plus. Correspond autant de fois que possible lors de la première rencontre, ne réessaie pas avec moins même si la correspondance globale échoue (Correspondance possessive). exp ++ des Correspond 1 fois ou plus. Correspondance possessive. exp ?+ des Correspond zéro ou une fois. Correspondance possessive. exp {n}+ des Correspond exactement n fois. exp {n,}+ des Correspond au moins n fois. Correspondance possessive. exp {n,m}+ des Correspond entre n et m fois. Correspondance possessive. exp (...) des Parenthèses de capture. La plage d'entrée qui correspond à la sous-expression entre parenthèses est disponible après la correspondance. exp (?:...) des Parenthèses non capturantes. Groupe le motif inclus, mais ne fournit pas de capture du texte correspondant. Légèrement plus efficace que les parenthèses de capture. exp (?>...) des Parenthèses de correspondance atomique. La première correspondance de la sous-expression entre parenthèses est la seule tentée ; si elle ne mène pas à une correspondance globale du motif, revient à la recherche d'une correspondance à une position avant le "(?>" exp (?# ... ) des Commentaire en format libre (?# commentaire ). exp (?= ... ) des Assertion de pré-vérification. Vrai si le motif entre parenthèses correspond à la position d'entrée actuelle, mais n'avance pas la position d'entrée. exp (?! ... ) des Assertion de pré-vérification négative. Vrai si le motif entre parenthèses ne correspond pas à la position d'entrée actuelle. N'avance pas la position d'entrée. exp (?<= ... ) des Assertion de post-vérification. Vrai si le motif entre parenthèses correspond au texte précédant la position d'entrée actuelle, le dernier caractère de la correspondance étant le caractère d'entrée juste avant la position actuelle. Ne modifie pas la position d'entrée. La longueur des chaînes possibles correspondantes au motif de post-vérification ne doit pas être illimitée (pas d'opérateurs * ou +). exp (?<! ... ) des Assertion de post-vérification négative. Vrai si le motif entre parenthèses ne correspond pas au texte précédant la position d'entrée actuelle, le dernier caractère de la correspondance étant le caractère d'entrée juste avant la position actuelle. Ne modifie pas la position d'entrée. La longueur des chaînes possibles correspondantes au motif de post-vérification ne doit pas être illimitée (pas d'opérateurs * ou +). exp (?ismwx-ismwx: ... ) des Paramètres de drapeaux. Évalue l'expression entre parenthèses avec les drapeaux spécifiés activés ou -désactivés. Les drapeaux sont définis dans les Options de Drapeaux. exp (?ismwx-ismwx) des Paramètres de drapeaux. Modifie les paramètres de drapeaux. Les modifications s'appliquent à la partie du motif suivant le paramètre. Par exemple, (?i) passe à une correspondance insensible à la casse. Les drapeaux sont définis dans les Options de Drapeaux. ================================================ FILE: RegEx+/it.lproj/CheatSheet.plist ================================================ metacharacters exp \a des Corrisponde a un BELL, \u0007 exp \A des Corrisponde all'inizio dell'input. Differisce da ^ in quanto \A non corrisponderà dopo una nuova riga all'interno dell'input. exp \b, outside of a [Set] des Corrisponde se la posizione corrente è un limite di parola. I limiti si verificano alle transizioni tra caratteri di parola (\w) e non-parola (\W), con i segni combinati ignorati. Per migliori limiti di parola, vedere useUnicodeWordBoundaries. exp \b, within a [Set] des Corrisponde a un BACKSPACE, \u0008. exp \B des Corrisponde se la posizione corrente non è un limite di parola. exp \cX des Corrisponde a un carattere control-X exp \d des Corrisponde a qualsiasi carattere con la categoria generale Unicode Nd (Number, Decimal Digit). exp \D des Corrisponde a qualsiasi carattere che non è una cifra decimale. exp \e des Corrisponde a un ESCAPE, \u001B. exp \E des Termina una sequenza citata \Q ... \E. exp \f des Corrisponde a un FORM FEED, \u000C. exp \G des Corrisponde se la posizione corrente è alla fine della corrispondenza precedente. exp \n des Corrisponde a un LINE FEED, \u000A. exp \N{UNICODE CHARACTER NAME} des Corrisponde al carattere nominato. exp \p{UNICODE PROPERTY NAME} des Corrisponde a qualsiasi carattere con la proprietà Unicode specificata. exp \P{UNICODE PROPERTY NAME} des Corrisponde a qualsiasi carattere che non ha la proprietà Unicode specificata. exp \Q des Cita tutti i caratteri seguenti fino a \E. exp \r des Corrisponde a un CARRIAGE RETURN, \u000D. exp \s des Corrisponde a un carattere di spaziatura. Lo spazio è definito come [\t\n\f\r\p{Z}]. exp \S des Corrisponde a un carattere non-spazio. exp \t des Corrisponde a una TABULAZIONE ORIZZONTALE, \u0009. exp \uhhhh des Corrisponde al carattere con il valore esadecimale hhhh. exp \Uhhhhhhhh des Corrisponde al carattere con il valore esadecimale hhhhhhhh. Devono essere fornite esattamente otto cifre esadecimali, anche se il punto di codice Unicode più grande è \U0010ffff. exp \w des Corrisponde a un carattere di parola. I caratteri di parola sono [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}]. exp \W des Corrisponde a un carattere non-parola. exp \x{hhhh} des Corrisponde al carattere con il valore esadecimale hhhh. Possono essere fornite da una a sei cifre esadecimali. exp \xhh des Corrisponde al carattere con il valore esadecimale a due cifre hh. exp \X des Corrisponde a un cluster di grafemi. exp \Z des Corrisponde se la posizione corrente è alla fine dell'input, ma prima del terminatore di riga finale, se esiste. exp \z des Corrisponde se la posizione corrente è alla fine dell'input. exp \n des Riferimento all'indietro. Corrisponde a ciò che ha corrisposto l'ennesimo gruppo di cattura. n deve essere un numero ≥ 1 e ≤ numero totale di gruppi di cattura nel pattern. exp \0ooo des Corrisponde a un carattere ottale. ooo va da uno a tre cifre ottali. 0377 è il più grande carattere ottale consentito. Lo zero iniziale è richiesto; distingue le costanti ottali dai riferimenti all'indietro. exp [pattern] des Corrisponde a qualsiasi carattere del pattern. exp . des Corrisponde a qualsiasi carattere. exp ^ des Corrisponde all'inizio di una riga. exp $ des Corrisponde alla fine di una riga. exp \ des Cita il carattere seguente. I caratteri che devono essere citati per essere trattati come letterali sono * ? + [ ( ) { } ^ $ | \ . / operators exp | des Alternanza. A|B corrisponde ad A o B. exp * des Corrisponde 0 o più volte. Corrisponde il maggior numero di volte possibile. exp + des Corrisponde 1 o più volte. Corrisponde il maggior numero di volte possibile. exp ? des Corrisponde zero o una volta. Preferisce una volta. exp {n} des Corrisponde esattamente n volte. exp {n,} des Corrisponde almeno n volte. Corrisponde il maggior numero di volte possibile. exp {n,m} des Corrisponde tra n e m volte. Corrisponde il maggior numero di volte possibile, ma non più di m volte. exp *? des Corrisponde 0 o più volte. Corrisponde il minor numero di volte possibile. exp +? des Corrisponde 1 o più volte. Corrisponde il minor numero di volte possibile. exp ?? des Corrisponde zero o una volta. Preferisce zero. exp {n}? des Corrisponde esattamente n volte. exp {n,}? des Corrisponde almeno n volte, ma non più di quanto richiesto per una corrispondenza complessiva del pattern. exp {n,m}? des Corrisponde tra n e m volte. Corrisponde il minor numero di volte possibile, ma non meno di n volte. exp *+ des Corrisponde 0 o più volte. Corrisponde il maggior numero di volte possibile al primo incontro, non riprova con meno anche se la corrispondenza complessiva fallisce (Corrispondenza possessiva). exp ++ des Corrisponde 1 o più volte. Corrispondenza possessiva. exp ?+ des Corrisponde zero o una volta. Corrispondenza possessiva. exp {n}+ des Corrisponde esattamente n volte. exp {n,}+ des Corrisponde almeno n volte. Corrispondenza possessiva. exp {n,m}+ des Corrisponde tra n e m volte. Corrispondenza possessiva. exp (...) des Parentesi di cattura. L'intervallo di input che corrisponde alla sottoespressione tra parentesi è disponibile dopo la corrispondenza. exp (?:...) des Parentesi non catturanti. Raggruppa il pattern incluso, ma non fornisce la cattura del testo corrispondente. Leggermente più efficiente delle parentesi di cattura. exp (?>...) des Parentesi di corrispondenza atomica. La prima corrispondenza della sottoespressione tra parentesi è l'unica tentata; se non porta a una corrispondenza complessiva del pattern, torna alla ricerca di una corrispondenza a una posizione prima del "(?>" exp (?# ... ) des Commento in formato libero (?# commento ). exp (?= ... ) des Asserzione di anticipazione positiva. Vero se il pattern tra parentesi corrisponde alla posizione di input corrente, ma non avanza la posizione di input. exp (?! ... ) des Asserzione di anticipazione negativa. Vero se il pattern tra parentesi non corrisponde alla posizione di input corrente. Non avanza la posizione di input. exp (?<= ... ) des Asserzione di lookbehind. Vero se il pattern tra parentesi corrisponde al testo che precede la posizione di input corrente, con l'ultimo carattere della corrispondenza che è il carattere di input appena prima della posizione corrente. Non altera la posizione di input. La lunghezza delle stringhe possibili corrispondenti al pattern di lookbehind non deve essere illimitata (nessun operatore * o +). exp (?<! ... ) des Asserzione di lookbehind negativa. Vero se il pattern tra parentesi non corrisponde al testo che precede la posizione di input corrente, con l'ultimo carattere della corrispondenza che è il carattere di input appena prima della posizione corrente. Non altera la posizione di input. La lunghezza delle stringhe possibili corrispondenti al pattern di lookbehind non deve essere illimitata (nessun operatore * o +). exp (?ismwx-ismwx: ... ) des Impostazioni di flag. Valuta l'espressione tra parentesi con i flag specificati abilitati o -disabilitati. I flag sono definiti nelle Opzioni di Flag. exp (?ismwx-ismwx) des Impostazioni di flag. Modifica le impostazioni di flag. Le modifiche si applicano alla parte del pattern che segue l'impostazione. Ad esempio, (?i) passa a una corrispondenza non sensibile alle maiuscole. I flag sono definiti nelle Opzioni di Flag. ================================================ FILE: RegEx+/ja.lproj/CheatSheet.plist ================================================ metacharacters exp \a des BELL、\u0007にマッチします exp \A des 入力の開始にマッチします。^とは異なり、\Aは入力内の改行後にはマッチしません。 exp \b, outside of a [Set] des 現在位置が単語境界の場合にマッチします。境界は単語文字(\w)と非単語文字(\W)の間の遷移で発生し、結合文字は無視されます。より良い単語境界については、useUnicodeWordBoundariesを参照してください。 exp \b, within a [Set] des [集合]内でBACKSPACE、\u0008にマッチします。 exp \B des 現在位置が単語境界でない場合にマッチします。 exp \cX des コントロール-X文字にマッチします exp \d des Unicode汎用カテゴリNd(数字、十進数字)の任意の文字にマッチします。 exp \D des 十進数字以外の任意の文字にマッチします。 exp \e des ESCAPE、\u001Bにマッチします。 exp \E des \Q ... \Eクォートされたシーケンスを終了します。 exp \f des FORM FEED、\u000Cにマッチします。 exp \G des 現在位置が前回のマッチの終端にある場合にマッチします。 exp \n des LINE FEED、\u000Aにマッチします。 exp \N{UNICODE CHARACTER NAME} des 指定された名前の文字にマッチします。 exp \p{UNICODE PROPERTY NAME} des 指定されたUnicodeプロパティを持つ任意の文字にマッチします。 exp \P{UNICODE PROPERTY NAME} des 指定されたUnicodeプロパティを持たない任意の文字にマッチします。 exp \Q des \Eまで後続の全ての文字をクォートします。 exp \r des CARRIAGE RETURN、\u000Dにマッチします。 exp \s des 空白文字にマッチします。空白文字は[\t\n\f\r\p{Z}]として定義されます。 exp \S des 非空白文字にマッチします。 exp \t des HORIZONTAL TABULATION、\u0009にマッチします。 exp \uhhhh des 16進値hhhhの文字にマッチします。 exp \Uhhhhhhhh des 16進値hhhhhhhhの文字にマッチします。最大のUnicodeコードポイントが\U0010ffffであっても、正確に8桁の16進数を指定する必要があります。 exp \w des 単語文字にマッチします。単語文字は[\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}]です。 exp \W des 非単語文字にマッチします。 exp \x{hhhh} des 16進値hhhhの文字にマッチします。1桁から6桁まで指定できます。 exp \xhh des 2桁の16進値hhの文字にマッチします。 exp \X des 書記素クラスターにマッチします。 exp \Z des 現在位置が入力の終端にあり、かつ最終行終端記号がある場合はその直前の位置にある場合にマッチします。 exp \z des 現在位置が入力の終端にある場合にマッチします。 exp \n des 後方参照。n番目のキャプチャグループがマッチした内容と同じものにマッチします。nはパターン内のキャプチャグループ総数以下かつ1以上の数値である必要があります。 exp \0ooo des 8進文字にマッチします。oooは1桁から3桁の8進数字です。0377が許可される最大の8進文字です。先頭のゼロは必須で、8進定数と後方参照を区別します。 exp [pattern] des パターン内の任意の1文字にマッチします。 exp . des 任意の文字にマッチします。 exp ^ des 行の開始にマッチします。 exp $ des 行の終端にマッチします。 exp \ des 後続の文字をクォートします。リテラルとして扱うためにクォートが必要な文字は * ? + [ ( ) { } ^ $ | \ . / です operators exp | des 選択。A|BはAまたはBのいずれかにマッチします。 exp * des 0回以上マッチします。可能な限り多くマッチします。 exp + des 1回以上マッチします。可能な限り多くマッチします。 exp ? des 0回または1回マッチします。1回を優先します。 exp {n} des 正確にn回マッチします。 exp {n,} des 最低n回マッチします。可能な限り多くマッチします。 exp {n,m} des n回からm回までマッチします。可能な限り多くマッチしますが、m回を超えません。 exp *? des 0回以上マッチします。可能な限り少なくマッチします。 exp +? des 1回以上マッチします。可能な限り少なくマッチします。 exp ?? des 0回または1回マッチします。0回を優先します。 exp {n}? des 正確にn回マッチします。 exp {n,}? des 最低n回マッチしますが、全体のパターンマッチに必要な回数を超えません。 exp {n,m}? des n回からm回までマッチします。可能な限り少なくマッチしますが、n回未満にはなりません。 exp *+ des 0回以上マッチします。初回時に可能な限り多くマッチし、全体のマッチが失敗しても、より少ないマッチで再試行しません(占有マッチ)。 exp ++ des 1回以上マッチします。占有マッチです。 exp ?+ des 0回または1回マッチします。占有マッチです。 exp {n}+ des 正確にn回マッチします。 exp {n,}+ des 最低n回マッチします。占有マッチです。 exp {n,m}+ des n回からm回までマッチします。占有マッチです。 exp (...) des キャプチャ括弧。括弧内のサブ式にマッチした入力の範囲は、マッチ後に使用可能です。 exp (?:...) des 非キャプチャ括弧。含まれるパターンをグループ化しますが、マッチしたテキストのキャプチャは行いません。キャプチャ括弧よりもやや効率的です。 exp (?>...) des アトミックマッチ括弧。括弧内のサブ式の最初のマッチのみを試し、全体のパターンマッチにつながらない場合は"(?>"の直前の位置までバックアップします exp (?# ... ) des 自由形式コメント (?# comment )。 exp (?= ... ) des 先読みアサーション。括弧内のパターンが現在の入力位置でマッチする場合はtrueですが、入力位置は進みません。 exp (?! ... ) des 否定先読みアサーション。括弧内のパターンが現在の入力位置でマッチしない場合はtrueです。入力位置は進みません。 exp (?<= ... ) des 後読みアサーション。括弧内のパターンが現在の入力位置の前のテキストにマッチし、マッチの最後の文字が現在位置の直前の入力文字である場合はtrueです。入力位置は変更されません。後読みパターンによってマッチされる可能性のある文字列の長さは無制限であってはなりません(*や+演算子は使用できません。 exp (?<! ... ) des 否定後読みアサーション。括弧内のパターンが現在の入力位置の前のテキストにマッチせず、マッチの最後の文字が現在位置の直前の入力文字である場合はtrueです。入力位置は変更されません。後読みパターンによってマッチされる可能性のある文字列の長さは無制限であってはなりません(*や+演算子は使用できません。 exp (?ismwx-ismwx: ... ) des フラグ設定。指定されたフラグを有効または-無効にして括弧内の式を評価します。フラグはFlag Optionsで定義されています。 exp (?ismwx-ismwx) des フラグ設定。フラグ設定を変更します。変更は設定以降のパターン部分に適用されます。例えば、(?i)は大文字小文字を区別しないマッチに変更します。フラグはFlag Optionsで定義されています。 ================================================ FILE: RegEx+/ko.lproj/CheatSheet.plist ================================================ metacharacters exp \a des BELL 문자와 일치, \u0007 exp \A des 입력의 시작 부분과 일치합니다. 입력 내 줄바꿈 후에는 일치하지 않는다는 점에서 ^와 다릅니다. exp \b, outside of a [Set] des 현재 위치가 단어 경계인 경우 일치합니다. 경계는 결합 표시를 무시하고 단어(\w)와 비단어(\W) 문자 간의 전환에서 발생합니다. 더 나은 단어 경계를 원하면 useUnicodeWordBoundaries를 참조하세요. exp \b, within a [Set] des BACKSPACE와 일치, \u0008. exp \B des 현재 위치가 단어 경계가 아닌 경우 일치합니다. exp \cX des 제어-X 문자와 일치합니다 exp \d des 유니코드 일반 범주가 Nd(숫자, 10진수)인 모든 문자와 일치합니다. exp \D des 10진수가 아닌 모든 문자와 일치합니다. exp \e des ESCAPE와 일치, \u001B. exp \E des \Q ... \E 인용 시퀀스를 종료합니다. exp \f des FORM FEED와 일치, \u000C. exp \G des 현재 위치가 이전 일치의 끝에 있는 경우 일치합니다. exp \n des LINE FEED와 일치, \u000A. exp \N{UNICODE CHARACTER NAME} des 명명된 문자와 일치합니다. exp \p{UNICODE PROPERTY NAME} des 지정된 유니코드 속성을 가진 모든 문자와 일치합니다. exp \P{UNICODE PROPERTY NAME} des 지정된 유니코드 속성을 갖지 않은 모든 문자와 일치합니다. exp \Q des \E까지 다음 모든 문자를 인용합니다. exp \r des CARRIAGE RETURN과 일치, \u000D. exp \s des 공백 문자와 일치합니다. 공백은 [\t\n\f\r\p{Z}]로 정의됩니다. exp \S des 공백이 아닌 문자와 일치합니다. exp \t des HORIZONTAL TABULATION과 일치, \u0009. exp \uhhhh des 16진수 값 hhhh를 가진 문자와 일치합니다. exp \Uhhhhhhhh des 16진수 값 hhhhhhhh를 가진 문자와 일치합니다. 가장 큰 유니코드 코드 포인트가 \U0010ffff이더라도 정확히 8개의 16진수를 제공해야 합니다. exp \w des 단어 문자와 일치합니다. 단어 문자는 [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}]입니다. exp \W des 단어가 아닌 문자와 일치합니다. exp \x{hhhh} des 16진수 값 hhhh를 가진 문자와 일치합니다. 1개에서 6개의 16진수를 제공할 수 있습니다. exp \xhh des 두 자리 16진수 값 hh를 가진 문자와 일치합니다. exp \X des 자소 클러스터와 일치합니다. exp \Z des 현재 위치가 입력의 끝에 있지만 최종 줄 종결자가 있는 경우 그 앞에 있으면 일치합니다. exp \z des 현재 위치가 입력의 끝에 있으면 일치합니다. exp \n des 역참조. n번째 캡처 그룹이 일치한 내용과 일치합니다. n은 ≥ 1이고 패턴의 총 캡처 그룹 수 ≤인 숫자여야 합니다. exp \0ooo des 8진수 문자와 일치합니다. ooo는 1개에서 3개의 8진수입니다. 0377은 허용되는 가장 큰 8진수 문자입니다. 앞의 0은 필수이며 8진수 상수를 역참조와 구별합니다. exp [pattern] des 패턴의 모든 문자 중 하나와 일치합니다. exp . des 모든 문자와 일치합니다. exp ^ des 줄의 시작 부분과 일치합니다. exp $ des 줄의 끝 부분과 일치합니다. exp \ des 다음 문자를 인용합니다. 리터럴로 처리되기 위해 인용해야 하는 문자는 * ? + [ ( ) { } ^ $ | \ . /입니다 operators exp | des 대체. A|B는 A 또는 B와 일치합니다. exp * des 0회 이상 일치합니다. 가능한 한 많이 일치합니다. exp + des 1회 이상 일치합니다. 가능한 한 많이 일치합니다. exp ? des 0회 또는 1회 일치합니다. 1회를 선호합니다. exp {n} des 정확히 n회 일치합니다. exp {n,} des 최소 n회 일치합니다. 가능한 한 많이 일치합니다. exp {n,m} des n회에서 m회 사이로 일치합니다. 가능한 한 많이 일치하지만 m회를 초과하지 않습니다. exp *? des 0회 이상 일치합니다. 가능한 한 적게 일치합니다. exp +? des 1회 이상 일치합니다. 가능한 한 적게 일치합니다. exp ?? des 0회 또는 1회 일치합니다. 0회를 선호합니다. exp {n}? des 정확히 n회 일치합니다. exp {n,}? des 최소 n회 일치하지만 전체 패턴 일치에 필요한 것보다 많지 않습니다. exp {n,m}? des n회에서 m회 사이로 일치합니다. 가능한 한 적게 일치하지만 n회보다 적지 않습니다. exp *+ des 0회 이상 일치합니다. 처음 발견했을 때 가능한 한 많이 일치하고, 전체 일치가 실패하더라도 더 적게 재시도하지 않습니다(소유 일치). exp ++ des 1회 이상 일치합니다. 소유 일치. exp ?+ des 0회 또는 1회 일치합니다. 소유 일치. exp {n}+ des 정확히 n회 일치합니다. exp {n,}+ des 최소 n회 일치합니다. 소유 일치. exp {n,m}+ des n회에서 m회 사이로 일치합니다. 소유 일치. exp (...) des 캡처 괄호. 괄호로 묶인 하위 표현식과 일치하는 입력 범위는 일치 후에 사용할 수 있습니다. exp (?:...) des 비캡처 괄호. 포함된 패턴을 그룹화하지만 일치하는 텍스트의 캡처를 제공하지 않습니다. 캡처 괄호보다 다소 효율적입니다. exp (?>...) des 원자 일치 괄호. 괄호로 묶인 하위 표현식의 첫 번째 일치만 시도됩니다. 전체 패턴 일치로 이어지지 않으면 "(?>" 앞의 위치로 일치 검색을 백업합니다 exp (?# ... ) des 자유 형식 주석 (?# comment ). exp (?= ... ) des 전방 탐색 어서션. 괄호로 묶인 패턴이 현재 입력 위치와 일치하지만 입력 위치를 진행하지 않으면 참입니다. exp (?! ... ) des 부정 전방 탐색 어서션. 괄호로 묶인 패턴이 현재 입력 위치와 일치하지 않으면 참입니다. 입력 위치를 진행하지 않습니다. exp (?<= ... ) des 후방 탐색 어서션. 괄호로 묶인 패턴이 현재 입력 위치 앞의 텍스트와 일치하고 일치의 마지막 문자가 현재 위치 바로 앞의 입력 문자인 경우 참입니다. 입력 위치를 변경하지 않습니다. 후방 탐색 패턴과 일치할 수 있는 문자열의 길이는 무제한이 아니어야 합니다(* 또는 + 연산자 없음). exp (?<! ... ) des 부정 후방 탐색 어서션. 괄호로 묶인 패턴이 현재 입력 위치 앞의 텍스트와 일치하지 않고 일치의 마지막 문자가 현재 위치 바로 앞의 입력 문자인 경우 참입니다. 입력 위치를 변경하지 않습니다. 후방 탐색 패턴과 일치할 수 있는 문자열의 길이는 무제한이 아니어야 합니다(* 또는 + 연산자 없음). exp (?ismwx-ismwx: ... ) des 플래그 설정. 지정된 플래그가 활성화되거나 -비활성화된 상태에서 괄호로 묶인 표현식을 평가합니다. 플래그는 플래그 옵션에 정의되어 있습니다. exp (?ismwx-ismwx) des 플래그 설정. 플래그 설정을 변경합니다. 변경 사항은 설정 다음의 패턴 부분에 적용됩니다. 예를 들어 (?i)는 대소문자를 구분하지 않는 일치로 변경합니다. 플래그는 플래그 옵션에 정의되어 있습니다. ================================================ FILE: RegEx+/nl.lproj/CheatSheet.plist ================================================ metacharacters exp \a des Komt overeen met een BELL, \u0007 exp \A des Komt overeen met het begin van de invoer. Verschilt van ^ doordat \A niet overeenkomt na een nieuwe regel in de invoer. exp \b, outside of a [Set] des Komt overeen als de huidige positie een woordgrens is. Grenzen treden op bij overgangen tussen woord- (\w) en niet-woord- (\W) tekens, waarbij combinerende markeringen worden genegeerd. Voor betere woordgrenzen, zie useUnicodeWordBoundaries. exp \b, within a [Set] des Komt overeen met een BACKSPACE, \u0008. exp \B des Komt overeen als de huidige positie geen woordgrens is. exp \cX des Komt overeen met een control-X teken exp \d des Komt overeen met elk teken met de Unicode Algemene Categorie Nd (Getal, Decimaal Cijfer.) exp \D des Komt overeen met elk teken dat geen decimaal cijfer is. exp \e des Komt overeen met een ESCAPE, \u001B. exp \E des Beëindigt een \Q ... \E geciteerde reeks. exp \f des Komt overeen met een FORM FEED, \u000C. exp \G des Komt overeen als de huidige positie aan het einde van de vorige overeenkomst is. exp \n des Komt overeen met een LINE FEED, \u000A. exp \N{UNICODE CHARACTER NAME} des Komt overeen met het benoemde teken. exp \p{UNICODE PROPERTY NAME} des Komt overeen met elk teken met de opgegeven Unicode-eigenschap. exp \P{UNICODE PROPERTY NAME} des Komt overeen met elk teken dat niet de opgegeven Unicode-eigenschap heeft. exp \Q des Citeert alle volgende tekens tot \E. exp \r des Komt overeen met een CARRIAGE RETURN, \u000D. exp \s des Komt overeen met een witruimte-teken. Witruimte is gedefinieerd als [\t\n\f\r\p{Z}]. exp \S des Komt overeen met een niet-witruimte-teken. exp \t des Komt overeen met een HORIZONTALE TABULATIE, \u0009. exp \uhhhh des Komt overeen met het teken met de hex-waarde hhhh. exp \Uhhhhhhhh des Komt overeen met het teken met de hex-waarde hhhhhhhh. Er moeten precies acht hex-cijfers worden opgegeven, ook al is het grootste Unicode-codepunt \U0010ffff. exp \w des Komt overeen met een woordteken. Woordtekens zijn [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}]. exp \W des Komt overeen met een niet-woordteken. exp \x{hhhh} des Komt overeen met het teken met de hex-waarde hhhh. Er kunnen één tot zes hex-cijfers worden opgegeven. exp \xhh des Komt overeen met het teken met de tweecijferige hex-waarde hh. exp \X des Komt overeen met een Grafeemcluster. exp \Z des Komt overeen als de huidige positie aan het einde van de invoer is, maar voor de laatste regeleenheid, als die bestaat. exp \z des Komt overeen als de huidige positie aan het einde van de invoer is. exp \n des Terugverwijzing. Komt overeen met wat de n-de vastleggroep heeft gevonden. n moet een getal ≥ 1 en ≤ het totale aantal vastleggroepen in het patroon zijn. exp \0ooo des Komt overeen met een octaal teken. ooo is één tot drie octale cijfers. 0377 is het grootste toegestane octale teken. De leidende nul is vereist; deze onderscheidt octale constanten van terugverwijzingen. exp [pattern] des Komt overeen met elk willekeurig teken uit het patroon. exp . des Komt overeen met elk willekeurig teken. exp ^ des Komt overeen aan het begin van een regel. exp $ des Komt overeen aan het einde van een regel. exp \ des Citeert het volgende teken. Tekens die geciteerd moeten worden om als letterlijk te worden behandeld zijn * ? + [ ( ) { } ^ $ | \ . / operators exp | des Alternatie. A|B komt overeen met A of B. exp * des Komt 0 of meer keer overeen. Komt zo vaak mogelijk overeen. exp + des Komt 1 of meer keer overeen. Komt zo vaak mogelijk overeen. exp ? des Komt nul of één keer overeen. Voorkeur voor één. exp {n} des Komt precies n keer overeen. exp {n,} des Komt ten minste n keer overeen. Komt zo vaak mogelijk overeen. exp {n,m} des Komt tussen n en m keer overeen. Komt zo vaak mogelijk overeen, maar niet vaker dan m. exp *? des Komt 0 of meer keer overeen. Komt zo weinig mogelijk overeen. exp +? des Komt 1 of meer keer overeen. Komt zo weinig mogelijk overeen. exp ?? des Komt nul of één keer overeen. Voorkeur voor nul. exp {n}? des Komt precies n keer overeen. exp {n,}? des Komt ten minste n keer overeen, maar niet vaker dan nodig voor een algehele patroonovereenkomst. exp {n,m}? des Komt tussen n en m keer overeen. Komt zo weinig mogelijk overeen, maar niet minder dan n. exp *+ des Komt 0 of meer keer overeen. Komt zo vaak mogelijk overeen bij de eerste ontmoeting, probeer niet opnieuw met minder, zelfs niet als de algehele overeenkomst mislukt (Bezitterige Overeenkomst). exp ++ des Komt 1 of meer keer overeen. Bezitterige overeenkomst. exp ?+ des Komt nul of één keer overeen. Bezitterige overeenkomst. exp {n}+ des Komt precies n keer overeen. exp {n,}+ des Komt ten minste n keer overeen. Bezitterige Overeenkomst. exp {n,m}+ des Komt tussen n en m keer overeen. Bezitterige Overeenkomst. exp (...) des Vastleggende haakjes. Het bereik van de invoer dat overeenkwam met de subexpressie tussen haakjes is beschikbaar na de overeenkomst. exp (?:...) des Niet-vastleggende haakjes. Groepeert het opgenomen patroon, maar biedt geen vastlegging van overeenkomende tekst. Iets efficiënter dan vastleggende haakjes. exp (?>...) des Atoom-overeenkomst haakjes. De eerste overeenkomst van de subexpressie tussen haakjes is de enige die wordt geprobeerd; als dit niet leidt tot een algehele patroonovereenkomst, ga dan terug naar een positie voor de "(?>" exp (?# ... ) des Vrij-formaat commentaar (?# commentaar ). exp (?= ... ) des Look-ahead bewering. Waar als het patroon tussen haakjes overeenkomt op de huidige invoerpositie, maar de invoerpositie niet opschuift. exp (?! ... ) des Negatieve look-ahead bewering. Waar als het patroon tussen haakjes niet overeenkomt op de huidige invoerpositie. Schuift de invoerpositie niet op. exp (?<= ... ) des Look-behind bewering. Waar als het patroon tussen haakjes overeenkomt met tekst die voorafgaat aan de huidige invoerpositie, waarbij het laatste teken van de overeenkomst het invoerteken is vlak voor de huidige positie. Verandert de invoerpositie niet. De lengte van mogelijke tekenreeksen die overeenkomen met het look-behind patroon mag niet onbegrensd zijn (geen * of + operatoren). exp (?<! ... ) des Negatieve look-behind bewering. Waar als het patroon tussen haakjes niet overeenkomt met tekst die voorafgaat aan de huidige invoerpositie, waarbij het laatste teken van de overeenkomst het invoerteken is vlak voor de huidige positie. Verandert de invoerpositie niet. De lengte van mogelijke tekenreeksen die overeenkomen met het look-behind patroon mag niet onbegrensd zijn (geen * of + operatoren). exp (?ismwx-ismwx: ... ) des Vlaginstellingen. Evalueer de expressie tussen haakjes met de opgegeven vlaggen ingeschakeld of -uitgeschakeld. De vlaggen zijn gedefinieerd in Vlagopties. exp (?ismwx-ismwx) des Vlaginstellingen. Wijzig de vlaginstellingen. Wijzigingen zijn van toepassing op het deel van het patroon dat volgt op de instelling. Bijvoorbeeld, (?i) wijzigt in een hoofdletterongevoelige overeenkomst. De vlaggen zijn gedefinieerd in Vlagopties. ================================================ FILE: RegEx+/pl.lproj/CheatSheet.plist ================================================ metacharacters exp \a des Dopasowanie dzwonka (BELL), \u0007 exp \A des Dopasowanie na początku wejścia. Różni się od ^ tym, że \A nie dopasuje po nowej linii wewnątrz wejścia. exp \b, outside of a [Set] des Dopasowanie, jeśli obecna pozycja jest granicą słowa. Granice występują na przejściach między znakami słownymi (\w) i niesłownymi (\W), z pominięciem znaków łączących. Dla lepszych granic słów zobacz useUnicodeWordBoundaries. exp \b, within a [Set] des Dopasowanie znaku BACKSPACE, \u0008. exp \B des Dopasowanie, jeśli obecna pozycja nie jest granicą słowa. exp \cX des Dopasowanie znaku control-X exp \d des Dopasowanie dowolnego znaku z kategorii Unicode Nd (Liczba, cyfra dziesiętna). exp \D des Dopasowanie dowolnego znaku, który nie jest cyfrą dziesiętną. exp \e des Dopasowanie znaku ESCAPE, \u001B. exp \E des Kończy sekwencję cytowaną \Q ... \E. exp \f des Dopasowanie znaku FORM FEED, \u000C. exp \G des Dopasowanie, jeśli obecna pozycja znajduje się na końcu poprzedniego dopasowania. exp \n des Dopasowanie znaku LINE FEED, \u000A. exp \N{UNICODE CHARACTER NAME} des Dopasowanie nazwanego znaku. exp \p{UNICODE PROPERTY NAME} des Dopasowanie dowolnego znaku o określonej właściwości Unicode. exp \P{UNICODE PROPERTY NAME} des Dopasowanie dowolnego znaku niemającego określonej właściwości Unicode. exp \Q des Cytuje wszystkie następne znaki aż do \E. exp \r des Dopasowanie znaku CARRIAGE RETURN, \u000D. exp \s des Dopasowanie białego znaku. Białe znaki to [\t\n\f\r\p{Z}]. exp \S des Dopasowanie znaku niebędącego białym znakiem. exp \t des Dopasowanie tabulacji poziomej, \u0009. exp \uhhhh des Dopasowanie znaku o wartości szesnastkowej hhhh. exp \Uhhhhhhhh des Dopasowanie znaku o wartości szesnastkowej hhhhhhhh. Należy podać dokładnie osiem cyfr szesnastkowych, mimo że największym punktem kodowym Unicode jest \U0010ffff. exp \w des Dopasowanie znaku słowa. Znaki słowa to [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}]. exp \W des Dopasowanie znaku niebędącego znakiem słowa. exp \x{hhhh} des Dopasowanie znaku o wartości szesnastkowej hhhh. Można podać od jednej do sześciu cyfr szesnastkowych. exp \xhh des Dopasowanie znaku o dwucyfrowej wartości szesnastkowej hh. exp \X des Dopasowanie klastra grafemów. exp \Z des Dopasowanie, jeśli obecna pozycja znajduje się na końcu wejścia, ale przed końcowym separatorem linii, jeśli taki istnieje. exp \z des Dopasowanie, jeśli obecna pozycja znajduje się na końcu wejścia. exp \n des Referencja wsteczna. Dopasowuje to, co dopasowała n-ta grupa przechwytująca. n musi być liczbą ≥ 1 i ≤ całkowitej liczbie grup przechwytujących w rzędzie. exp \0ooo des Dopasowanie znaku ósemkowego. ooo to od jednej do trzech cyfr ósemkowych. 0377 to największy dozwolony znak ósemkowy. Wymagane jest zero wiodące; odróżnia ono stałe ósemkowe od referencji wstecznych. exp [pattern] des Dopasowanie dowolnego pojedynczego znaku z wzorca. exp . des Dopasowanie dowolnego znaku. exp ^ des Dopasowanie na początku linii. exp $ des Dopasowanie na końcu linii. exp \ des Cytuje następny znak. Znaki, które muszą być cytowane, aby być traktowane dosłownie, to * ? + [ ( ) { } ^ $ | \ . / operators exp | des Alternatywa. A|B dopasowuje albo A, albo B. exp * des Dopasowanie 0 lub więcej razy. Dopasowuje tak wiele razy, jak to możliwe. exp + des Dopasowanie 1 lub więcej razy. Dopasowuje tak wiele razy, jak to możliwe. exp ? des Dopasowanie zero lub jeden raz. Preferuje jeden. exp {n} des Dopasowanie dokładnie n razy. exp {n,} des Dopasowanie co najmniej n razy. Dopasowuje tak wiele razy, jak to możliwe. exp {n,m} des Dopasowanie od n do m razy. Dopasowuje tak wiele razy, jak to możliwe, ale nie więcej niż m. exp *? des Dopasowanie 0 lub więcej razy. Dopasowuje tak mało razy, jak to możliwe. exp +? des Dopasowanie 1 lub więcej razy. Dopasowuje tak mało razy, jak to możliwe. exp ?? des Dopasowanie zero lub jeden raz. Preferuje zero. exp {n}? des Dopasowanie dokładnie n razy. exp {n,}? des Dopasowanie co najmniej n razy, ale nie więcej niż wymaga dopasowanie całego wzorca. exp {n,m}? des Dopasowanie od n do m razy. Dopasowuje tak mało razy, jak to możliwe, ale nie mniej niż n. exp *+ des Dopasowanie 0 lub więcej razy. Dopasowuje tak wiele razy, jak to możliwe przy pierwszym napotkaniu, nie próbuje ponownie z mniejszą liczbą, nawet jeśli całe dopasowanie się nie powiedzie (dopasowanie zachłanne/posesywne). exp ++ des Dopasowanie 1 lub więcej razy. Dopasowanie posesywne. exp ?+ des Dopasowanie zero lub jeden raz. Dopasowanie posesywne. exp {n}+ des Dopasowanie dokładnie n razy. exp {n,}+ des Dopasowanie co najmniej n razy. Dopasowanie posesywne. exp {n,m}+ des Dopasowanie od n do m razy. Dopasowanie posesywne. exp (...) des Nawiasy przechwytujące. Zakres wejścia, który pasował do podwyrażenia w nawiasach, jest dostępny po dopasowaniu. exp (?:...) des Nawiasy nieprzechwytujące. Grupuje zawarty wzorzec, ale nie zapewnia przechwytywania dopasowanego tekstu. Nieco bardziej wydajne niż nawiasy przechwytujące. exp (?>...) des Nawiasy dopasowania atomowego. Wypróbowywane jest tylko pierwsze dopasowanie podwyrażenia w nawiasach; jeśli nie prowadzi ono do dopasowania całego wzorca, wyszukiwanie jest cofane do pozycji przed "(?>" exp (?# ... ) des Komentarz o dowolnym formacie (?# komentarz ). exp (?= ... ) des Asercja look-ahead (wyprzedzająca). Prawda, jeśli wzorzec w nawiasach pasuje w bieżącej pozycji wejściowej, ale nie przesuwa pozycji wejściowej. exp (?! ... ) des Negatywna asercja look-ahead. Prawda, jeśli wzorzec w nawiasach nie pasuje w bieżącej pozycji wejściowej. Nie przesuwa pozycji wejściowej. exp (?<= ... ) des Asercja look-behind (wsteczna). Prawda, jeśli wzorzec w nawiasach pasuje do tekstu poprzedzającego bieżącą pozycję wejściową, przy czym ostatni znak dopasowania jest znakiem wejściowym tuż przed bieżącą pozycją. Nie zmienia pozycji wejściowej. Długość możliwych ciągów pasujących do wzorca look-behind nie może być nieograniczona (brak operatorów * lub +). exp (?<! ... ) des Negatywna asercja look-behind. Prawda, jeśli wzorzec w nawiasach nie pasuje do tekstu poprzedzającego bieżącą pozycję wejściową, przy czym ostatni znak dopasowania jest znakiem wejściowym tuż przed bieżącą pozycją. Nie zmienia pozycji wejściowej. Długość możliwych ciągów pasujących do wzorca look-behind nie może być nieograniczona (brak operatorów * lub +). exp (?ismwx-ismwx: ... ) des Ustawienia flag. Ewaluuje wyrażenie w nawiasach z włączonymi lub wyłączonymi (-) określonymi flagami. Flagi są zdefiniowane w Opcjach Flag. exp (?ismwx-ismwx) des Ustawienia flag. Zmienia ustawienia flag. Zmiany dotyczą części wzorca następującej po ustawieniu. Na przykład (?i) zmienia na dopasowanie niewrażliwe na wielkość liter. Flagi są zdefiniowane w Opcjach Flag. ================================================ FILE: RegEx+/zh-Hans.lproj/CheatSheet.plist ================================================ metacharacters exp \a des 匹配一个警报声, \u0007 exp \A des 匹配输入的开端。和 ^ 不同的是,\A 不会匹配换行后的开端。 exp \b, 在 [集合] 外 des 当前位置是词语边界时匹配。词语边界是指词语 (\w) 和非词语 (\W) 字符之间的界限,这种边界会忽略连字符。为了更好地使用词语边界,请打开『使用 unicode 词语边界』选项。 exp \b, 在 [集合] 内 des 匹配一个退格, \u0008. exp \B des 当前位置不是词语边界时匹配。 exp \cX des 匹配一个 control-X 字符。 exp \d des 匹配任何符合 Unicode 通用类别的数字字符。 exp \D des 匹配任何不为数字的字符。 exp \e des 匹配一个 ESCAPE, \u001B。 exp \E des 终止一个 \Q ... \E 元字符引用序列。 exp \f des 匹配换页符, \u000C。 exp \G des 当前位置是上一个匹配的结束位置时匹配。 exp \n des 匹配一个换行符, \u000A。 exp \N{UNICODE CHARACTER NAME} des 匹配 Unicode 命名字符。 exp \p{UNICODE PROPERTY NAME} des 匹配任意有指定 Unicode 属性的字符。 exp \P{UNICODE PROPERTY NAME} des 匹配任意不包括指定 Unicode 属性的字符。 exp \Q des 开始元字符引用序列,直到遇到 \E。 exp \r des 匹配一个 <CR> 回车, \u000D. exp \s des 匹配一个空白字符,它的定义是 [\t\n\f\r\p{Z}]。 exp \S des 匹配一个非空白字符。 exp \t des 匹配一个横向 TAB 缩进,\u0009。 exp \uhhhh des 匹配 hex 值为 hhhh 的字符串。 exp \Uhhhhhhhh des 匹配 hex 值为 hhhhhhhh 的字符串。尽管最大的 Unicode 值是 \U0010ffff,但还是要提供完整的八个十六进制数字。 exp \w des 匹配一个词语字符,它的字符定义是 [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}]。 exp \W des 匹配一个非词语字符。 exp \x{hhhh} des 匹配 hex 值为 hhhh 的字符串,可以是一到六位的十六进制数字。 exp \xhh des 匹配 hex 值为 hh 的两位十六进制数字对应的字符。 exp \X des 匹配一个字符簇(Grapheme Cluster)。 exp \Z des 当前位置是输入的结尾时匹配,但仅匹配到行尾标识前,如果有的话。 exp \z des 当前位置是输入的结尾时匹配。 exp \n des 换行引用。无论第 n 个捕获组匹配到什么,n 必须是一个 ≥ 1 并 ≤ 总捕获数的数字。 exp \0ooo des 匹配一个八进制字符。ooo 为一个到三个八进制数字。0377 是有效的最大八进制字符。前置的零是必须的;它将八进制常量与之后的引用值区分开来。 exp [pattern] des 匹配任何在这个方括号内的字符。 exp . des 匹配所有字符。 exp ^ des 匹配行首。 exp $ des 匹配行尾。 exp \ des 转义后续字符。有些字符必须转义后才能被当成字面量使用,如 * ? + [ ( ) { } ^ $ | \ . / operators exp | des 可选匹配。A|B 表示是 A 或者是 B 的一个字符。 exp * des 匹配零次或多次,优先匹配多次。 exp + des 匹配一次或多次,优先匹配多次。 exp ? des 匹配零到一次,优先匹配一次。 exp {n} des 明确匹配 n 次。 exp {n,} des 匹配最少 n 次,最多无限次。 exp {n,m} des 匹配 n 到 m 次,优先匹配尽可能多的次数,但不会超过 m 次。 exp *? des 匹配零次或多次,尽可能少匹配。 exp +? des 匹配一次或多次,尽可能少匹配。 exp ?? des 匹配零次或一次,尽可能少匹配。 exp {n}? des 明确匹配 n 次。 exp {n,}? des 匹配至少 n 次,但不超过整体模式匹配的要求。 exp {n,m}? des 匹配 n 到 m 次,尽可能少匹配,但不少于 n 次。 exp *+ des 匹配零次或以上。第一次遇到时,尽量多匹配,即使整体匹配失败,也不要用较少的次数重试(独占模式)。 exp ++ des 匹配一次或多次,独占模式。 exp ?+ des 匹配零次或一次,独占模式。 exp {n}+ des 明确匹配 n 次。 exp {n,}+ des 匹配至少 n 次,独占模式,匹配得越多越好。 exp {n,m}+ des 匹配 n 到 m 次,独占模式,匹配得越多越好。 exp (...) des 圆括号匹配。整个表达式匹配结束后,圆括号内的子表达式所捕获到的区域能从匹配信息中单独获得。 exp (?:...) des 非捕获圆括号。匹配括号内冒号之后的表达式但不做捕获,无法从匹配信息中获得,某些时候比捕获圆括号更加高效。 exp (?>...) des 原子匹配圆括号。括号子表达式的第一次匹配是唯一一次尝试;如果没有导致整体模式匹配,则搜索匹配回到 "(?>" 之前的位置。 exp (?# ... ) des 自由格式的注释 (?# 注释 ). exp (?= ... ) des 向后断言。当括号内的表达式在当前输入位置匹配时生效,但不移动输入位置。 exp (?! ... ) des 否定的向后断言。当括号内的表达式在当前输入位置不匹配时生效。不移动输入位置。 exp (?<= ... ) des 向前断言。如果括号内的表达式与当前输入位置之前的文本相匹配,且匹配的最后一个字符是当前位置之前的输入字符,则生效,且不改变输入位置。向前断言模式匹配的字符串长度不能是无限制的(没有 * 或 + 运算符)。 exp (?<! ... ) des 否定的向前断言。如果括号内的表达式与当前输入位置之前的文本不匹配,则生效,匹配的最后一个字符是当前位置之前的输入字符。不改变输入位置。否定的向前断言模式匹配的字符串长度不能是无限制的(没有 * 或 + 运算符)。 exp (?ismwx-ismwx: ... ) des 标志设置。在启用或禁用指定的标志时评估括号内的表达式。这些标志在标志选项中定义。例如,(?i:Aa) 启用不区分大小写,(?-i:Aa) 禁用不区分大小写。 exp (?ismwx-ismwx) des 标志设置。更改标志设置。更改适用于设置后的模式部分。例如,(?i) 改变为不区分大小写的匹配。标志在标志选项中定义。 ================================================ FILE: RegEx+/zh-Hant.lproj/CheatSheet.plist ================================================ metacharacters exp \a des 匹配一個警報聲, \u0007 exp \A des 匹配輸入的開端。和 ^ 不同的是,\A 不會匹配換行後的開端。 exp \b, 在 [集合] 外 des 當前位置是詞語邊界時匹配。詞語邊界是指詞語 (\w) 和非詞語 (\W) 字符之間的界限,這種邊界會忽略連字符。為了更好地使用詞語邊界,請打開『使用 unicode 詞語邊界』選項。 exp \b, 在 [集合] 內 des 匹配一個退格, \u0008. exp \B des 當前位置不是詞語邊界時匹配。 exp \cX des 匹配一個 control-X 字符。 exp \d des 匹配任何符合 Unicode 通用類別的數字字符。 exp \D des 匹配任何不為數字的字符。 exp \e des 匹配一個 ESCAPE, \u001B。 exp \E des 終止一個 \Q ... \E 元字符引用序列。 exp \f des 匹配換頁符, \u000C。 exp \G des 當前位置是上一個匹配的結束位置時匹配。 exp \n des 匹配一個換行符, \u000A。 exp \N{UNICODE CHARACTER NAME} des 匹配 Unicode 命名字符。 exp \p{UNICODE PROPERTY NAME} des 匹配任意有指定 Unicode 屬性的字符。 exp \P{UNICODE PROPERTY NAME} des 匹配任意不包括指定 Unicode 屬性的字符。 exp \Q des 開始元字符引用序列,直到遇到 \E。 exp \r des 匹配一個 <CR> 回車, \u000D. exp \s des 匹配一個空白字符,它的定義是 [\t\n\f\r\p{Z}]。 exp \S des 匹配一個非空白字符。 exp \t des 匹配一個橫向 TAB 縮進,\u0009。 exp \uhhhh des 匹配 hex 值為 hhhh 的字符串。 exp \Uhhhhhhhh des 匹配 hex 值為 hhhhhhhh 的字符串。儘管最大的 Unicode 值是 \U0010ffff,但還是要提供完整的八個十六進制數字。 exp \w des 匹配一個詞語字符,它的字符定義是 [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}]。 exp \W des 匹配一個非詞語字符。 exp \x{hhhh} des 匹配 hex 值為 hhhh 的字符串,可以是一到六位的十六進制數字。 exp \xhh des 匹配 hex 值為 hh 的兩位十六進制數字對應的字符。 exp \X des 匹配一個字符簇(Grapheme Cluster)。 exp \Z des 當前位置是輸入的結尾時匹配,但僅匹配到行尾標識前,如果有的話。 exp \z des 當前位置是輸入的結尾時匹配。 exp \n des 換行引用。無論第 n 個捕獲組匹配到什麼,n 必須是一個 ≥ 1 並 ≤ 總捕獲數的數字。 exp \0ooo des 匹配一個八進制字符。ooo 為一個到三個八進制數字。0377 是有效的最大八進制字符。前置的零是必須的;它將八進制常量與之後的引用值區分開來。 exp [pattern] des 匹配任何在這個方括號內的字符。 exp . des 匹配所有字符。 exp ^ des 匹配行首。 exp $ des 匹配行尾。 exp \ des 轉義後續字符。有些字符必須轉義後才能被當成字面量使用,如 * ? + [ ( ) { } ^ $ | \ . / operators exp | des 可選匹配。A|B 表示是 A 或者是 B 的一個字符。 exp * des 匹配零次或多次,優先匹配多次。 exp + des 匹配一次或多次,優先匹配多次。 exp ? des 匹配零到一次,優先匹配一次。 exp {n} des 明確匹配 n 次。 exp {n,} des 匹配最少 n 次,最多無限次。 exp {n,m} des 匹配 n 到 m 次,優先匹配儘可能多的次數,但不會超過 m 次。 exp *? des 匹配零次或多次,儘可能少匹配。 exp +? des 匹配一次或多次,儘可能少匹配。 exp ?? des 匹配零次或一次,儘可能少匹配。 exp {n}? des 明確匹配 n 次。 exp {n,}? des 匹配至少 n 次,但不超過整體模式匹配的要求。 exp {n,m}? des 匹配 n 到 m 次,儘可能少匹配,但不少於 n 次。 exp *+ des 匹配零次或以上。第一次遇到時,儘量多匹配,即使整體匹配失敗,也不要用較少的次數重試(獨佔模式)。 exp ++ des 匹配一次或多次,獨佔模式。 exp ?+ des 匹配零次或一次,獨佔模式。 exp {n}+ des 明確匹配 n 次。 exp {n,}+ des 匹配至少 n 次,獨佔模式,匹配得越多越好。 exp {n,m}+ des 匹配 n 到 m 次,獨佔模式,匹配得越多越好。 exp (...) des 圓括號匹配。整個表達式匹配結束後,圓括號內的子表達式所捕獲到的區域能從匹配信息中單獨獲得。 exp (?:...) des 非捕獲圓括號。匹配括號內冒號之後的表達式但不做捕獲,無法從匹配信息中獲得,某些時候比捕獲圓括號更加高效。 exp (?>...) des 原子匹配圓括號。括號子表達式的第一次匹配是唯一一次嘗試;如果沒有導致整體模式匹配,則搜索匹配回到 "(?>" 之前的位置。 exp (?# ... ) des 自由格式的註釋 (?# 註釋 ). exp (?= ... ) des 向後斷言。當括號內的表達式在當前輸入位置匹配時生效,但不移動輸入位置。 exp (?! ... ) des 否定的向後斷言。當括號內的表達式在當前輸入位置不匹配時生效。不移動輸入位置。 exp (?<= ... ) des 向前斷言。如果括號內的表達式與當前輸入位置之前的文本相匹配,且匹配的最後一個字符是當前位置之前的輸入字符,則生效,且不改變輸入位置。向前斷言模式匹配的字符串長度不能是無限制的(沒有 * 或 + 運算符)。 exp (?<! ... ) des 否定的向前斷言。如果括號內的表達式與當前輸入位置之前的文本不匹配,則生效,匹配的最後一個字符是當前位置之前的輸入字符。不改變輸入位置。否定的向前斷言模式匹配的字符串長度不能是無限制的(沒有 * 或 + 運算符)。 exp (?ismwx-ismwx: ... ) des 標誌設置。在啟用或禁用指定的標誌時評估括號內的表達式。這些標誌在標誌選項中定義。例如,(?i:Aa) 啟用不區分大小寫,(?-i:Aa) 禁用不區分大小寫。 exp (?ismwx-ismwx) des 標誌設置。更改標誌設置。更改適用於設置後的模式部分。例如,(?i) 改變為不區分大小寫的匹配。標誌在標誌選項中定義。 ================================================ FILE: RegEx+.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 2B98B5427488929CF4B9104F /* RegExFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73E3E4B7BCD55ACE7E2C6A55 /* RegExFlowView.swift */; }; D709006C245DB72C00326016 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D709006B245DB72C00326016 /* CloudKit.framework */; }; D7090092245DB87C00326016 /* RegEx.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D7090090245DB87C00326016 /* RegEx.xcdatamodeld */; }; D71871E6245EE9AC001F1E4E /* RegExFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71871E5245EE9AC001F1E4E /* RegExFetch.swift */; }; D71F8F10246F8E5300A11283 /* ActivityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71F8F0F246F8E5300A11283 /* ActivityViewController.swift */; }; D721780B2451776000F17390 /* RegExTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D721780A2451776000F17390 /* RegExTextView.swift */; }; D731F64B245EF4C500EEE17F /* RegEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = D731F649245EF4C500EEE17F /* RegEx.swift */; }; D731F64E245EF77600EEE17F /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D731F64D245EF77600EEE17F /* DataManager.swift */; }; D731F650245F053A00EEE17F /* LibraryView+Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = D731F64F245F053A00EEE17F /* LibraryView+Data.swift */; }; D731F652245F0B9A00EEE17F /* LibraryItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D731F651245F0B9A00EEE17F /* LibraryItemView.swift */; }; D731F6562460F2BA00EEE17F /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D731F6552460F2BA00EEE17F /* SafariView.swift */; }; D74BF66D247A3C0A0032EE35 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74BF66C247A3C0A0032EE35 /* AboutView.swift */; }; D75902AD2513C6300032013F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75902AC2513C6300032013F /* AppDelegate.swift */; }; D75902B02513C6620032013F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75902AF2513C6620032013F /* SceneDelegate.swift */; }; D7732029245D1877003C0CF8 /* RegExSyntaxHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7732028245D1877003C0CF8 /* RegExSyntaxHighlighter.swift */; }; D7732033245D3388003C0CF8 /* String+NSRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7732032245D3388003C0CF8 /* String+NSRange.swift */; }; D7732035245D3456003C0CF8 /* MatchesTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7732034245D3456003C0CF8 /* MatchesTextView.swift */; }; D77C7970247EBC6D00D3B1B2 /* CheatSheet.plist in Resources */ = {isa = PBXBuildFile; fileRef = D77C7972247EBC6D00D3B1B2 /* CheatSheet.plist */; }; D78E45502E4795E5002ACA56 /* AppAboutView in Frameworks */ = {isa = PBXBuildFile; productRef = D78E454F2E4795E5002ACA56 /* AppAboutView */; }; D78E45BC2E48352B002ACA56 /* ShortcutKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78E45BB2E483529002ACA56 /* ShortcutKeys.swift */; }; D7A925DC2B81057E0023E8EA /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = D7A925DB2B81057E0023E8EA /* Localizable.xcstrings */; }; D7BB73F9244F47E400343AC5 /* EditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7BB73F8244F47E400343AC5 /* EditorView.swift */; }; D7CCA7D9244E010D00D81C74 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CCA7D8244E010D00D81C74 /* HomeView.swift */; }; D7CCA7DB244E010F00D81C74 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D7CCA7DA244E010F00D81C74 /* Assets.xcassets */; }; D7CCA7DE244E010F00D81C74 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D7CCA7DD244E010F00D81C74 /* Preview Assets.xcassets */; }; D7CCA7E1244E010F00D81C74 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D7CCA7DF244E010F00D81C74 /* LaunchScreen.storyboard */; }; D7CCA7EC244E038E00D81C74 /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CCA7EB244E038E00D81C74 /* LibraryView.swift */; }; D7CCA7EF244E041000D81C74 /* CheatSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7CCA7EE244E041000D81C74 /* CheatSheetView.swift */; }; D7DC514A2529645F00A495E2 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DC51492529645F00A495E2 /* SearchView.swift */; }; D7EF735A2453E7FB005974F0 /* EditorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EF73592453E7FB005974F0 /* EditorViewModel.swift */; }; D7F100012F100001002ACA56 /* _RegexParser in Frameworks */ = {isa = PBXBuildFile; productRef = D7F100032F100001002ACA56 /* _RegexParser */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 73E3E4B7BCD55ACE7E2C6A55 /* RegExFlowView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RegExFlowView.swift; sourceTree = ""; }; D709006B245DB72C00326016 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; D7090091245DB87C00326016 /* RegEx.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = RegEx.xcdatamodel; sourceTree = ""; }; D7129F942567FDFF0031D31B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; D71871E5245EE9AC001F1E4E /* RegExFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegExFetch.swift; sourceTree = ""; }; D71F8F0F246F8E5300A11283 /* ActivityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityViewController.swift; sourceTree = ""; }; D721780A2451776000F17390 /* RegExTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegExTextView.swift; sourceTree = ""; }; D731F649245EF4C500EEE17F /* RegEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegEx.swift; sourceTree = ""; }; D731F64D245EF77600EEE17F /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = ""; }; D731F64F245F053A00EEE17F /* LibraryView+Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LibraryView+Data.swift"; sourceTree = ""; }; D731F651245F0B9A00EEE17F /* LibraryItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryItemView.swift; sourceTree = ""; }; D731F6552460F2BA00EEE17F /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; D74BF66C247A3C0A0032EE35 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; D75902AC2513C6300032013F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; D75902AF2513C6620032013F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; D7732028245D1877003C0CF8 /* RegExSyntaxHighlighter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegExSyntaxHighlighter.swift; sourceTree = ""; }; D7732032245D3388003C0CF8 /* String+NSRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+NSRange.swift"; sourceTree = ""; }; D7732034245D3456003C0CF8 /* MatchesTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchesTextView.swift; sourceTree = ""; }; D77C7971247EBC6D00D3B1B2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = en; path = en.lproj/CheatSheet.plist; sourceTree = ""; }; D77C7973247EBC7300D3B1B2 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "zh-Hans"; path = "zh-Hans.lproj/CheatSheet.plist"; sourceTree = ""; }; D78E45B82E47A2B0002ACA56 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = ja; path = ja.lproj/CheatSheet.plist; sourceTree = ""; }; D78E45B92E47A2D2002ACA56 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = de; path = de.lproj/CheatSheet.plist; sourceTree = ""; }; D78E45BA2E47A2DD002ACA56 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = es; path = es.lproj/CheatSheet.plist; sourceTree = ""; }; D78E45BB2E483529002ACA56 /* ShortcutKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutKeys.swift; sourceTree = ""; }; D78E45BD2E47A2E8002ACA56 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = fr; path = fr.lproj/CheatSheet.plist; sourceTree = ""; }; D78E45BE2E47A2F3002ACA56 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = it; path = it.lproj/CheatSheet.plist; sourceTree = ""; }; D78E45BF2E47A300002ACA56 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = ko; path = ko.lproj/CheatSheet.plist; sourceTree = ""; }; D78E45C02E47A310002ACA56 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = nl; path = nl.lproj/CheatSheet.plist; sourceTree = ""; }; D78E45C12E47A320002ACA56 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = pl; path = pl.lproj/CheatSheet.plist; sourceTree = ""; }; D7A925DB2B81057E0023E8EA /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; D7BB73F8244F47E400343AC5 /* EditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorView.swift; sourceTree = ""; }; D7CCA7D1244E010D00D81C74 /* RegEx.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RegEx.app; sourceTree = BUILT_PRODUCTS_DIR; }; D7CCA7D8244E010D00D81C74 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; D7CCA7DA244E010F00D81C74 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; D7CCA7DD244E010F00D81C74 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; D7CCA7E2244E010F00D81C74 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D7CCA7E8244E011C00D81C74 /* RegEx+.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "RegEx+.entitlements"; sourceTree = ""; }; D7CCA7EB244E038E00D81C74 /* LibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = ""; }; D7CCA7EE244E041000D81C74 /* CheatSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheatSheetView.swift; sourceTree = ""; }; D7D0E96F249F4FA800F6B9DF /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "zh-Hant"; path = "zh-Hant.lproj/CheatSheet.plist"; sourceTree = ""; }; D7DC51492529645F00A495E2 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; D7E9174A246C47D400F2BE17 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; D7EF73592453E7FB005974F0 /* EditorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorViewModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ D7CCA7CE244E010D00D81C74 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( D709006C245DB72C00326016 /* CloudKit.framework in Frameworks */, D78E45502E4795E5002ACA56 /* AppAboutView in Frameworks */, D7F100012F100001002ACA56 /* _RegexParser in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ D709006A245DB72C00326016 /* Frameworks */ = { isa = PBXGroup; children = ( D709006B245DB72C00326016 /* CloudKit.framework */, ); name = Frameworks; sourceTree = ""; }; D7090093245DC43800326016 /* CoreData+CloudKit */ = { isa = PBXGroup; children = ( D731F64D245EF77600EEE17F /* DataManager.swift */, D731F649245EF4C500EEE17F /* RegEx.swift */, D71871E5245EE9AC001F1E4E /* RegExFetch.swift */, ); path = "CoreData+CloudKit"; sourceTree = ""; }; D7090094245DC46200326016 /* Editor */ = { isa = PBXGroup; children = ( D7BB73F8244F47E400343AC5 /* EditorView.swift */, D7EF73592453E7FB005974F0 /* EditorViewModel.swift */, 73E3E4B7BCD55ACE7E2C6A55 /* RegExFlowView.swift */, ); path = Editor; sourceTree = ""; }; D74BF66E247A3C0E0032EE35 /* About */ = { isa = PBXGroup; children = ( D74BF66C247A3C0A0032EE35 /* AboutView.swift */, ); path = About; sourceTree = ""; }; D761DDC5245DC4DF00FE0678 /* Library */ = { isa = PBXGroup; children = ( D7CCA7EB244E038E00D81C74 /* LibraryView.swift */, D731F651245F0B9A00EEE17F /* LibraryItemView.swift */, D731F64F245F053A00EEE17F /* LibraryView+Data.swift */, ); path = Library; sourceTree = ""; }; D761DDC6245DC52A00FE0678 /* CheatSheet */ = { isa = PBXGroup; children = ( D7CCA7EE244E041000D81C74 /* CheatSheetView.swift */, ); path = CheatSheet; sourceTree = ""; }; D7732027245D185C003C0CF8 /* RegExTextView */ = { isa = PBXGroup; children = ( D78E45BB2E483529002ACA56 /* ShortcutKeys.swift */, D721780A2451776000F17390 /* RegExTextView.swift */, D7732028245D1877003C0CF8 /* RegExSyntaxHighlighter.swift */, D7732034245D3456003C0CF8 /* MatchesTextView.swift */, D7732032245D3388003C0CF8 /* String+NSRange.swift */, ); path = RegExTextView; sourceTree = ""; }; D7CCA7C8244E010D00D81C74 = { isa = PBXGroup; children = ( D7CCA7D3244E010D00D81C74 /* RegEx+ */, D7CCA7D2244E010D00D81C74 /* Products */, D709006A245DB72C00326016 /* Frameworks */, ); sourceTree = ""; }; D7CCA7D2244E010D00D81C74 /* Products */ = { isa = PBXGroup; children = ( D7CCA7D1244E010D00D81C74 /* RegEx.app */, ); name = Products; sourceTree = ""; }; D7CCA7D3244E010D00D81C74 /* RegEx+ */ = { isa = PBXGroup; children = ( D7E9174A246C47D400F2BE17 /* README.md */, D7CCA7E8244E011C00D81C74 /* RegEx+.entitlements */, D75902AC2513C6300032013F /* AppDelegate.swift */, D75902AF2513C6620032013F /* SceneDelegate.swift */, D7CCA7D8244E010D00D81C74 /* HomeView.swift */, D7090093245DC43800326016 /* CoreData+CloudKit */, D761DDC5245DC4DF00FE0678 /* Library */, D7090094245DC46200326016 /* Editor */, D74BF66E247A3C0E0032EE35 /* About */, D761DDC6245DC52A00FE0678 /* CheatSheet */, D7CCA7ED244E039D00D81C74 /* Views */, D7CCA7DA244E010F00D81C74 /* Assets.xcassets */, D7CCA7DF244E010F00D81C74 /* LaunchScreen.storyboard */, D7CCA7E2244E010F00D81C74 /* Info.plist */, D7090090245DB87C00326016 /* RegEx.xcdatamodeld */, D7CCA7DC244E010F00D81C74 /* Preview Content */, D77C7972247EBC6D00D3B1B2 /* CheatSheet.plist */, D7A925DB2B81057E0023E8EA /* Localizable.xcstrings */, ); path = "RegEx+"; sourceTree = ""; }; D7CCA7DC244E010F00D81C74 /* Preview Content */ = { isa = PBXGroup; children = ( D7CCA7DD244E010F00D81C74 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; D7CCA7ED244E039D00D81C74 /* Views */ = { isa = PBXGroup; children = ( D7732027245D185C003C0CF8 /* RegExTextView */, D731F6552460F2BA00EEE17F /* SafariView.swift */, D71F8F0F246F8E5300A11283 /* ActivityViewController.swift */, D7DC51492529645F00A495E2 /* SearchView.swift */, ); path = Views; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ D7CCA7D0244E010D00D81C74 /* RegEx+ */ = { isa = PBXNativeTarget; buildConfigurationList = D7CCA7E5244E010F00D81C74 /* Build configuration list for PBXNativeTarget "RegEx+" */; buildPhases = ( D734CBE224AA41E900C1F385 /* SwiftLint */, D7CCA7CD244E010D00D81C74 /* Sources */, D7CCA7CE244E010D00D81C74 /* Frameworks */, D7CCA7CF244E010D00D81C74 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "RegEx+"; packageProductDependencies = ( D78E454F2E4795E5002ACA56 /* AppAboutView */, D7F100032F100001002ACA56 /* _RegexParser */, ); productName = RegExPro; productReference = D7CCA7D1244E010D00D81C74 /* RegEx.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ D7CCA7C9244E010D00D81C74 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1150; LastUpgradeCheck = 1640; ORGANIZATIONNAME = Lex.sh; TargetAttributes = { D7CCA7D0244E010D00D81C74 = { CreatedOnToolsVersion = 11.4.1; }; }; }; buildConfigurationList = D7CCA7CC244E010D00D81C74 /* Build configuration list for PBXProject "RegEx+" */; compatibilityVersion = "Xcode 11.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, "zh-Hans", "zh-Hant", Base, ja, de, es, fr, it, ko, nl, pl, ); mainGroup = D7CCA7C8244E010D00D81C74; packageReferences = ( D78E454E2E4795E5002ACA56 /* XCRemoteSwiftPackageReference "AppAboutView" */, D7F100022F100001002ACA56 /* XCRemoteSwiftPackageReference "swift-experimental-string-processing" */, ); productRefGroup = D7CCA7D2244E010D00D81C74 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( D7CCA7D0244E010D00D81C74 /* RegEx+ */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ D7CCA7CF244E010D00D81C74 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( D7CCA7E1244E010F00D81C74 /* LaunchScreen.storyboard in Resources */, D7A925DC2B81057E0023E8EA /* Localizable.xcstrings in Resources */, D77C7970247EBC6D00D3B1B2 /* CheatSheet.plist in Resources */, D7CCA7DE244E010F00D81C74 /* Preview Assets.xcassets in Resources */, D7CCA7DB244E010F00D81C74 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ D734CBE224AA41E900C1F385 /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = SwiftLint; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "export PATH=/opt/homebrew/bin/:$PATH\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ D7CCA7CD244E010D00D81C74 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( D731F64B245EF4C500EEE17F /* RegEx.swift in Sources */, D7CCA7EC244E038E00D81C74 /* LibraryView.swift in Sources */, D7732035245D3456003C0CF8 /* MatchesTextView.swift in Sources */, D71871E6245EE9AC001F1E4E /* RegExFetch.swift in Sources */, D71F8F10246F8E5300A11283 /* ActivityViewController.swift in Sources */, D731F6562460F2BA00EEE17F /* SafariView.swift in Sources */, D7732033245D3388003C0CF8 /* String+NSRange.swift in Sources */, D75902B02513C6620032013F /* SceneDelegate.swift in Sources */, D7CCA7EF244E041000D81C74 /* CheatSheetView.swift in Sources */, D721780B2451776000F17390 /* RegExTextView.swift in Sources */, D7DC514A2529645F00A495E2 /* SearchView.swift in Sources */, D7090092245DB87C00326016 /* RegEx.xcdatamodeld in Sources */, D731F64E245EF77600EEE17F /* DataManager.swift in Sources */, D78E45BC2E48352B002ACA56 /* ShortcutKeys.swift in Sources */, D7EF735A2453E7FB005974F0 /* EditorViewModel.swift in Sources */, D731F652245F0B9A00EEE17F /* LibraryItemView.swift in Sources */, D7BB73F9244F47E400343AC5 /* EditorView.swift in Sources */, D74BF66D247A3C0A0032EE35 /* AboutView.swift in Sources */, D7732029245D1877003C0CF8 /* RegExSyntaxHighlighter.swift in Sources */, D75902AD2513C6300032013F /* AppDelegate.swift in Sources */, D731F650245F053A00EEE17F /* LibraryView+Data.swift in Sources */, D7CCA7D9244E010D00D81C74 /* HomeView.swift in Sources */, 2B98B5427488929CF4B9104F /* RegExFlowView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ D77C7972247EBC6D00D3B1B2 /* CheatSheet.plist */ = { isa = PBXVariantGroup; children = ( D77C7971247EBC6D00D3B1B2 /* en */, D77C7973247EBC7300D3B1B2 /* zh-Hans */, D7D0E96F249F4FA800F6B9DF /* zh-Hant */, D78E45B82E47A2B0002ACA56 /* ja */, D78E45B92E47A2D2002ACA56 /* de */, D78E45BA2E47A2DD002ACA56 /* es */, D78E45BD2E47A2E8002ACA56 /* fr */, D78E45BE2E47A2F3002ACA56 /* it */, D78E45BF2E47A300002ACA56 /* ko */, D78E45C02E47A310002ACA56 /* nl */, D78E45C12E47A320002ACA56 /* pl */, ); name = CheatSheet.plist; sourceTree = ""; }; D7CCA7DF244E010F00D81C74 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( D7129F942567FDFF0031D31B /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ D7CCA7E3244E010F00D81C74 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 5SKD83S59G; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.6; MACOSX_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; D7CCA7E4244E010F00D81C74 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 5SKD83S59G; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.6; MACOSX_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; }; name = Release; }; D7CCA7E6244E010F00D81C74 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "RegEx+/RegEx+.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 22; DEVELOPMENT_ASSET_PATHS = "RegEx+/Preview\\ Content/Preview\\ Assets.xcassets"; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "$(SRCROOT)/RegEx+/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 0.9; PRODUCT_BUNDLE_IDENTIFIER = sh.lex.RegExCatalyst; PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)App"; PRODUCT_NAME = RegEx; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; D7CCA7E7244E010F00D81C74 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "RegEx+/RegEx+.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 22; DEVELOPMENT_ASSET_PATHS = "RegEx+/Preview\\ Content/Preview\\ Assets.xcassets"; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "$(SRCROOT)/RegEx+/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 0.9; PRODUCT_BUNDLE_IDENTIFIER = sh.lex.RegExCatalyst; PRODUCT_MODULE_NAME = "$(PRODUCT_NAME:c99extidentifier)App"; PRODUCT_NAME = RegEx; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ D7CCA7CC244E010D00D81C74 /* Build configuration list for PBXProject "RegEx+" */ = { isa = XCConfigurationList; buildConfigurations = ( D7CCA7E3244E010F00D81C74 /* Debug */, D7CCA7E4244E010F00D81C74 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; D7CCA7E5244E010F00D81C74 /* Build configuration list for PBXNativeTarget "RegEx+" */ = { isa = XCConfigurationList; buildConfigurations = ( D7CCA7E6244E010F00D81C74 /* Debug */, D7CCA7E7244E010F00D81C74 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ D78E454E2E4795E5002ACA56 /* XCRemoteSwiftPackageReference "AppAboutView" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lexrus/AppAboutView"; requirement = { kind = upToNextMajorVersion; minimumVersion = 0.0.4; }; }; D7F100022F100001002ACA56 /* XCRemoteSwiftPackageReference "swift-experimental-string-processing" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/swiftlang/swift-experimental-string-processing"; requirement = { kind = revision; revision = e3f7b258f75a42b78624972131a0175cb0b662da; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ D78E454F2E4795E5002ACA56 /* AppAboutView */ = { isa = XCSwiftPackageProductDependency; package = D78E454E2E4795E5002ACA56 /* XCRemoteSwiftPackageReference "AppAboutView" */; productName = AppAboutView; }; D7F100032F100001002ACA56 /* _RegexParser */ = { isa = XCSwiftPackageProductDependency; package = D7F100022F100001002ACA56 /* XCRemoteSwiftPackageReference "swift-experimental-string-processing" */; productName = _RegexParser; }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ D7090090245DB87C00326016 /* RegEx.xcdatamodeld */ = { isa = XCVersionGroup; children = ( D7090091245DB87C00326016 /* RegEx.xcdatamodel */, ); currentVersion = D7090091245DB87C00326016 /* RegEx.xcdatamodel */; path = RegEx.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; }; /* End XCVersionGroup section */ }; rootObject = D7CCA7C9244E010D00D81C74 /* Project object */; } ================================================ FILE: fastlane/Deliverfile ================================================ # The Deliverfile allows you to store various App Store Connect metadata # For more information, check out the docs # https://docs.fastlane.tools/actions/deliver/ ================================================ FILE: fastlane/Fastfile ================================================ # This file contains the fastlane.tools configuration # You can find the documentation at https://docs.fastlane.tools # # For a list of all available actions, check out # # https://docs.fastlane.tools/actions # # For a list of all available plugins, check out # # https://docs.fastlane.tools/plugins/available-plugins # # Uncomment the line if you want fastlane to automatically update itself # update_fastlane default_platform(:ios) platform :ios do desc "Push a new release build to the App Store" lane :release do increment_build_number(xcodeproj: "RegEx+.xcodeproj") build_app(scheme: "RegEx+") upload_to_app_store end end ================================================ FILE: fastlane/metadata/copyright.txt ================================================ 2025 Lex.sh ================================================ FILE: fastlane/metadata/de-DE/apple_tv_privacy_policy.txt ================================================ ================================================ FILE: fastlane/metadata/de-DE/description.txt ================================================ Entfesseln Sie die Macht der regulären Ausdrücke mit RegEx+! Von Entwicklern für Entwickler konzipiert, ist RegEx+ Ihr unverzichtbares Werkzeug zur Beherrschung regulärer Ausdrücke auf macOS und iOS. Tauchen Sie ein in eine nahtlose Erfahrung, bei der Ihre Daten mühelos über CloudKit zwischen Geräten synchronisiert werden, sodass Sie Ihre Arbeit haben, wo und wann Sie sie brauchen. Ob Anfänger oder erfahrener Profi, unser umfassendes Cheatsheet entmystifiziert Metazeichen und Operatoren und befähigt Sie, komplexe Muster mühelos zu entschlüsseln und zu konstruieren. Verbessern Sie Ihr Coding-Spiel mit RegEx+ und transformieren Sie die Art, wie Sie mit Text, Daten und mehr umgehen! ================================================ FILE: fastlane/metadata/de-DE/keywords.txt ================================================ regexp,reguläre,ausdrücke,entwickler,programmieren,code,editor,pattern,syntax,ios,mac,software,tool ================================================ FILE: fastlane/metadata/de-DE/marketing_url.txt ================================================ ================================================ FILE: fastlane/metadata/de-DE/name.txt ================================================ RegEx+ ================================================ FILE: fastlane/metadata/de-DE/privacy_url.txt ================================================ https://lex.sh/regexplus/privacypolicy/ ================================================ FILE: fastlane/metadata/de-DE/promotional_text.txt ================================================ Teste, debugge und speichere Regex-Muster schneller auf iPhone, iPad und Mac. Live-Matching, Spickzettel und CloudKit-Synchronisation in einem fokussierten Tool. ================================================ FILE: fastlane/metadata/de-DE/release_notes.txt ================================================ Neues in RegEx+: • Unterstützung für Französisch, Italienisch, Koreanisch, Niederländisch und Polnisch hinzugefügt. • Leistungsoptimierungen und Stabilitätsverbesserungen. ================================================ FILE: fastlane/metadata/de-DE/subtitle.txt ================================================ Regex Spickzettel & Cloud-Sync ================================================ FILE: fastlane/metadata/de-DE/support_url.txt ================================================ https://x.com/lexrus ================================================ FILE: fastlane/metadata/en-US/apple_tv_privacy_policy.txt ================================================ ================================================ FILE: fastlane/metadata/en-US/description.txt ================================================ Unleash the Power of Regular Expressions with RegEx+! Designed for developers by developers, RegEx+ is your go-to tool for mastering regular expressions on both macOS and iOS. Dive into a seamless experience as your data syncs effortlessly across devices via CloudKit, ensuring you have your work where you need it, when you need it. Whether you're a beginner or a seasoned pro, our comprehensive cheatsheet demystifies metacharacters and operators, empowering you to decode and construct complex patterns with ease. Elevate your coding game with RegEx+ and transform the way you handle text, data, and beyond! ================================================ FILE: fastlane/metadata/en-US/keywords.txt ================================================ regexp,tester,builder,match,replace,capture,group,validator,pattern,swift,nsregularexpression,editor ================================================ FILE: fastlane/metadata/en-US/marketing_url.txt ================================================ ================================================ FILE: fastlane/metadata/en-US/name.txt ================================================ RegEx+ ================================================ FILE: fastlane/metadata/en-US/privacy_url.txt ================================================ https://lex.sh/regexplus/privacypolicy/ ================================================ FILE: fastlane/metadata/en-US/promotional_text.txt ================================================ Test, debug, and save regex patterns faster on iPhone, iPad, and Mac. Live matching, cheat sheet, and CloudKit sync in one focused tool. ================================================ FILE: fastlane/metadata/en-US/release_notes.txt ================================================ What's New in RegEx+: • Added French, Italian, Korean, Dutch and Polish language support. • Performance optimizations and stability improvements. ================================================ FILE: fastlane/metadata/en-US/subtitle.txt ================================================ Regex Cheat Sheet + Cloud Sync ================================================ FILE: fastlane/metadata/en-US/support_url.txt ================================================ https://x.com/lexrus ================================================ FILE: fastlane/metadata/es-ES/apple_tv_privacy_policy.txt ================================================ ================================================ FILE: fastlane/metadata/es-ES/description.txt ================================================ ¡Libera el Poder de las Expresiones Regulares con RegEx+! Diseñado por desarrolladores para desarrolladores, RegEx+ es tu herramienta esencial para dominar las expresiones regulares en macOS e iOS. Sumérgete en una experiencia perfecta mientras tus datos se sincronizan sin esfuerzo entre dispositivos a través de CloudKit, asegurando que tengas tu trabajo donde lo necesites, cuando lo necesites. Ya seas principiante o un profesional experimentado, nuestra hoja de referencia integral desmitifica metacaracteres y operadores, permitiéndote decodificar y construir patrones complejos con facilidad. ¡Eleva tu juego de programación con RegEx+ y transforma la forma en que manejas texto, datos y más! ================================================ FILE: fastlane/metadata/es-ES/keywords.txt ================================================ regexp,expresiones,regulares,programador,código,editor,patrón,sintaxis,ios,mac,software,herramienta ================================================ FILE: fastlane/metadata/es-ES/marketing_url.txt ================================================ ================================================ FILE: fastlane/metadata/es-ES/name.txt ================================================ RegEx+ ================================================ FILE: fastlane/metadata/es-ES/privacy_url.txt ================================================ https://lex.sh/regexplus/privacypolicy/ ================================================ FILE: fastlane/metadata/es-ES/promotional_text.txt ================================================ Pruebe, depure y guarde patrones regex más rápido en iPhone, iPad y Mac. Coincidencias en vivo, guía de consulta y sincronización CloudKit. ================================================ FILE: fastlane/metadata/es-ES/release_notes.txt ================================================ Novedades en RegEx+: • Se ha añadido compatibilidad con los idiomas francés, italiano, coreano, neerlandés y polaco. • Optimizaciones de rendimiento y mejoras de estabilidad. ================================================ FILE: fastlane/metadata/es-ES/subtitle.txt ================================================ Acordeón Regex y Cloud Sync ================================================ FILE: fastlane/metadata/es-ES/support_url.txt ================================================ https://x.com/lexrus ================================================ FILE: fastlane/metadata/fr-FR/apple_tv_privacy_policy.txt ================================================ ================================================ FILE: fastlane/metadata/fr-FR/description.txt ================================================ Libérez la puissance des expressions régulières avec RegEx+ ! Conçu pour les développeurs par des développeurs, RegEx+ est votre outil de référence pour maîtriser les expressions régulières sur macOS et iOS. Plongez dans une expérience fluide où vos données se synchronisent automatiquement sur tous vos appareils via CloudKit, vous assurant d'avoir votre travail où vous en avez besoin, quand vous en avez besoin. Que vous soyez débutant ou professionnel chevronné, notre antisèche complète démystifie les métacaractères et les opérateurs, vous permettant de décoder et de construire des motifs complexes avec facilité. Élevez votre niveau de programmation avec RegEx+ et transformez votre façon de gérer le texte, les données et bien plus encore ! ================================================ FILE: fastlane/metadata/fr-FR/keywords.txt ================================================ regexp,expression,régulière,développeur,code,éditeur,motif,syntaxe,ios,mac,logiciel,outil ================================================ FILE: fastlane/metadata/fr-FR/marketing_url.txt ================================================ ================================================ FILE: fastlane/metadata/fr-FR/name.txt ================================================ RegEx+ ================================================ FILE: fastlane/metadata/fr-FR/privacy_url.txt ================================================ https://x.com/lexrus ================================================ FILE: fastlane/metadata/fr-FR/promotional_text.txt ================================================ Testez, déboguez et enregistrez vos regex plus vite sur iPhone, iPad et Mac. Matching en direct, antisèche et synchro CloudKit. ================================================ FILE: fastlane/metadata/fr-FR/release_notes.txt ================================================ Quoi de neuf dans RegEx+ : • Ajout de la prise en charge des langues française, italienne, coréenne, néerlandaise et polonaise. • Optimisations des performances et améliorations de la stabilité. ================================================ FILE: fastlane/metadata/fr-FR/subtitle.txt ================================================ Antisèche Regex et Cloud Sync ================================================ FILE: fastlane/metadata/fr-FR/support_url.txt ================================================ https://x.com/lexrus ================================================ FILE: fastlane/metadata/it/apple_tv_privacy_policy.txt ================================================ ================================================ FILE: fastlane/metadata/it/description.txt ================================================ Scatena la potenza delle espressioni regolari con RegEx+! Progettato per sviluppatori da sviluppatori, RegEx+ è il tuo strumento di riferimento per padroneggiare le espressioni regolari su macOS e iOS. Immergiti in un'esperienza fluida dove i tuoi dati si sincronizzano automaticamente su tutti i dispositivi tramite CloudKit, assicurandoti di avere il tuo lavoro dove ne hai bisogno, quando ne hai bisogno. Che tu sia un principiante o un professionista esperto, la nostra guida di riferimento completa demistifica metacaratteri e operatori, permettendoti di decodificare e costruire pattern complessi con facilità. Eleva il tuo livello di programmazione con RegEx+ e trasforma il modo in cui gestisci testo, dati e molto altro! ================================================ FILE: fastlane/metadata/it/keywords.txt ================================================ regexp,espressione,regolare,sviluppatore,codice,editor,pattern,sintassi,ios,mac,software,strumento ================================================ FILE: fastlane/metadata/it/marketing_url.txt ================================================ ================================================ FILE: fastlane/metadata/it/name.txt ================================================ RegEx+ ================================================ FILE: fastlane/metadata/it/privacy_url.txt ================================================ https://x.com/lexrus ================================================ FILE: fastlane/metadata/it/promotional_text.txt ================================================ Testa, debugga e salva i pattern regex più velocemente su iPhone, iPad e Mac. Matching in tempo reale, guida rapida e sincro CloudKit. ================================================ FILE: fastlane/metadata/it/release_notes.txt ================================================ Novità in RegEx+: • Aggiunto il supporto per le lingue francese, italiana, coreana, olandese e polacca. • Ottimizzazioni delle prestazioni e miglioramenti della stabilità. ================================================ FILE: fastlane/metadata/it/subtitle.txt ================================================ Guida Regex e Cloud Sync ================================================ FILE: fastlane/metadata/it/support_url.txt ================================================ https://x.com/lexrus ================================================ FILE: fastlane/metadata/ja/apple_tv_privacy_policy.txt ================================================ ================================================ FILE: fastlane/metadata/ja/description.txt ================================================ RegEx+で正規表現の力を解き放とう! 開発者による開発者のためのRegEx+は、macOSとiOSで正規表現をマスターするための必須ツールです。CloudKitを通じてデバイス間でデータが簡単に同期されるシームレスな体験に飛び込み、必要な時に必要な場所で作業ができることを保証します。初心者でも熟練のプロでも、包括的なチートシートがメタ文字と演算子を分かりやすく説明し、複雑なパターンを簡単にデコードして構築できるようにします。RegEx+でコーディングスキルを向上させ、テキスト、データなどの扱い方を変革しましょう! ================================================ FILE: fastlane/metadata/ja/keywords.txt ================================================ 正規表現,開発者,プログラミング,コード,エディタ,パターン,構文,ソフトウェア,ツール,ios,mac,regexp ================================================ FILE: fastlane/metadata/ja/marketing_url.txt ================================================ ================================================ FILE: fastlane/metadata/ja/name.txt ================================================ RegEx+ ================================================ FILE: fastlane/metadata/ja/privacy_url.txt ================================================ https://lex.sh/regexplus/privacypolicy/ ================================================ FILE: fastlane/metadata/ja/promotional_text.txt ================================================ iPhone、iPad、Macで正規表現のテストと保存を高速化。ライブマッチング、リファレンス、CloudKit同期を一つのツールで。 ================================================ FILE: fastlane/metadata/ja/release_notes.txt ================================================ RegEx+ の新機能: • フランス語、イタリア語、韓国語、オランダ語、ポーランド語のサポートを追加しました。 • パフォーマンスの最適化と安定性の向上。 ================================================ FILE: fastlane/metadata/ja/subtitle.txt ================================================ 正規表現の早見表とクラウド同期 ================================================ FILE: fastlane/metadata/ja/support_url.txt ================================================ https://x.com/lexrus ================================================ FILE: fastlane/metadata/nl-NL/apple_tv_privacy_policy.txt ================================================ ================================================ FILE: fastlane/metadata/nl-NL/description.txt ================================================ Ontketen de kracht van Reguliere Expressies met RegEx+! RegEx+ is ontworpen door ontwikkelaars voor ontwikkelaars en is dé tool voor het beheersen van reguliere expressies op zowel macOS als iOS. Ervaar een naadloze ervaring terwijl je gegevens moeiteloos worden gesynchroniseerd tussen apparaten via CloudKit, zodat je je werk altijd bij de hand hebt. Of je nu een beginner bent of een ervaren pro, ons uitgebreide spiekbriefje verheldert metatekens en operatoren, waardoor je complexe patronen met gemak kunt ontcijferen en construeren. Til je programmeervaardigheden naar een hoger niveau met RegEx+ en transformeer de manier waarop je tekst, gegevens en meer verwerkt! ================================================ FILE: fastlane/metadata/nl-NL/keywords.txt ================================================ regexp,reguliere,expressies,ontwikkelaar,code,editor,patroon,syntax,ios,mac,software,tool ================================================ FILE: fastlane/metadata/nl-NL/marketing_url.txt ================================================ ================================================ FILE: fastlane/metadata/nl-NL/name.txt ================================================ RegEx+ ================================================ FILE: fastlane/metadata/nl-NL/privacy_url.txt ================================================ https://lex.sh/regexplus/privacypolicy/ ================================================ FILE: fastlane/metadata/nl-NL/promotional_text.txt ================================================ Test, debug en bewaar regex-patronen sneller op iPhone, iPad en Mac. Live matching, spiekbriefje en CloudKit-synchronisatie. ================================================ FILE: fastlane/metadata/nl-NL/release_notes.txt ================================================ Wat is er nieuw in RegEx+: • Ondersteuning voor de Franse, Italiaanse, Koreaanse, Nederlandse en Poolse taal toegevoegd. • Prestatieoptimalisaties en stabiliteitsverbeteringen. ================================================ FILE: fastlane/metadata/nl-NL/subtitle.txt ================================================ Regex Spiekbrief & Cloud Sync ================================================ FILE: fastlane/metadata/nl-NL/support_url.txt ================================================ https://x.com/lexrus ================================================ FILE: fastlane/metadata/pl/apple_tv_privacy_policy.txt ================================================ ================================================ FILE: fastlane/metadata/pl/description.txt ================================================ Uwolnij moc Wyrażeń Regularnych z RegEx+! Zaprojektowany przez programistów dla programistów, RegEx+ to Twoje podstawowe narzędzie do opanowania wyrażeń regularnych na macOS i iOS. Ciesz się płynną pracą, gdy Twoje dane synchronizują się bez wysiłku między urządzeniami przez CloudKit, zapewniając dostęp do pracy zawsze wtedy, gdy jej potrzebujesz. Niezależnie od tego, czy jesteś początkującym, czy doświadczonym profesjonalistą, nasza kompleksowa ściąga wyjaśnia znaki specjalne i operatory, pozwalając z łatwością dekodować i budować złożone wzorce. Podnieś swoje umiejętności kodowania dzięki RegEx+ i odmień sposób, w jaki pracujesz z tekstem, danymi i nie tylko! ================================================ FILE: fastlane/metadata/pl/keywords.txt ================================================ regexp,wyrażenia,regularne,programista,kod,edytor,wzór,składnia,ios,mac,oprogramowanie,narzędzie ================================================ FILE: fastlane/metadata/pl/marketing_url.txt ================================================ ================================================ FILE: fastlane/metadata/pl/name.txt ================================================ RegEx+ ================================================ FILE: fastlane/metadata/pl/privacy_url.txt ================================================ https://lex.sh/regexplus/privacypolicy/ ================================================ FILE: fastlane/metadata/pl/promotional_text.txt ================================================ Testuj, debuguj i zapisuj wyrażenia regex szybciej na iPhone, iPad i Mac. Dopasowanie na żywo, ściąga i synchronizacja CloudKit. ================================================ FILE: fastlane/metadata/pl/release_notes.txt ================================================ Co nowego w RegEx+: • Dodano obsługę języka francuskiego, włoskiego, koreańskiego, holenderskiego i polskiego. • Optymalizacja wydajności i poprawa stabilności. ================================================ FILE: fastlane/metadata/pl/subtitle.txt ================================================ Ściąga Regex i Cloud Sync ================================================ FILE: fastlane/metadata/pl/support_url.txt ================================================ https://x.com/lexrus ================================================ FILE: fastlane/metadata/primary_category.txt ================================================ DEVELOPER_TOOLS ================================================ FILE: fastlane/metadata/primary_first_sub_category.txt ================================================ ================================================ FILE: fastlane/metadata/primary_second_sub_category.txt ================================================ ================================================ FILE: fastlane/metadata/secondary_category.txt ================================================ UTILITIES ================================================ FILE: fastlane/metadata/secondary_first_sub_category.txt ================================================ ================================================ FILE: fastlane/metadata/secondary_second_sub_category.txt ================================================ ================================================ FILE: fastlane/metadata/zh-Hans/apple_tv_privacy_policy.txt ================================================ ================================================ FILE: fastlane/metadata/zh-Hans/description.txt ================================================ 用 RegEx+ 释放正则表达式的力量! 由开发者为开发者设计,RegEx+ 是您在 macOS 和 iOS 上掌握正则表达式的必备工具。沉浸在无缝体验中,您的数据通过 CloudKit 在设备间轻松同步,确保您在需要的时候、需要的地方都能访问您的工作。无论您是初学者还是经验丰富的专业人士,我们全面的速查表都能揭开元字符和操作符的神秘面纱,让您轻松解码和构建复杂模式。用 RegEx+ 提升您的编程技能,改变您处理文本、数据等内容的方式! ================================================ FILE: fastlane/metadata/zh-Hans/keywords.txt ================================================ 正则表达式,正则,开发,编程,代码,编辑器,模式,语法,软件,工具,ios,mac,regexp ================================================ FILE: fastlane/metadata/zh-Hans/marketing_url.txt ================================================ ================================================ FILE: fastlane/metadata/zh-Hans/name.txt ================================================ RegEx+ ================================================ FILE: fastlane/metadata/zh-Hans/privacy_url.txt ================================================ https://lex.sh/regexplus/privacypolicy/ ================================================ FILE: fastlane/metadata/zh-Hans/promotional_text.txt ================================================ 在 iPhone、iPad 和 Mac 上更快速地测试、调试和保存正则表达式。实时匹配、速查表和 CloudKit 同步,一站式搞定。 ================================================ FILE: fastlane/metadata/zh-Hans/release_notes.txt ================================================ RegEx+ 新功能: • 新增法语、意大利语、韩语、荷兰语和波兰语支持。 • 性能优化和稳定性改进。 ================================================ FILE: fastlane/metadata/zh-Hans/subtitle.txt ================================================ 正则表达式速查表与云同步 ================================================ FILE: fastlane/metadata/zh-Hans/support_url.txt ================================================ https://x.com/lexrus ================================================ FILE: fastlane/metadata/zh-Hant/apple_tv_privacy_policy.txt ================================================ ================================================ FILE: fastlane/metadata/zh-Hant/description.txt ================================================ 用 RegEx+ 釋放正則表達式的力量! 由開發者為開發者設計,RegEx+ 是您在 macOS 和 iOS 上掌握正則表達式的必備工具。沉浸在無縫體驗中,您的資料透過 CloudKit 在裝置間輕鬆同步,確保您在需要的時候、需要的地方都能存取您的工作。無論您是初學者還是經驗豐富的專業人士,我們全面的速查表都能揭開元字符和操作符的神秘面紗,讓您輕鬆解碼和構建複雜模式。用 RegEx+ 提升您的程式設計技能,改變您處理文字、資料等內容的方式! ================================================ FILE: fastlane/metadata/zh-Hant/keywords.txt ================================================ 正則表達式,正則,開發,編程,代碼,編輯器,模式,語法,軟件,工具,ios,mac,regexp ================================================ FILE: fastlane/metadata/zh-Hant/marketing_url.txt ================================================ ================================================ FILE: fastlane/metadata/zh-Hant/name.txt ================================================ RegEx+ ================================================ FILE: fastlane/metadata/zh-Hant/privacy_url.txt ================================================ https://lex.sh/regexplus/privacypolicy/ ================================================ FILE: fastlane/metadata/zh-Hant/promotional_text.txt ================================================ 在 iPhone、iPad 和 Mac 上更快速地測試、調試和保存正則表達式。即時匹配、速查表和 CloudKit 同步,一站式搞定。 ================================================ FILE: fastlane/metadata/zh-Hant/release_notes.txt ================================================ RegEx+ 新功能: • 新增法語、義大利語、韓語、荷蘭語和波蘭語支援。 • 效能優化和穩定性改進。 ================================================ FILE: fastlane/metadata/zh-Hant/subtitle.txt ================================================ 正則表達式速查表與雲同步 ================================================ FILE: fastlane/metadata/zh-Hant/support_url.txt ================================================ https://x.com/lexrus ================================================ FILE: mise.toml ================================================ [tasks.sc2tc] description = "Convert Simplified Chinese to Traditional Chinese using OpenCC" run = "opencc -c s2hk -i RegEx+/zh-Hans.lproj/CheatSheet.plist -o RegEx+/zh-Hant.lproj/CheatSheet.plist" [tasks.pull-metadata] description = "Pull all metadata from App Store for all platforms" run = [ "fastlane deliver download_metadata -j ios --api_key_path fastlane/keys/app_key.json --sync_screenshots --force", "fastlane deliver download_metadata -j osx --api_key_path fastlane/keys/app_key.json --sync_screenshots --force", ] [tasks.push-metadata] description = "Push all metadata to App Store for all platforms" run = [ "fastlane deliver upload_metadata -j ios --api_key_path fastlane/keys/app_key.json --sync_screenshots --force", "fastlane deliver upload_metadata -j osx --api_key_path fastlane/keys/app_key.json --sync_screenshots --force", ]