diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..c02cb603 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Note that Issues are for bugs only. Use the discussions tab for feature requests.** + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/hr.jpg b/.github/hr.jpg new file mode 100644 index 00000000..eff497b2 Binary files /dev/null and b/.github/hr.jpg differ diff --git a/.github/resources/images/.nomedia b/.github/resources/images/.nomedia new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/.github/resources/images/.nomedia @@ -0,0 +1 @@ + diff --git a/.github/resources/images/features_title_dark.png b/.github/resources/images/features_title_dark.png new file mode 100644 index 00000000..fcc587e8 Binary files /dev/null and b/.github/resources/images/features_title_dark.png differ diff --git a/.github/resources/images/features_title_light.png b/.github/resources/images/features_title_light.png new file mode 100644 index 00000000..52dad1bc Binary files /dev/null and b/.github/resources/images/features_title_light.png differ diff --git a/.github/resources/images/hr.jpg b/.github/resources/images/hr.jpg new file mode 100644 index 00000000..eff497b2 Binary files /dev/null and b/.github/resources/images/hr.jpg differ diff --git a/.github/resources/screenshot/.nomedia b/.github/resources/screenshot/.nomedia new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/.github/resources/screenshot/.nomedia @@ -0,0 +1 @@ + diff --git a/.github/resources/screenshot/screenshot1.webp b/.github/resources/screenshot/screenshot1.webp new file mode 100644 index 00000000..51d7d9b0 Binary files /dev/null and b/.github/resources/screenshot/screenshot1.webp differ diff --git a/.github/resources/screenshot/screenshot2.webp b/.github/resources/screenshot/screenshot2.webp new file mode 100644 index 00000000..115fb33e Binary files /dev/null and b/.github/resources/screenshot/screenshot2.webp differ diff --git a/.github/resources/screenshot/screenshot3.webp b/.github/resources/screenshot/screenshot3.webp new file mode 100644 index 00000000..27fe7f2b Binary files /dev/null and b/.github/resources/screenshot/screenshot3.webp differ diff --git a/.github/resources/screenshot/screenshot4.webp b/.github/resources/screenshot/screenshot4.webp new file mode 100644 index 00000000..916dc2fd Binary files /dev/null and b/.github/resources/screenshot/screenshot4.webp differ diff --git a/.github/resources/screenshot/screenshots_title_dark.png b/.github/resources/screenshot/screenshots_title_dark.png new file mode 100644 index 00000000..5316bf23 Binary files /dev/null and b/.github/resources/screenshot/screenshots_title_dark.png differ diff --git a/.github/resources/screenshot/screenshots_title_light.png b/.github/resources/screenshot/screenshots_title_light.png new file mode 100644 index 00000000..912b181c Binary files /dev/null and b/.github/resources/screenshot/screenshots_title_light.png differ diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 00000000..4985d0a1 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,27 @@ +name: Android Tests CI + +on: + workflow_dispatch: + push: + branches: [ javac-17 ] + pull_request: + branches: [ javac-17 ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + cache: gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew test diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml new file mode 100644 index 00000000..0147389d --- /dev/null +++ b/.github/workflows/build-apk.yml @@ -0,0 +1,41 @@ +name: Android CI + +on: + pull_request: + workflow_dispatch: + +jobs: + build: + name: Build debug apk + runs-on: ubuntu-latest + steps: + - name: Cancel previous runs + uses: styfle/cancel-workflow-action@0.5.0 + with: + access_token: ${{ github.token }} + + - uses: actions/checkout@v2 + + - name: set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + cache: gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build debug apk + uses: eskatos/gradle-command-action@v1 + with: + arguments: assembleDebug + distributions-cache-enabled: true + dependencies-cache-enabled: true + configuration-cache-enabled: true + + - name: Upload debug apk + uses: actions/upload-artifact@v2 + with: + name: apk-debug + path: app/build/outputs/apk/debug diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c516c411 --- /dev/null +++ b/.gitignore @@ -0,0 +1,88 @@ +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/misc.xml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +.idea/misc.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..b77d990f --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..87aac072 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 00000000..d8e95616 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..eaf78c3a --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,54 @@ + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 00000000..4dcbce12 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..b9cc99f8 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,131 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at the [codeassist +telegram group](telegram_group) or [discord server](discord_server). +All complaints will be reviewed and investigated promptly and fairly. + +[telegram_group]: https://t.me/codeassist_app +[discord_server]: https://discord.gg/3YMZkgFS + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..7fd6eb4b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,48 @@ +# Contributing to CodeAssist +We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: + +- Reporting a bug +- Discussing the current state of the code +- Submitting a fix +- Proposing new features +- Becoming a maintainer + +## We Develop with Github +We use github to host code, to track issues and feature requests, as well as accept pull requests. + +## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html), So All Code Changes Happen Through Pull Requests +Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests: + +1. Fork the repo and create your branch from `master`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. Issue that pull request! + +## Any contributions you make will be under the GPL-3.0 Software License +In short, when you submit code changes, your submissions are understood to be under the same [GPL-3.0 License](https://choosealicense.com/licenses/gpl-3.0/) that covers the project. Feel free to contact the maintainers if that's a concern. + +## Report bugs using Github's +We use GitHub issues to track public bugs. Report a bug by [opening a new issue](); it's that easy! + +## Write bug reports with detail, background, and sample code +[This is an example](http://stackoverflow.com/q/12488905/180626) of a bug report I wrote, and I think it's not a bad model. Here's [another example from Craig Hockenberry](http://www.openradar.me/11905408), an app developer whom I greatly respect. + +**Great Bug Reports** tend to have: + +- A quick summary and/or background +- Steps to reproduce + - Be specific! + - Give sample code if you can. [My stackoverflow question](http://stackoverflow.com/q/12488905/180626) includes sample code that *anyone* with a base R setup can run to reproduce what I was seeing +- What you expected would happen +- What actually happens +- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) + +People *love* thorough bug reports. I'm not even kidding. + +## License +By contributing, you agree that your contributions will be licensed under its GPL-3.0 License. + +## References +This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md) diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 00000000..57bd04f5 --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +

+ + +

+ + + +
+ +[![stability-alpha](https://img.shields.io/badge/stability-alpha-f4d03f.svg)](https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#alpha) +[![Chat](https://img.shields.io/badge/chat-on%20discord-7289da)](https://discord.gg/pffnyE6prs) + +

A javac APIs-based code editor that supports building Android apps.

+ +
+ + + + + + + + + +- [x] APK Compilation + +- [x] AAB Support + +- [x] Java + +- [x] Kotlin + +- [x] R8/ProGuard + +- [x] Code Completions (Currently for Java only) + +- [x] Quick fixes (Import missing class and Implement Abstract Methods) + +- [x] Layout Preview (80%) + +- [x] Automatic dependency resolution + +- [ ] Layout Editor + +- [ ] Debugger + +- [ ] Lint + + + +## CodeAssist Community + +Discord server: https://discord.gg/pffnyE6prs + + + +English-language chat in Telegram: https://t.me/codeassist_app + + + +Russian-language (руÑÑкоÑзычный) chat in Telegram: https://t.me/codeassist_chat + + + +## Building - Android Studio + +Clone this repository to your local device and then open it on Android Studio. + + + +## Contributing + +- Pull request must have a short description as a title and a more detailed one in the description + +- Feature additions must include Unit/Instrumentation tests. This is for future stability of the app and modules. + + + +# Special thanks + +- Rosemoe/CodeEditor + +- JavaNIDE + +- Mike Anderson + +- Java Language Server + +- Ilyasse Salama + + +# CodeAssist-ALPHA-0.2.9 diff --git a/actions-api/.gitignore b/actions-api/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/actions-api/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/actions-api/README.md b/actions-api/README.md new file mode 100644 index 00000000..4c95a3aa --- /dev/null +++ b/actions-api/README.md @@ -0,0 +1,105 @@ +## Introduction + +Developers can easily add functionalities to existing IDE menus and toolbars or add new menus +altogether using the Actions API. + +## Creating a Custom Action + +Custom Actions in CodeAssist are implemented by extending the abstract class [AnAction](https://github.com/tyron12233/CodeAssist/blob/91f91294c07570fb888c24e592c5b1f847cecc74/actions-api/src/main/java/com/tyron/actions/AnAction.java) +Classes that extend it should override `AnAction.update()` and `AnAction.actionPerformed()`. + +- The `update` method implements the code that updates the appearance of the action +- The `actionPerformed` method implements the code that will be run when the user has selected +the action. + +## Action Places + +There are different places on which a custom action can be shown. See [ActionPlaces](https://github.com/tyron12233/CodeAssist/blob/91f91294c07570fb888c24e592c5b1f847cecc74/actions-api/src/main/java/com/tyron/actions/ActionPlaces.java) +for a list of places CodeAssist currently supports. + +## Example + +This example shows how to create an Action to save the current file in the IDE. + +### Subclassing AnAction + +```java +import androidx.annotation.NonNull; + +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; + +public class SaveAction extends AnAction { + @Override + public void update(@NonNull AnActionEvent event) { + + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + + } +} +``` + +Currently this action does not do anything useful and does not have a text that would represent it. + +### Extending the update() method + +T he `AnActionEvent` object which is passed in the method, contains context information about +where the current event has taken place. See [AnActionEvent.getData()](https://github.com/tyron12233/CodeAssist/blob/91f91294c07570fb888c24e592c5b1f847cecc74/actions-api/src/main/java/com/tyron/actions/AnActionEvent.java#L57) +and [CommonDataKeys](https://github.com/tyron12233/CodeAssist/blob/91f91294c07570fb888c24e592c5b1f847cecc74/actions-api/src/main/java/com/tyron/actions/CommonDataKeys.java) +for a list of objects that can be retrieved. + +Determine which objects do you need in the update method, that way it will be guaranteed to be +not null when the user has clicked on the action. +```java +@Override +public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + // set the presentation to be not visible by default, that way methods can just + // return right away if it has been determined that its not the right place for this action. + presentation.setVisible(false); + + // this ensures that the action is only visible on the Main Toolbar. + if (!ActionPlaces.MAIN_TOOLBAR.equals(event.getPlace())) { + return; + } + + // the fragment data key returns the current focused fragment when this action was invoked. + Fragment fragment = event.getData(CommonDataKeys.FRAGMENT); + if (fragment == null) { + return; + } + + // if the code has reached here, it has been determined that this action should be visible + presentation.setVisible(true); + presentation.setText("Save"); +} +``` +**The update method is called frequently on the UI thread. This method needs to execute very quickly and +no real work should be performed in this method. For example working with the file system such as reading +and writing to files are considered invalid and will block the UI thread.** + +## Extending the actionPerformed() method + +CodeAssist has a [Savable](https://github.com/tyron12233/CodeAssist/blob/main/app/src/main/java/com/tyron/code/ui/editor/Savable.java) interface +which its editors implement to indicate that its content can be saved. This action leverages that feature. + +```java +@Override +public void actionPerformed(@NonNull AnActionEvent e) { + Fragment fragment = e.getRequiredData(CommonDataKeys.FRAGMENT); + if (fragment instanceof Savable) { + ((Savable) fragment).save(); + } +} +``` + +### Registering the action + +Currently, actions need to be registered dynamically through the [ActionManager](https://github.com/tyron12233/CodeAssist/blob/main/app/src/main/java/com/tyron/code/ui/editor/Savable.java) +Actions need to have a unique String identifier that the Actions API will use to identify it from others. +```java +ActionManager.getInstance().registerAction(UNIQUE_ID, new SaveAction()); +``` diff --git a/actions-api/build.gradle b/actions-api/build.gradle new file mode 100644 index 00000000..58c19d77 --- /dev/null +++ b/actions-api/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'java-library' +} + +dependencies { + implementation 'androidx.annotation:annotation:1.3.0' + implementation project(path: ':build-tools:build-logic') + implementation project(path: ':build-tools:project') + implementation project(path: ':build-tools:logging') + // needed for UserDataHolder class + implementation project(path: ':build-tools:kotlinc') + implementation project(path: ':fileeditor-api') + implementation project(path: ':editor-api') + compileOnly project(path: ':android-stubs') + + testImplementation 'junit:junit:4.+' +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} \ No newline at end of file diff --git a/actions-api/consumer-rules.pro b/actions-api/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/actions-api/proguard-rules.pro b/actions-api/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/actions-api/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/actions-api/src/main/AndroidManifest.xml b/actions-api/src/main/AndroidManifest.xml new file mode 100644 index 00000000..6fdeef42 --- /dev/null +++ b/actions-api/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/actions-api/src/main/java/com/tyron/actions/ActionGroup.java b/actions-api/src/main/java/com/tyron/actions/ActionGroup.java new file mode 100644 index 00000000..6f261421 --- /dev/null +++ b/actions-api/src/main/java/com/tyron/actions/ActionGroup.java @@ -0,0 +1,31 @@ +package com.tyron.actions; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.beans.PropertyChangeSupport; + +public abstract class ActionGroup extends AnAction { + + private final PropertyChangeSupport mChangeSupport = new PropertyChangeSupport(this); + + public static final ActionGroup EMPTY_GROUP = new ActionGroup() { + + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return new AnAction[0]; + } + }; + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + + } + + public boolean isPopup() { + return true; + } + + public abstract AnAction[] getChildren(@Nullable AnActionEvent e); + +} diff --git a/actions-api/src/main/java/com/tyron/actions/ActionManager.java b/actions-api/src/main/java/com/tyron/actions/ActionManager.java new file mode 100644 index 00000000..e8204c9b --- /dev/null +++ b/actions-api/src/main/java/com/tyron/actions/ActionManager.java @@ -0,0 +1,30 @@ +package com.tyron.actions; + +import android.view.Menu; + +import androidx.annotation.NonNull; + +import com.tyron.actions.impl.ActionManagerImpl; + +public abstract class ActionManager { + + private static ActionManager sInstance = null; + public static ActionManager getInstance() { + if (sInstance == null) { + sInstance = new ActionManagerImpl(); + } + return sInstance; + } + + public abstract void fillMenu(DataContext context, Menu menu, String place, boolean isContext, boolean isToolbar); + + public abstract String getId(@NonNull AnAction action); + + public abstract void registerAction(@NonNull String actionId, @NonNull AnAction action); + + public abstract void unregisterAction(@NonNull String actionId); + + public abstract void replaceAction(@NonNull String actionId, @NonNull AnAction newAction); + + public abstract boolean isGroup(@NonNull String actionId); +} diff --git a/actions-api/src/main/java/com/tyron/actions/ActionPlaces.java b/actions-api/src/main/java/com/tyron/actions/ActionPlaces.java new file mode 100644 index 00000000..8dc8601e --- /dev/null +++ b/actions-api/src/main/java/com/tyron/actions/ActionPlaces.java @@ -0,0 +1,21 @@ +package com.tyron.actions; + +public class ActionPlaces { + + /** + * The toolbar located in MainFragment + */ + public static final String MAIN_TOOLBAR = "main_toolbar"; + + /** + * Inside a CodeEditorFragment + */ + public static final String EDITOR = "editor"; + + /** + * The tab layout in an EditorContainerFragment + */ + public static final String EDITOR_TAB = "editorTab"; + + public static final String FILE_MANAGER = "fileManager"; +} diff --git a/actions-api/src/main/java/com/tyron/actions/AnAction.java b/actions-api/src/main/java/com/tyron/actions/AnAction.java new file mode 100644 index 00000000..8df5b8bf --- /dev/null +++ b/actions-api/src/main/java/com/tyron/actions/AnAction.java @@ -0,0 +1,136 @@ +package com.tyron.actions; + +import android.graphics.drawable.Drawable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.function.Supplier; + +/** + * Represents a menu that can be performed. + * + * For an action to do something, override the {@link AnAction#actionPerformed(AnActionEvent)} + * and optionally override {@link AnAction#update(AnActionEvent)}. By implementing the + * {@link AnAction#update(AnActionEvent)} method, you can dynamically change the action's + * presentation depending on the place. For more information on places see {@link ActionPlaces} + * + *
+ *     public class MyAction extends AnAction {
+ *         public MyAction() {
+ *             // ...
+ *         }
+ *
+ *         public void update(AnActionEvent e) {
+ *             Presentation presentation = e.getPresentation();
+ *             presentation.setVisible(true);
+ *             presentation.setText(e.getPlace());
+ *         }
+ *
+ *         public void actionPerformed(AnActionEvent e) {
+ *             // do something when this action is pressed
+ *         }
+ *     }
+ * 
+ * + * This implementation is partially adopted from IntelliJ's Actions API. + * + * @see AnActionEvent + * @see Presentation + * @see ActionPlaces + */ +public abstract class AnAction { + + private Presentation mTemplatePresentation; + + public AnAction() { + + } + + public AnAction(Drawable icon) { + this(Presentation.NULL_STRING, Presentation.NULL_STRING, icon); + } + + public AnAction(@Nullable String text) { + this(text, null, null); + } + + public AnAction(@NonNull Supplier dynamicText) { + this(dynamicText, Presentation.NULL_STRING, null); + } + + public AnAction(@Nullable String text, + @Nullable String description, + @Nullable Drawable icon) { + this(() -> text, () -> description, icon); + } + + public AnAction(@NonNull Supplier dynamicText, @Nullable Drawable icon) { + this(dynamicText, Presentation.NULL_STRING, icon); + } + + public AnAction(@NonNull Supplier dynamicText, + @NonNull Supplier description, + @Nullable Drawable icon) { + Presentation presentation = getTemplatePresentation(); + presentation.setText(dynamicText); + presentation.setDescription(description); + presentation.setIcon(icon); + } + + public final Presentation getTemplatePresentation() { + if (mTemplatePresentation == null) { + mTemplatePresentation = createTemplatePresentation(); + } + return mTemplatePresentation; + } + + private Presentation createTemplatePresentation() { + return Presentation.createTemplatePresentation(); + } + + public boolean displayTextInToolbar() { + return false; + } + + public boolean useSmallerFontForTextInToolbar() { + return false; + } + + /** + * Updates the state of this action. Default implementation does nothing. + * Override this method to provide the ability to dynamically change action's + * state and(or) presentation depending on the context. (For example + * when your action state depends on the selection you can check for the + * selection and change the state accordingly.) + * + * This method can be called frequently and on UI thread. + * This means that this method is supposed to work really fast, + * no work should be done at this phase. For example checking values such as editor + * selection is fine but working with the file system such as reading/writing to a file is not. + * If the action state cannot be determined, do the checks in + * {@link #actionPerformed(AnActionEvent)} and inform the user if the action cannot + * be performed by possibly showing a dialog. + * + * @param event Carries information on the invocation place and data available + */ + public void update(@NonNull AnActionEvent event) { + + } + + /** + * Implement this method to handle when this action has been clicked or pressed. + * + * @param e Carries information on the invocation place and data available. + */ + public abstract void actionPerformed(@NonNull AnActionEvent e); + + + public void beforeActionPerformedUpdate(@NonNull AnActionEvent e) { + update(e); + } + + public String getTemplateText() { + return getTemplatePresentation().getText(); + } +} diff --git a/actions-api/src/main/java/com/tyron/actions/AnActionEvent.java b/actions-api/src/main/java/com/tyron/actions/AnActionEvent.java new file mode 100644 index 00000000..7cd82d2d --- /dev/null +++ b/actions-api/src/main/java/com/tyron/actions/AnActionEvent.java @@ -0,0 +1,104 @@ +package com.tyron.actions; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RestrictTo; + +import org.jetbrains.kotlin.com.intellij.openapi.util.Key; + +/** + * Container for information necessary to execute or update an {@link AnAction} + * + * @see AnAction#update(AnActionEvent) + * @see AnAction#actionPerformed(AnActionEvent) + */ +public class AnActionEvent implements PlaceProvider { + + private final DataContext mDataContext; + private final String mPlace; + private Presentation mPresentation; + private final boolean mIsContextMenuAction; + private final boolean mIsActionToolbar; + + public AnActionEvent(@NonNull DataContext context, + @NonNull String place, + @NonNull Presentation presentation, + boolean isContextMenuAction, + boolean isActionToolbar) { + mDataContext = context; + mPlace = place; + mPresentation = presentation; + mIsContextMenuAction = isContextMenuAction; + mIsActionToolbar = isActionToolbar; + } + + public void setPresentation(Presentation presentation) { + mPresentation = presentation; + } + + @Override + public String getPlace() { + return mPlace; + } + + public Presentation getPresentation() { + return mPresentation; + } + + public boolean isContextMenuAction() { + return mIsContextMenuAction; + } + + public boolean isActionToolbar() { + return mIsActionToolbar; + } + + @Nullable + public T getData(Key key) { + return mDataContext.getData(key); + } + + /** + * Returns a non null data by a data key. This method assumes that data has been checked + * for {@code null} in {@code AnAction#update} method. + * + *

+ * Example of proper usage: + * + *
+     *     public class MyAction extends AnAction {
+     *         public void update(AnActionEvent e) {
+     *             // perform action if and only if EDITOR != null
+     *             boolean visible = e.getData(CommonDataKeys.EDITOR) != null;
+     *             e.getPresentation().setVisible(visible);
+     *         }
+     *
+     *         public void actionPerformed(AnActionEvent e) {
+     *             // if we're here then EDITOR != null
+     *             Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
+     *         }
+     *     }
+     * 
+ */ + @NonNull + public T getRequiredData(Key key) { + T data = getData(key); + assert data != null; + return data; + } + + /** + * Returns the data context which allows to retrieve information about the state of the IDE + * related to the action invocation. (Active editor, fragment and so on) + * + * @return the data context instance + */ + public DataContext getDataContext() { + return mDataContext; + } + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + public void injectData(Key key, T value) { + mDataContext.putData(key, value); + } +} diff --git a/actions-api/src/main/java/com/tyron/actions/CommonDataKeys.java b/actions-api/src/main/java/com/tyron/actions/CommonDataKeys.java new file mode 100644 index 00000000..601e3a61 --- /dev/null +++ b/actions-api/src/main/java/com/tyron/actions/CommonDataKeys.java @@ -0,0 +1,44 @@ +package com.tyron.actions; + +import android.app.Activity; +import android.content.Context; + +import androidx.fragment.app.Fragment; + +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.builder.project.Project; +import com.tyron.editor.Editor; +import com.tyron.fileeditor.api.FileEditor; + +import org.jetbrains.kotlin.com.intellij.openapi.util.Key; + +import java.io.File; + +public class CommonDataKeys { + + /** + * The current file opened in the editor + */ + public static final Key FILE = Key.create("file"); + + public static final Key ACTIVITY = Key.create("activity"); + /** + * The current accessible context + */ + public static final Key CONTEXT = Key.create("context"); + + /** + * The current fragment this action is invoked on + */ + public static final Key FRAGMENT = Key.create("fragment"); + + public static final Key DIAGNOSTIC = Key.create("diagnostic"); + + /** + * The current opened project + */ + public static final Key PROJECT = Key.create("project"); + + public static final Key EDITOR = Key.create("editor"); + public static final Key FILE_EDITOR_KEY = Key.create("fileEditor"); +} diff --git a/actions-api/src/main/java/com/tyron/actions/DataContext.java b/actions-api/src/main/java/com/tyron/actions/DataContext.java new file mode 100644 index 00000000..70e424b5 --- /dev/null +++ b/actions-api/src/main/java/com/tyron/actions/DataContext.java @@ -0,0 +1,36 @@ +package com.tyron.actions; + +import android.content.Context; +import android.content.ContextWrapper; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.com.intellij.openapi.util.Key; +import org.jetbrains.kotlin.com.intellij.openapi.util.UserDataHolderBase; + +public class DataContext extends ContextWrapper { + + private final UserDataHolderBase mUserDataHolder; + + public static DataContext wrap(Context context) { + if (context instanceof DataContext) { + return ((DataContext) context); + } + return new DataContext(context); + } + + public DataContext(Context base) { + super(base); + + mUserDataHolder = new UserDataHolderBase(); + } + + @Nullable + public T getData(@NotNull Key key) { + return mUserDataHolder.getUserData(key); + } + + public void putData(@NotNull Key key, @Nullable T t) { + mUserDataHolder.putUserData(key, t); + } +} diff --git a/actions-api/src/main/java/com/tyron/actions/PlaceProvider.java b/actions-api/src/main/java/com/tyron/actions/PlaceProvider.java new file mode 100644 index 00000000..5bb8cd38 --- /dev/null +++ b/actions-api/src/main/java/com/tyron/actions/PlaceProvider.java @@ -0,0 +1,6 @@ +package com.tyron.actions; + +public interface PlaceProvider { + + String getPlace(); +} diff --git a/actions-api/src/main/java/com/tyron/actions/Presentation.java b/actions-api/src/main/java/com/tyron/actions/Presentation.java new file mode 100644 index 00000000..3aa531a2 --- /dev/null +++ b/actions-api/src/main/java/com/tyron/actions/Presentation.java @@ -0,0 +1,146 @@ +package com.tyron.actions; + +import android.graphics.drawable.Drawable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * Controls how an action would look in the UI + */ +public class Presentation { + + public static final Supplier NULL_STRING = () -> null; + + /** + * value: String + */ + public static final String PROP_DESCRIPTION = " description"; + /** + * value: Drawable + */ + public static final String PROP_ICON = "icon"; + /** + * value: Boolean + */ + public static final String PROP_VISIBLE = "visible"; + /** + * value: Boolean + */ + public static final String PROP_ENABLED = "enabled"; + + private Drawable mIcon; + private boolean mIsVisible; + private boolean mIsEnabled = true; + + private Supplier mTextSupplier = NULL_STRING; + private Supplier mDescriptionSupplier = NULL_STRING; + + private PropertyChangeSupport mChangeSupport; + + public Presentation() { + + } + + public Presentation(Supplier dynamicText) { + mTextSupplier = dynamicText; + } + + public static Presentation createTemplatePresentation() { + return new Presentation(); + } + + public void addPropertyChangeListener(@NonNull PropertyChangeListener listener) { + PropertyChangeSupport support = mChangeSupport; + if (support == null) { + mChangeSupport = support = new PropertyChangeSupport(this); + } + support.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(@NonNull PropertyChangeListener listener) { + PropertyChangeSupport support = mChangeSupport; + if (support != null) { + mChangeSupport.removePropertyChangeListener(listener); + } + } + + public String getText() { + return mTextSupplier.get(); + } + + public void setText(@Nullable String text) { + setText(() -> text); + } + + public void setText(@NonNull Supplier text) { + mTextSupplier = text; + } + + public void setDescription(@NonNull Supplier description) { + Supplier oldDescription = mDescriptionSupplier; + mDescriptionSupplier = description; + fireObjectPropertyChange(PROP_DESCRIPTION, oldDescription, mDescriptionSupplier); + } + + private void fireObjectPropertyChange(String propertyName, Object oldValue, Object newValue) { +// PropertyChangeSupport support = mChangeSupport; +// if (support != null && !Objects.equals(oldValue, newValue)) { +// support.firePropertyChange(propertyName, oldValue, newValue); +// } + } + + private void fireBooleanPropertyChange(String propertyName, boolean oldValue, boolean newValue) { +// PropertyChangeSupport support = mChangeSupport; +// if (support != null && oldValue != newValue) { +// support.firePropertyChange(propertyName, oldValue, newValue); +// } + } + + @NonNull + @Override + public Presentation clone() { + try { + return (Presentation) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + public void setIcon(Drawable icon) { + mIcon = icon; + } + + public Drawable getIcon() { + return mIcon; + } + + public boolean isVisible() { + return mIsVisible; + } + + public boolean isEnabled() { + return mIsEnabled; + } + + public void setEnabled(boolean enabled) { + boolean old = mIsEnabled; + fireBooleanPropertyChange(PROP_ENABLED, old, enabled); + mIsEnabled = enabled; + } + + public void setVisible(boolean visible) { + boolean old = mIsVisible; + fireBooleanPropertyChange(PROP_VISIBLE, old, visible); + mIsVisible = visible; + } + + public String getDescription() { + return mDescriptionSupplier.get(); + } +} diff --git a/actions-api/src/main/java/com/tyron/actions/impl/ActionManagerImpl.java b/actions-api/src/main/java/com/tyron/actions/impl/ActionManagerImpl.java new file mode 100644 index 00000000..154d41fd --- /dev/null +++ b/actions-api/src/main/java/com/tyron/actions/impl/ActionManagerImpl.java @@ -0,0 +1,226 @@ +package com.tyron.actions.impl; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.os.Build; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; + +import androidx.annotation.NonNull; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.tyron.actions.ActionGroup; +import com.tyron.actions.ActionManager; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.Presentation; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ActionManagerImpl extends ActionManager { + + private final Map mIdToAction = new LinkedHashMap<>(); + private final Map mActionToId = new HashMap<>(); + + @Override + public void fillMenu(DataContext context, + Menu menu, + String place, + boolean isContext, + boolean isToolbar) { + // Inject values + context.putData(CommonDataKeys.CONTEXT, context); + if (Build.VERSION_CODES.P <= Build.VERSION.SDK_INT) { + menu.setGroupDividerEnabled(true); + } + + for (AnAction value : mIdToAction.values()) { + + AnActionEvent event = + new AnActionEvent(context, place, value.getTemplatePresentation(), isContext, + isToolbar); + + event.setPresentation(value.getTemplatePresentation()); + + value.update(event); + + if (event.getPresentation() + .isVisible()) { + fillMenu(menu, value, event); + } + } + } + + + private void fillMenu(Menu menu, AnAction action, AnActionEvent event) { + Presentation presentation = event.getPresentation(); + + MenuItem menuItem; + if (isGroup(action)) { + ActionGroup actionGroup = (ActionGroup) action; + if (!actionGroup.isPopup()) { + fillMenu(View.generateViewId(), menu, actionGroup, event); + return; + } + SubMenu subMenu = menu.addSubMenu(presentation.getText()); + menuItem = subMenu.getItem(); + + AnAction[] children = actionGroup.getChildren(event); + if (children != null) { + for (AnAction child : children) { + event.setPresentation(child.getTemplatePresentation()); + + child.update(event); + + if (event.getPresentation() + .isVisible()) { + if (actionGroup.isPopup()) { + fillSubMenu(subMenu, child, event); + } + } + } + } + } else { + menuItem = menu.add(presentation.getText()); + } + + menuItem.setEnabled(presentation.isEnabled()); + menuItem.setTitle(presentation.getText()); + if (presentation.getIcon() != null) { + menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + } else { + menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + } + menuItem.setIcon(presentation.getIcon()); + menuItem.setOnMenuItemClickListener(item -> performAction(action, event)); + } + + private void fillMenu(int id, Menu menu, ActionGroup group, AnActionEvent event) { + AnAction[] children = group.getChildren(event); + if (children == null) { + return; + } + + for (AnAction child : children) { + event.setPresentation(child.getTemplatePresentation()); + child.update(event); + if (event.getPresentation() + .isVisible()) { + MenuItem add = menu.add(id, Menu.NONE, Menu.NONE, event.getPresentation() + .getText()); + add.setEnabled(event.getPresentation() + .isEnabled()); + add.setIcon(event.getPresentation() + .getIcon()); + add.setOnMenuItemClickListener(item -> performAction(child, event)); + } + } + } + + private void fillSubMenu(SubMenu subMenu, AnAction action, AnActionEvent event) { + Presentation presentation = event.getPresentation(); + + MenuItem menuItem; + + if (isGroup(action)) { + ActionGroup group = (ActionGroup) action; + if (!group.isPopup()) { + fillMenu(View.generateViewId(), subMenu, group, event); + } + + SubMenu subSubMenu = subMenu.addSubMenu(presentation.getText()); + menuItem = subSubMenu.getItem(); + + AnAction[] children = group.getChildren(event); + if (children != null) { + for (AnAction child : children) { + event.setPresentation(child.getTemplatePresentation()); + + child.update(event); + + if (event.getPresentation() + .isVisible()) { + fillSubMenu(subSubMenu, child, event); + } + } + } + } else { + menuItem = subMenu.add(presentation.getText()); + } + + menuItem.setEnabled(presentation.isEnabled()); + if (presentation.getIcon() != null) { + menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + } else { + menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + } + menuItem.setIcon(presentation.getIcon()); + menuItem.setContentDescription(presentation.getDescription()); + menuItem.setOnMenuItemClickListener(item -> performAction(action, event)); + } + + private boolean performAction(AnAction action, AnActionEvent event) { + try { + action.actionPerformed(event); + } catch (Throwable e) { + ClipboardManager clipboardManager = event.getDataContext() + .getSystemService(ClipboardManager.class); + new MaterialAlertDialogBuilder(event.getDataContext()).setTitle( + "Unable to perform action") + .setMessage(e.getMessage()) + .setPositiveButton(android.R.string.ok, null) + .setNegativeButton(android.R.string.copy, + (d, w) -> clipboardManager.setPrimaryClip( + ClipData.newPlainText("Error report", + Log.getStackTraceString(e)))) + .show(); + return false; + } + return true; + } + + @Override + public String getId(@NonNull AnAction action) { + return mActionToId.get(action); + } + + @Override + public void registerAction(@NonNull String actionId, @NonNull AnAction action) { + mIdToAction.put(actionId, action); + mActionToId.put(action, actionId); + } + + @Override + public void unregisterAction(@NonNull String actionId) { + AnAction anAction = mIdToAction.get(actionId); + if (anAction != null) { + mIdToAction.remove(actionId); + mActionToId.remove(anAction); + } + } + + @Override + public void replaceAction(@NonNull String actionId, @NonNull AnAction newAction) { + unregisterAction(actionId); + registerAction(actionId, newAction); + } + + @Override + public boolean isGroup(@NonNull String actionId) { + return isGroup(mIdToAction.get(actionId)); + } + + private boolean isGroup(AnAction action) { + return action instanceof ActionGroup; + } +} diff --git a/actions-api/src/main/java/com/tyron/actions/impl/ActionToolbar.java b/actions-api/src/main/java/com/tyron/actions/impl/ActionToolbar.java new file mode 100644 index 00000000..aaacfafc --- /dev/null +++ b/actions-api/src/main/java/com/tyron/actions/impl/ActionToolbar.java @@ -0,0 +1,28 @@ +package com.tyron.actions.impl; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; + +import com.tyron.actions.DataContext; + +/** + * A toolbar that holds a {@link DataContext} + */ +public class ActionToolbar extends Toolbar { + + public ActionToolbar(@NonNull Context context) { + this(DataContext.wrap(context), null); + } + + public ActionToolbar(@NonNull Context context, @Nullable AttributeSet attrs) { + super(DataContext.wrap(context), attrs); + } + + public ActionToolbar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(DataContext.wrap(context), attrs, defStyleAttr); + } +} diff --git a/actions-api/src/main/java/com/tyron/actions/impl/PresentationFactory.java b/actions-api/src/main/java/com/tyron/actions/impl/PresentationFactory.java new file mode 100644 index 00000000..76fdbd5d --- /dev/null +++ b/actions-api/src/main/java/com/tyron/actions/impl/PresentationFactory.java @@ -0,0 +1,50 @@ +package com.tyron.actions.impl; + +import androidx.annotation.NonNull; + +import com.tyron.actions.AnAction; +import com.tyron.actions.Presentation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.WeakHashMap; + +public class PresentationFactory { + + private final Map mPresentations = new WeakHashMap<>(); + private boolean mNeedRebuild; + + private static final Collection sAllFactories = new ArrayList<>(); + + public PresentationFactory() { + sAllFactories.add(this); + } + + public final Presentation getPresentation(@NonNull AnAction action) { + Presentation presentation = mPresentations.get(action); + if (presentation == null) { + Presentation templatePresentation = action.getTemplatePresentation(); + presentation = templatePresentation.clone(); + presentation = mPresentations.putIfAbsent(action, presentation); + + processPresentation(Objects.requireNonNull(presentation)); + } + return presentation; + } + + protected void processPresentation(@NonNull Presentation presentation) { + + } + + public void reset() { + mPresentations.clear(); + } + + public static void clearPresentationCaches() { + for (PresentationFactory factory : sAllFactories) { + factory.reset(); + } + } +} diff --git a/actions-api/src/main/java/com/tyron/actions/util/DataContextUtils.java b/actions-api/src/main/java/com/tyron/actions/util/DataContextUtils.java new file mode 100644 index 00000000..0554adfa --- /dev/null +++ b/actions-api/src/main/java/com/tyron/actions/util/DataContextUtils.java @@ -0,0 +1,18 @@ +package com.tyron.actions.util; + +import android.content.Context; +import android.view.View; + +import com.tyron.actions.DataContext; + +public class DataContextUtils { + + public static DataContext getDataContext(View view) { + Context context = view.getContext(); + if (context instanceof DataContext) { + return (DataContext) context; + } + + return new DataContext(context); + } +} diff --git a/actions-api/src/test/java/com/tyron/actions/ExampleUnitTest.java b/actions-api/src/test/java/com/tyron/actions/ExampleUnitTest.java new file mode 100644 index 00000000..2f273471 --- /dev/null +++ b/actions-api/src/test/java/com/tyron/actions/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.tyron.actions; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/android-stubs/.gitignore b/android-stubs/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/android-stubs/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/android-stubs/build.gradle b/android-stubs/build.gradle new file mode 100644 index 00000000..5f1dff5c --- /dev/null +++ b/android-stubs/build.gradle @@ -0,0 +1,14 @@ +plugins { + id 'java-library' +} + +dependencies { + compileOnlyApi files('libs/android.jar') + + compileOnly 'androidx.annotation:annotation:1.3.0' +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} \ No newline at end of file diff --git a/android-stubs/libs/android.jar b/android-stubs/libs/android.jar new file mode 100644 index 00000000..af4ff4fd Binary files /dev/null and b/android-stubs/libs/android.jar differ diff --git a/android-stubs/src/main/java/androidx/appcompat/app/AlertDialog.java b/android-stubs/src/main/java/androidx/appcompat/app/AlertDialog.java new file mode 100644 index 00000000..d825f25f --- /dev/null +++ b/android-stubs/src/main/java/androidx/appcompat/app/AlertDialog.java @@ -0,0 +1,241 @@ +package androidx.appcompat.app; + +import android.content.DialogInterface; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListAdapter; + +import androidx.annotation.ArrayRes; +import androidx.annotation.AttrRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; + +import androidx.appcompat.app.AlertDialog; + +public class AlertDialog { + public void dismiss() { + throw new RuntimeException("Stub!"); + } + + public static class Builder { + @NonNull + public AlertDialog create() { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setTitle(@StringRes int titleId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setTitle(@Nullable CharSequence title) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setCustomTitle(@Nullable View customTitleView) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setMessage(@StringRes int messageId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setMessage(@Nullable CharSequence message) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setIcon(@DrawableRes int iconId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setIconAttribute(@AttrRes int attrId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setPositiveButton( + @StringRes int textId, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setPositiveButton( + @Nullable CharSequence text, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setPositiveButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setNegativeButton( + @StringRes int textId, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setNegativeButton( + @Nullable CharSequence text, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setNegativeButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setNeutralButton( + @StringRes int textId, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setNeutralButton( + @Nullable CharSequence text, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setNeutralButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setCancelable(boolean cancelable) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setOnCancelListener( + @Nullable DialogInterface.OnCancelListener onCancelListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setOnDismissListener( + @Nullable DialogInterface.OnDismissListener onDismissListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setOnKeyListener(@Nullable DialogInterface.OnKeyListener onKeyListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setItems( + @ArrayRes int itemsId, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setItems( + @Nullable CharSequence[] items, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setAdapter( + @Nullable final ListAdapter adapter, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setCursor( + @Nullable final Cursor cursor, + @Nullable final DialogInterface.OnClickListener listener, + @NonNull String labelColumn) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setMultiChoiceItems( + @ArrayRes int itemsId, + @Nullable boolean[] checkedItems, + @Nullable final DialogInterface.OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setMultiChoiceItems( + @Nullable CharSequence[] items, + @Nullable boolean[] checkedItems, + @Nullable final DialogInterface.OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setMultiChoiceItems( + @Nullable Cursor cursor, + @NonNull String isCheckedColumn, + @NonNull String labelColumn, + @Nullable final DialogInterface.OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setSingleChoiceItems( + @ArrayRes int itemsId, int checkedItem, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setSingleChoiceItems( + @Nullable Cursor cursor, + int checkedItem, + @NonNull String labelColumn, + @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setSingleChoiceItems( + @Nullable CharSequence[] items, int checkedItem, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setSingleChoiceItems( + @Nullable ListAdapter adapter, int checkedItem, @Nullable final DialogInterface.OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setOnItemSelectedListener( + @Nullable final AdapterView.OnItemSelectedListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setView(int layoutResId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public AlertDialog.Builder setView(@Nullable View view) { + throw new RuntimeException("Stub!"); + } + + public AlertDialog show() { + throw new RuntimeException("Stub!"); + } + } +} diff --git a/android-stubs/src/main/java/androidx/appcompat/widget/Toolbar.java b/android-stubs/src/main/java/androidx/appcompat/widget/Toolbar.java new file mode 100644 index 00000000..cc4ccc8c --- /dev/null +++ b/android-stubs/src/main/java/androidx/appcompat/widget/Toolbar.java @@ -0,0 +1,28 @@ +package androidx.appcompat.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.ViewGroup; + +public class Toolbar extends ViewGroup { + public Toolbar(Context context) { + super(context); + } + + public Toolbar(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public Toolbar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public Toolbar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onLayout(boolean b, int i, int i1, int i2, int i3) { + + } +} diff --git a/android-stubs/src/main/java/androidx/fragment/app/Fragment.java b/android-stubs/src/main/java/androidx/fragment/app/Fragment.java new file mode 100644 index 00000000..7b23ad9e --- /dev/null +++ b/android-stubs/src/main/java/androidx/fragment/app/Fragment.java @@ -0,0 +1,4 @@ +package androidx.fragment.app; + +public class Fragment { +} diff --git a/android-stubs/src/main/java/androidx/lifecycle/LiveData.java b/android-stubs/src/main/java/androidx/lifecycle/LiveData.java new file mode 100644 index 00000000..8ba54efa --- /dev/null +++ b/android-stubs/src/main/java/androidx/lifecycle/LiveData.java @@ -0,0 +1,8 @@ +package androidx.lifecycle; + +public class LiveData { + + public T getValue() { + throw new RuntimeException("Stub"); + } +} diff --git a/android-stubs/src/main/java/androidx/lifecycle/MutableLiveData.java b/android-stubs/src/main/java/androidx/lifecycle/MutableLiveData.java new file mode 100644 index 00000000..39ba5ed2 --- /dev/null +++ b/android-stubs/src/main/java/androidx/lifecycle/MutableLiveData.java @@ -0,0 +1,14 @@ +package androidx.lifecycle; + +import java.util.ArrayList; + +public class MutableLiveData extends LiveData { + + public MutableLiveData(T objects) { + super(); + } + + public void setValue(T value) { + throw new RuntimeException("Stub!"); + } +} diff --git a/android-stubs/src/main/java/androidx/lifecycle/ViewModel.java b/android-stubs/src/main/java/androidx/lifecycle/ViewModel.java new file mode 100644 index 00000000..237a24e9 --- /dev/null +++ b/android-stubs/src/main/java/androidx/lifecycle/ViewModel.java @@ -0,0 +1,4 @@ +package androidx.lifecycle; + +public class ViewModel { +} diff --git a/android-stubs/src/main/java/com/google/android/material/dialog/MaterialAlertDialogBuilder.java b/android-stubs/src/main/java/com/google/android/material/dialog/MaterialAlertDialogBuilder.java new file mode 100644 index 00000000..2209880f --- /dev/null +++ b/android-stubs/src/main/java/com/google/android/material/dialog/MaterialAlertDialogBuilder.java @@ -0,0 +1,340 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.material.dialog; + +import android.content.Context; +import android.content.DialogInterface.OnCancelListener; +import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface.OnDismissListener; +import android.content.DialogInterface.OnKeyListener; +import android.content.DialogInterface.OnMultiChoiceClickListener; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListAdapter; + +import androidx.annotation.ArrayRes; +import androidx.annotation.AttrRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.Px; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AlertDialog; + +/** + * An extension of {@link AlertDialog.Builder} for use with a Material theme (e.g., + * Theme.MaterialComponents). + * + *

This Builder must be used in order for AlertDialog objects to respond to color and shape + * theming provided by Material themes. + * + *

The type of dialog returned is still an {@link AlertDialog}; there is no specific Material + * implementation of {@link AlertDialog}. + */ +public class MaterialAlertDialogBuilder extends AlertDialog.Builder { + + public MaterialAlertDialogBuilder(@NonNull Context context) { + throw new RuntimeException("Stub!"); + } + + public MaterialAlertDialogBuilder(@NonNull Context context, int overrideThemeResId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public AlertDialog create() { + throw new RuntimeException("Stub!"); + } + + @Nullable + public Drawable getBackground() { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackground(@Nullable Drawable background) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackgroundInsetStart(@Px int backgroundInsetStart) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackgroundInsetTop(@Px int backgroundInsetTop) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackgroundInsetEnd(@Px int backgroundInsetEnd) { + throw new RuntimeException("Stub!"); + } + + @NonNull + public MaterialAlertDialogBuilder setBackgroundInsetBottom(@Px int backgroundInsetBottom) { + throw new RuntimeException("Stub!"); + } + + // The following methods are all pass-through methods used to specify the return type for the + // builder chain. + + @NonNull + @Override + public MaterialAlertDialogBuilder setTitle(@StringRes int titleId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setTitle(@Nullable CharSequence title) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setCustomTitle(@Nullable View customTitleView) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMessage(@StringRes int messageId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMessage(@Nullable CharSequence message) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setIcon(@DrawableRes int iconId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setIconAttribute(@AttrRes int attrId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setPositiveButton( + @StringRes int textId, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setPositiveButton( + @Nullable CharSequence text, @Nullable final OnClickListener listener) { + return (MaterialAlertDialogBuilder) super.setPositiveButton(text, listener); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setPositiveButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNegativeButton( + @StringRes int textId, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNegativeButton( + @Nullable CharSequence text, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNegativeButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNeutralButton( + @StringRes int textId, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNeutralButton( + @Nullable CharSequence text, @Nullable final OnClickListener listener) { + return (MaterialAlertDialogBuilder) super.setNeutralButton(text, listener); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setNeutralButtonIcon(@Nullable Drawable icon) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setCancelable(boolean cancelable) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setOnCancelListener( + @Nullable OnCancelListener onCancelListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setOnDismissListener( + @Nullable OnDismissListener onDismissListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setOnKeyListener(@Nullable OnKeyListener onKeyListener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setItems( + @ArrayRes int itemsId, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setItems( + @Nullable CharSequence[] items, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setAdapter( + @Nullable final ListAdapter adapter, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setCursor( + @Nullable final Cursor cursor, + @Nullable final OnClickListener listener, + @NonNull String labelColumn) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMultiChoiceItems( + @ArrayRes int itemsId, + @Nullable boolean[] checkedItems, + @Nullable final OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMultiChoiceItems( + @Nullable CharSequence[] items, + @Nullable boolean[] checkedItems, + @Nullable final OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setMultiChoiceItems( + @Nullable Cursor cursor, + @NonNull String isCheckedColumn, + @NonNull String labelColumn, + @Nullable final OnMultiChoiceClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setSingleChoiceItems( + @ArrayRes int itemsId, int checkedItem, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setSingleChoiceItems( + @Nullable Cursor cursor, + int checkedItem, + @NonNull String labelColumn, + @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setSingleChoiceItems( + @Nullable CharSequence[] items, int checkedItem, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setSingleChoiceItems( + @Nullable ListAdapter adapter, int checkedItem, @Nullable final OnClickListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setOnItemSelectedListener( + @Nullable final AdapterView.OnItemSelectedListener listener) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setView(int layoutResId) { + throw new RuntimeException("Stub!"); + } + + @NonNull + @Override + public MaterialAlertDialogBuilder setView(@Nullable View view) { + throw new RuntimeException("Stub!"); + } +} diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 00000000..0b1cf4e8 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,146 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + applicationId rootProject.ext.applicationId + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + compileOptions { + coreLibraryDesugaringEnabled = true + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + testOptions { + unitTests { + includeAndroidResources = true + } + + unitTests.all { + systemProperty 'robolectric.enabledSdks', '26' + } + } + + buildTypes { + release { + signingConfig signingConfigs.debug + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + packagingOptions { + resources.excludes += "license/*" + + exclude "plugin.properties" + exclude "plugin.xml" + exclude "about.html" + exclude ".api_description" + exclude "about_files/*" + exclude "META-INF/eclipse.inf" + } +} + +configurations.implementation { + exclude group: "org.jetbrains", module: "annotations" +} + +dependencies { + // TODO: Removed these modules, the features that are using these modules + // should be moved into its own module. + + // TODO: language processing should be on its own module + implementation 'org.antlr:antlr4-runtime:4.9.2' + implementation files ( + 'libs/language-base-0.5.0.jar', + 'libs/language-java-0.5.0.jar' + ) + + // TODO: completion providers should not be included on the main module + implementation project(path: ':code-editor') + implementation project(path: ':xml-completion') + implementation project(path: ':build-tools:viewbinding-inject') + implementation project(path: ':build-tools:xml-repository') + implementation project(path: ':java-completion') + // not used for compilation, but for analysis + implementation project(path: ':build-tools:javac') + + implementation project(path: ':language-api') + + implementation project(path: ':build-tools:build-logic') + implementation project(path: ':build-tools:manifmerger') + implementation project(path: ':build-tools:project') + implementation project(path: ':build-tools:logging') + implementation project(path: ':build-tools:jaxp:jaxp-internal') + implementation project(path: ':build-tools:jaxp:xml') + implementation project(path: ':common') + implementation project(path: ':build-tools:lint') + implementation project(path: ':layout-preview') + implementation project(path: ':kotlin-completion') + + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' + implementation project(path: ':dependency-resolver') + + implementation project(path: ':treeview') + + // apis + implementation project(path: ':completion-api') + implementation project(path: ':actions-api') + implementation project(path: ':editor-api') + implementation project(path: ':fileeditor-api') + + // formatters + implementation project(path: ':google-java-format') + + // about + implementation 'com.github.daniel-stoneuk:material-about-library:3.1.2' + + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'androidx.core:core:1.7.0' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'androidx.viewpager2:viewpager2:1.0.0' + implementation 'androidx.viewpager:viewpager:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.2.1' + implementation 'androidx.lifecycle:lifecycle-livedata-core:2.4.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel:2.4.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' + implementation 'androidx.lifecycle:lifecycle-livedata:2.4.1' + implementation 'androidx.fragment:fragment:1.4.1' + implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' + implementation 'androidx.activity:activity:1.4.0' + implementation 'androidx.drawerlayout:drawerlayout:1.1.1' + implementation 'com.github.angads25:filepicker:1.1.1' + + // image loading + implementation 'com.github.bumptech.glide:glide:4.12.0' + + implementation 'androidx.preference:preference:1.2.0' + implementation 'com.github.TutorialsAndroid:crashx:v6.0.19' + implementation project(path: ':eclipse-formatter') + + //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7' + + // testing + testImplementation 'junit:junit:4.13.2' + testImplementation "com.google.truth:truth:1.1.3" + testImplementation 'org.mockito:mockito-core:1.10.19' + testImplementation "org.robolectric:robolectric:4.7.3" + debugImplementation 'androidx.test:core:1.4.0' + debugImplementation 'androidx.fragment:fragment-testing:1.4.1' + + androidTestImplementation 'com.google.truth:truth:1.1.3' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") +} diff --git a/app/libs/google-java-format-1.7.jar b/app/libs/google-java-format-1.7.jar new file mode 100644 index 00000000..953541cf Binary files /dev/null and b/app/libs/google-java-format-1.7.jar differ diff --git a/app/libs/language-base-0.5.0.jar b/app/libs/language-base-0.5.0.jar new file mode 100644 index 00000000..fc0b74a9 Binary files /dev/null and b/app/libs/language-base-0.5.0.jar differ diff --git a/app/libs/language-java-0.5.0.jar b/app/libs/language-java-0.5.0.jar new file mode 100644 index 00000000..8d70aa22 Binary files /dev/null and b/app/libs/language-java-0.5.0.jar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..9e92b373 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,18 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\tools\adt-bundle-windows-x86_64-20131030\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json new file mode 100644 index 00000000..5e8313d1 --- /dev/null +++ b/app/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.tyron.code", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 19, + "versionName": "0.2.8.3 ALPHA", + "outputFile": "app-release.apk" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..729cf5a1 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/.gitignore b/app/src/main/assets/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/app/src/main/assets/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/src/main/assets/android-sources.zip b/app/src/main/assets/android-sources.zip new file mode 100644 index 00000000..47c3f56d Binary files /dev/null and b/app/src/main/assets/android-sources.zip differ diff --git a/app/src/main/assets/android-xml.zip b/app/src/main/assets/android-xml.zip new file mode 100644 index 00000000..5a13fe56 Binary files /dev/null and b/app/src/main/assets/android-xml.zip differ diff --git a/app/src/main/assets/google-maven.zip b/app/src/main/assets/google-maven.zip new file mode 100644 index 00000000..164f1db4 Binary files /dev/null and b/app/src/main/assets/google-maven.zip differ diff --git a/app/src/main/assets/lambda-stubs.zip b/app/src/main/assets/lambda-stubs.zip new file mode 100644 index 00000000..71b6caee Binary files /dev/null and b/app/src/main/assets/lambda-stubs.zip differ diff --git a/app/src/main/assets/project_unit_test.zip b/app/src/main/assets/project_unit_test.zip new file mode 100644 index 00000000..81f1b22a Binary files /dev/null and b/app/src/main/assets/project_unit_test.zip differ diff --git a/app/src/main/assets/rt.zip b/app/src/main/assets/rt.zip new file mode 100644 index 00000000..dc25e4d6 Binary files /dev/null and b/app/src/main/assets/rt.zip differ diff --git a/app/src/main/assets/templates.zip b/app/src/main/assets/templates.zip new file mode 100644 index 00000000..9b65f688 Binary files /dev/null and b/app/src/main/assets/templates.zip differ diff --git a/app/src/main/assets/test_project.zip b/app/src/main/assets/test_project.zip new file mode 100644 index 00000000..6dab2f10 Binary files /dev/null and b/app/src/main/assets/test_project.zip differ diff --git a/app/src/main/assets/testkey.pk8.zip b/app/src/main/assets/testkey.pk8.zip new file mode 100644 index 00000000..c7b4ccf7 Binary files /dev/null and b/app/src/main/assets/testkey.pk8.zip differ diff --git a/app/src/main/assets/testkey.x509.pem.zip b/app/src/main/assets/testkey.x509.pem.zip new file mode 100644 index 00000000..827671b8 Binary files /dev/null and b/app/src/main/assets/testkey.x509.pem.zip differ diff --git a/app/src/main/assets/textmate/QuietLight.tmTheme b/app/src/main/assets/textmate/QuietLight.tmTheme new file mode 100644 index 00000000..a8a5fc4d --- /dev/null +++ b/app/src/main/assets/textmate/QuietLight.tmTheme @@ -0,0 +1,632 @@ + + + + author + Martin Kühl + comment + Based on the Quiet Light theme for Espresso by Ian Beck. + name + Quiet Light + settings + + + settings + + background + #F5F5F5 + caret + #000000 + foreground + #333333 + invisibles + #AAAAAA + lineHighlight + #E4F6D4 + selection + #C9D0D9 + + + + name + Comments + scope + comment, punctuation.definition.comment + settings + + fontStyle + italic + foreground + #AAAAAA + + + + name + Comments: Preprocessor + scope + comment.block.preprocessor + settings + + fontStyle + + foreground + #AAAAAA + + + + name + Comments: Documentation + scope + comment.documentation, comment.block.documentation + settings + + foreground + #448C27 + + + + name + Invalid - Deprecated + scope + invalid.deprecated + settings + + background + #96000014 + + + + name + Invalid - Illegal + scope + invalid.illegal + settings + + background + #96000014 + foreground + #660000 + + + + name + Operators + scope + keyword.operator + settings + + foreground + #777777 + + + + name + Keywords + scope + keyword, storage + settings + + foreground + #4B83CD + + + + name + Types + scope + storage.type, support.type + settings + + foreground + #7A3E9D + + + + name + Language Constants + scope + constant.language, support.constant, variable.language + settings + + foreground + #AB6526 + + + + name + Variables + scope + variable, support.variable + settings + + foreground + #7A3E9D + + + + name + Functions + scope + entity.name.function, support.function + settings + + fontStyle + bold + foreground + #AA3731 + + + + name + Classes + scope + entity.name.type, entity.other.inherited-class, support.class + settings + + fontStyle + bold + foreground + #7A3E9D + + + + name + Exceptions + scope + entity.name.exception + settings + + foreground + #660000 + + + + name + Sections + scope + entity.name.section + settings + + fontStyle + bold + + + + name + Numbers, Characters + scope + constant.numeric, constant.character, constant + settings + + foreground + #AB6526 + + + + name + Strings + scope + string + settings + + foreground + #448C27 + + + + name + Strings: Escape Sequences + scope + constant.character.escape + settings + + foreground + #777777 + + + + name + Strings: Regular Expressions + scope + string.regexp + settings + + foreground + #4B83CD + + + + name + Strings: Symbols + scope + constant.other.symbol + settings + + foreground + #AB6526 + + + + name + Punctuation + scope + punctuation + settings + + foreground + #777777 + + + + name + Embedded Source + scope + string source, text source + settings + + background + #EAEBE6 + + + + name + ----------------------------------- + settings + + + + name + HTML: Doctype Declaration + scope + meta.tag.sgml.doctype, meta.tag.sgml.doctype string, meta.tag.sgml.doctype + entity.name.tag, meta.tag.sgml punctuation.definition.tag.html + + settings + + foreground + #AAAAAA + + + + name + HTML: Tags + scope + meta.tag, punctuation.definition.tag.html, + punctuation.definition.tag.begin.html, punctuation.definition.tag.end.html + + settings + + foreground + #91B3E0 + + + + name + HTML: Tag Names + scope + entity.name.tag + settings + + foreground + #4B83CD + + + + name + HTML: Attribute Names + scope + meta.tag entity.other.attribute-name, entity.other.attribute-name.html + + settings + + foreground + #91B3E0 + + + + name + HTML: Entities + scope + constant.character.entity, punctuation.definition.entity + settings + + foreground + #AB6526 + + + + name + ----------------------------------- + settings + + + + name + CSS: Selectors + scope + meta.selector, meta.selector entity, meta.selector entity punctuation, + entity.name.tag.css + + settings + + foreground + #7A3E9D + + + + name + CSS: Property Names + scope + meta.property-name, support.type.property-name + settings + + foreground + #AB6526 + + + + name + CSS: Property Values + scope + meta.property-value, meta.property-value constant.other, + support.constant.property-value + + settings + + foreground + #448C27 + + + + name + CSS: Important Keyword + scope + keyword.other.important + settings + + fontStyle + bold + + + + name + ----------------------------------- + settings + + + + name + Markup: Changed + scope + markup.changed + settings + + background + #FFFFDD + foreground + #000000 + + + + name + Markup: Deletion + scope + markup.deleted + settings + + background + #FFDDDD + foreground + #000000 + + + + name + Markup: Emphasis + scope + markup.italic + settings + + fontStyle + italic + + + + name + Markup: Error + scope + markup.error + settings + + background + #96000014 + foreground + #660000 + + + + name + Markup: Insertion + scope + markup.inserted + settings + + background + #DDFFDD + foreground + #000000 + + + + name + Markup: Link + scope + meta.link + settings + + foreground + #4B83CD + + + + name + Markup: Output + scope + markup.output, markup.raw + settings + + foreground + #777777 + + + + name + Markup: Prompt + scope + markup.prompt + settings + + foreground + #777777 + + + + name + Markup: Heading + scope + markup.heading + settings + + foreground + #AA3731 + + + + name + Markup: Strong + scope + markup.bold + settings + + fontStyle + bold + + + + name + Markup: Traceback + scope + markup.traceback + settings + + foreground + #660000 + + + + name + Markup: Underline + scope + markup.underline + settings + + fontStyle + underline + + + + name + Markup Quote + scope + markup.quote + settings + + foreground + #7A3E9D + + + + name + Markup Lists + scope + markup.list + settings + + foreground + #4B83CD + + + + name + Markup Styling + scope + markup.bold, markup.italic + settings + + foreground + #448C27 + + + + name + Markup Inline + scope + markup.inline.raw + settings + + fontStyle + + foreground + #AB6526 + + + + name + ----------------------------------- + settings + + + + name + Extra: Diff Range + scope + meta.diff.range, meta.diff.index, meta.separator + settings + + background + #DDDDFF + foreground + #434343 + + + + name + Extra: Diff From + scope + meta.diff.header.from-file + settings + + background + #FFDDDD + foreground + #434343 + + + + name + Extra: Diff To + scope + meta.diff.header.to-file + settings + + background + #DDFFDD + foreground + #434343 + + + + uuid + 231D6A91-5FD1-4CBE-BD2A-0F36C08693F1 + + \ No newline at end of file diff --git a/app/src/main/assets/textmate/darcula.json b/app/src/main/assets/textmate/darcula.json new file mode 100644 index 00000000..06fbcfe8 --- /dev/null +++ b/app/src/main/assets/textmate/darcula.json @@ -0,0 +1,542 @@ +{ + "name": "darcula", + "settings": [ + { + "settings": { + "background": "#1F1A1B", + "foreground": "#cccccc", + "lineHighlight": "#2B2B2B", + "blockLineColor": "#575757", + "currentBlockLineColor": "#7a7a7a", + "selection": "#214283", + "completionWindowBackground": "#1F1A1B", + "completionWindowStroke": "#555555" + } + }, + { + "name": "Package declaration", + "scope": "storage.modifier.package", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Import declaration", + "scope": "storage.modifier.import", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Class names (Identifiers starting with uppercase)", + "scope": "storage.type.java", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Annotation", + "scope": "storage.type.annotation", + "settings": { + "foreground": "#BBB529" + } + }, + { + "name": "Comment", + "scope": "comment", + "settings": { + "foreground": "#707070" + } + }, + { + "name": "Operator Keywords", + "scope": "keyword.operator,keyword.operator.logical,keyword.operator.relational,keyword.operator.assignment,keyword.operator.comparison,keyword.operator.ternary,keyword.operator.arithmetic,keyword.operator.spread", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Strings", + "scope": "string,string.character.escape,string.template.quoted,string.template.quoted.punctuation,string.template.quoted.punctuation.single,string.template.quoted.punctuation.double,string.type.declaration.annotation,string.template.quoted.punctuation.tag", + "settings": { + "foreground": "#6A8759" + } + }, + { + "name": "String Interpolation Begin and End", + "scope": "punctuation.definition.template-expression.begin,punctuation.definition.template-expression.end", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "String Interpolation Body", + "scope": "expression.string,meta.template.expression", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Number", + "scope": "constant.numeric", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "Built-in constant", + "scope": "constant.language,variable.language", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "User-defined constant", + "scope": "constant.character, constant.other", + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "Keyword", + "scope": "keyword,keyword.operator.new,keyword.operator.delete,keyword.operator.static,keyword.operator.this,keyword.operator.expression", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Method return type", + "scope": "meta.method.return-type", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "Method call identifier", + "scope": "meta.method-call", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "Types, Class Types", + "scope": "entity.name.type,meta.return.type,meta.type.annotation,meta.type.parameters,support.type.primitive", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "Storage type", + "scope": "storage,storage.type,storage.modifier,storage.arrow", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Class constructor", + "scope": "class.instance.constructor,new.expr entity.name.type", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Function", + "scope": "support.function, entity.name.function", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Function Types", + "scope": "annotation.meta.ts, annotation.meta.tsx", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Function Argument", + "scope": "variable.parameter, operator.rest.parameters", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "Variable, Property", + "scope": "variable.property,variable.other.property,variable.other.object.property,variable.object.property,support.variable.property", + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "Variable name", + "scope": "entity.name.variable", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "CONSTANT", + "scope": "variable.other.constant", + "settings": { + "foreground": "#9876AA" + } + }, + { + "name": "Module Name", + "scope": "quote.module", + "settings": { + "foreground": "#6A8759" + } + }, + { + "name": "Markup Headings", + "scope": "markup.heading", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Tag name", + "scope": "punctuation.definition.tag.html, punctuation.definition.tag.begin, punctuation.definition.tag.end, entity.name.tag", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Tag attribute", + "scope": "entity.other.attribute-name", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Object Keys", + "scope": "meta.object-literal.key", + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "TypeScript Class Modifiers", + "scope": "storage.modifier.ts", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "TypeScript Type Casting", + "scope": "ts.cast.expr,ts.meta.entity.class.method.new.expr.cast,ts.meta.entity.type.name.new.expr.cast,ts.meta.entity.type.name.var-single-variable.annotation,tsx.cast.expr,tsx.meta.entity.class.method.new.expr.cast,tsx.meta.entity.type.name.new.expr.cast,tsx.meta.entity.type.name.var-single-variable.annotation", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "TypeScript Type Declaration", + "scope": "ts.meta.type.support,ts.meta.type.entity.name,ts.meta.class.inherited-class,tsx.meta.type.support,tsx.meta.type.entity.name,tsx.meta.class.inherited-class,type-declaration,enum-declaration", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "TypeScript Method Declaration", + "scope": "function-declaration,method-declaration,method-overload-declaration,type-fn-type-parameters", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Documentation Block", + "scope": "comment.block.documentation", + "settings": { + "foreground": "#6A8759" + } + }, + { + "name": "Documentation Highlight (JSDoc)", + "scope": "storage.type.class.jsdoc", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Import-Export-All (*) Keyword", + "scope": "constant.language.import-export-all", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Object Key Seperator", + "scope": "objectliteral.key.separator, punctuation.separator.key-value", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Regex", + "scope": "regex", + "settings": { + "fontStyle": " italic" + } + }, + { + "name": "Typescript Namespace", + "scope": "ts.meta.entity.name.namespace,tsx.meta.entity.name.namespace", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Regex Character-class", + "scope": "regex.character-class", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Class Name", + "scope": "entity.name.type.class", + "settings": { + "foreground": "#A9B7C6" + } + }, + { + "name": "Class Inheritances", + "scope": "entity.other.inherited-class", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "Documentation Entity", + "scope": "entity.name.type.instance.jsdoc", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "YAML entity", + "scope": "yaml.entity.name,yaml.string.entity.name", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "YAML string value", + "scope": "yaml.string.out", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Ignored (Exceptions Rules)", + "scope": "meta.brace.square.ts,block.support.module,block.support.type.module,block.support.function.variable,punctuation.definition.typeparameters.begin,punctuation.definition.typeparameters.end", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Regex", + "scope": "string.regexp", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Regex Group/Set", + "scope": "punctuation.definition.group.regexp,punctuation.definition.character-class.regexp", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "Regex Character Class", + "scope": "constant.other.character-class.regexp, constant.character.escape.ts", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Regex Or Operator", + "scope": "expr.regex.or.operator", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Tag string", + "scope": "string.template.tag,string.template.punctuation.tag,string.quoted.punctuation.tag,string.quoted.embedded.tag, string.quoted.double.tag", + "settings": { + "foreground": "#6A8759" + } + }, + { + "name": "Tag function parenthesis", + "scope": "tag.punctuation.begin.arrow.parameters.embedded,tag.punctuation.end.arrow.parameters.embedded", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "Object-literal key class", + "scope": "object-literal.object.member.key.field.other,object-literal.object.member.key.accessor,object-literal.object.member.key.array.brace.square", + "settings": { + "foreground": "#CCCCCC" + } + }, + { + "name": "CSS Property-value", + "scope": "property-list.property-value,property-list.constant", + "settings": { + "foreground": "#A5C261" + } + }, + { + "name": "CSS Property variable", + "scope": "support.type.property-name.variable.css,support.type.property-name.variable.scss,variable.scss", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "CSS Property entity", + "scope": "entity.other.attribute-name.class.css,entity.other.attribute-name.class.scss,entity.other.attribute-name.parent-selector-suffix.css,entity.other.attribute-name.parent-selector-suffix.scss", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "CSS Property-value", + "scope": "property-list.property-value.rgb-value, keyword.other.unit.css,keyword.other.unit.scss", + "settings": { + "foreground": "#7A9EC2" + } + }, + { + "name": "CSS Property-value function", + "scope": "property-list.property-value.function", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "CSS constant variables", + "scope": "support.constant.property-value.css,support.constant.property-value.scss", + "settings": { + "foreground": "#A5C261" + } + }, + { + "name": "CSS Tag", + "scope": "css.entity.name.tag,scss.entity.name.tag", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "CSS ID, Selector", + "scope": "meta.selector.css, entity.attribute-name.id, entity.other.attribute-name.pseudo-class.css,entity.other.attribute-name.pseudo-element.css", + "settings": { + "foreground": "#FFC66D" + } + }, + { + "name": "CSS Keyword", + "scope": "keyword.scss,keyword.css", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Triple-slash Directive Tag", + "scope": "triple-slash.tag", + "settings": { + "foreground": "#CCCCCC", + "fontStyle": "italic" + } + }, + { + "scope": "token.info-token", + "settings": { + "foreground": "#6796e6" + } + }, + { + "scope": "token.warn-token", + "settings": { + "foreground": "#cd9731" + } + }, + { + "scope": "token.error-token", + "settings": { + "foreground": "#f44747" + } + }, + { + "scope": "token.debug-token", + "settings": { + "foreground": "#b267e6" + } + }, + { + "name": "Python operators", + "scope": "keyword.operator.logical.python", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "Dart class type", + "scope": "support.class.dart", + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "PHP variables", + "scope": [ + "variable.language.php", + "variable.other.php" + ], + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "Perl specific", + "scope": [ + "variable.other.readwrite.perl" + ], + "settings": { + "foreground": "#9E7BB0" + } + }, + { + "name": "PHP variables", + "scope": [ + "variable.other.property.php" + ], + "settings": { + "foreground": "#CC8242" + } + }, + { + "name": "PHP variables", + "scope": [ + "support.variable.property.php" + ], + "settings": { + "foreground": "#FFC66D" + } + }, + + { + "name": "XML Namespace prefix", + "scope": "entity.name.tag.namesapce.xml", + "settings": { + "foreground": "#9876AA" + } + } + ] +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/groovy/language-configuration.json b/app/src/main/assets/textmate/groovy/language-configuration.json new file mode 100644 index 00000000..f339aa97 --- /dev/null +++ b/app/src/main/assets/textmate/groovy/language-configuration.json @@ -0,0 +1,43 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": ["/*", "*/"] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], { + "open": "\"", + "close": "\"", + "notIn": ["string"] + }, { + "open": "'", + "close": "'", + "notIn": ["string"] + }, { + "open": "/**", + "close": " */", + "notIn": ["string"] + } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"], + ["<", ">"] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/groovy/syntaxes/groovy.tmLanguage b/app/src/main/assets/textmate/groovy/syntaxes/groovy.tmLanguage new file mode 100644 index 00000000..67d5b086 --- /dev/null +++ b/app/src/main/assets/textmate/groovy/syntaxes/groovy.tmLanguage @@ -0,0 +1,2145 @@ + + + + + fileTypes + + groovy + gvy + + foldingStartMarker + (\{\s*$|^\s*// \{\{\{) + foldingStopMarker + ^\s*(\}|// \}\}\}$) + keyEquivalent + ^~G + name + Groovy + patterns + + + captures + + 1 + + name + punctuation.definition.comment.groovy + + + match + ^(#!).+$\n + name + comment.line.hashbang.groovy + + + captures + + 1 + + name + keyword.other.package.groovy + + 2 + + name + storage.modifier.package.groovy + + 3 + + name + punctuation.terminator.groovy + + + match + ^\s*(package)\b(?:\s*([^ ;$]+)\s*(;)?)? + name + meta.package.groovy + + + begin + (import static)\b\s* + beginCaptures + + 1 + + name + keyword.other.import.static.groovy + + + captures + + 1 + + name + keyword.other.import.groovy + + 2 + + name + storage.modifier.import.groovy + + 3 + + name + punctuation.terminator.groovy + + + contentName + storage.modifier.import.groovy + end + \s*(?:$|(?=%>)(;)) + endCaptures + + 1 + + name + punctuation.terminator.groovy + + + name + meta.import.groovy + patterns + + + match + \. + name + punctuation.separator.groovy + + + match + \s + name + invalid.illegal.character_not_allowed_here.groovy + + + + + begin + (import)\b\s* + beginCaptures + + 1 + + name + keyword.other.import.groovy + + + captures + + 1 + + name + keyword.other.import.groovy + + 2 + + name + storage.modifier.import.groovy + + 3 + + name + punctuation.terminator.groovy + + + contentName + storage.modifier.import.groovy + end + \s*(?:$|(?=%>)|(;)) + endCaptures + + 1 + + name + punctuation.terminator.groovy + + + name + meta.import.groovy + patterns + + + match + \. + name + punctuation.separator.groovy + + + match + \s + name + invalid.illegal.character_not_allowed_here.groovy + + + + + captures + + 1 + + name + keyword.other.import.groovy + + 2 + + name + keyword.other.import.static.groovy + + 3 + + name + storage.modifier.import.groovy + + 4 + + name + punctuation.terminator.groovy + + + match + ^\s*(import)(?:\s+(static)\s+)\b(?:\s*([^ ;$]+)\s*(;)?)? + name + meta.import.groovy + + + include + #groovy + + + repository + + annotations + + patterns + + + begin + (?<!\.)(@[^ (]+)(\() + beginCaptures + + 1 + + name + storage.type.annotation.groovy + + 2 + + name + punctuation.definition.annotation-arguments.begin.groovy + + + end + (\)) + endCaptures + + 1 + + name + punctuation.definition.annotation-arguments.end.groovy + + + name + meta.declaration.annotation.groovy + patterns + + + captures + + 1 + + name + constant.other.key.groovy + + 2 + + name + keyword.operator.assignment.groovy + + + match + (\w*)\s*(=) + + + include + #values + + + match + , + name + punctuation.definition.seperator.groovy + + + + + match + (?<!\.)@\S+ + name + storage.type.annotation.groovy + + + + anonymous-classes-and-new + + begin + \bnew\b + beginCaptures + + 0 + + name + keyword.control.new.groovy + + + end + (?<=\)|\])(?!\s*{)|(?<=})|(?=[;])|$ + patterns + + + begin + (\w+)\s*(?=\[) + beginCaptures + + 1 + + name + storage.type.groovy + + + end + }|(?=\s*(?:,|;|\)))|$ + patterns + + + begin + \[ + end + \] + patterns + + + include + #groovy + + + + + begin + { + end + (?=}) + patterns + + + include + #groovy + + + + + + + begin + (?=\w.*\(?) + end + (?<=\))|$ + patterns + + + include + #object-types + + + begin + \( + beginCaptures + + 1 + + name + storage.type.groovy + + + end + \) + patterns + + + include + #groovy + + + + + + + begin + { + end + } + name + meta.inner-class.groovy + patterns + + + include + #class-body + + + + + + braces + + begin + \{ + end + \} + patterns + + + include + #groovy-code + + + + class + + begin + (?=\w?[\w\s]*(?:class|(?:@)?interface|enum)\s+\w+) + end + } + endCaptures + + 0 + + name + punctuation.section.class.end.groovy + + + name + meta.definition.class.groovy + patterns + + + include + #storage-modifiers + + + include + #comments + + + captures + + 1 + + name + storage.modifier.groovy + + 2 + + name + entity.name.type.class.groovy + + + match + (class|(?:@)?interface|enum)\s+(\w+) + name + meta.class.identifier.groovy + + + begin + extends + beginCaptures + + 0 + + name + storage.modifier.extends.groovy + + + end + (?={|implements) + name + meta.definition.class.inherited.classes.groovy + patterns + + + include + #object-types-inherited + + + include + #comments + + + + + begin + (implements)\s + beginCaptures + + 1 + + name + storage.modifier.implements.groovy + + + end + (?=\s*extends|\{) + name + meta.definition.class.implemented.interfaces.groovy + patterns + + + include + #object-types-inherited + + + include + #comments + + + + + begin + { + end + (?=}) + name + meta.class.body.groovy + patterns + + + include + #class-body + + + + + + class-body + + patterns + + + include + #enum-values + + + include + #constructors + + + include + #groovy + + + + closures + + begin + \{(?=.*?->) + end + \} + patterns + + + begin + (?<=\{)(?=[^\}]*?->) + end + -> + endCaptures + + 0 + + name + keyword.operator.groovy + + + patterns + + + begin + (?!->) + end + (?=->) + name + meta.closure.parameters.groovy + patterns + + + begin + (?!,|->) + end + (?=,|->) + name + meta.closure.parameter.groovy + patterns + + + begin + = + beginCaptures + + 0 + + name + keyword.operator.assignment.groovy + + + end + (?=,|->) + name + meta.parameter.default.groovy + patterns + + + include + #groovy-code + + + + + include + #parameters + + + + + + + + + begin + (?=[^}]) + end + (?=\}) + patterns + + + include + #groovy-code + + + + + + comment-block + + begin + /\* + captures + + 0 + + name + punctuation.definition.comment.groovy + + + end + \*/ + name + comment.block.groovy + + comments + + patterns + + + captures + + 0 + + name + punctuation.definition.comment.groovy + + + match + /\*\*/ + name + comment.block.empty.groovy + + + include + text.html.javadoc + + + include + #comment-block + + + captures + + 1 + + name + punctuation.definition.comment.groovy + + + match + (//).*$\n? + name + comment.line.double-slash.groovy + + + + constants + + patterns + + + match + \b([A-Z][A-Z0-9_]+)\b + name + constant.other.groovy + + + match + \b(true|false|null)\b + name + constant.language.groovy + + + + constructors + + applyEndPatternLast + 1 + begin + (?<=;|^)(?=\s*(?:(?:private|protected|public|native|synchronized|abstract|threadsafe|transient|static|final)\s+)*[A-Z]\w*\() + end + } + patterns + + + include + #method-content + + + + enum-values + + patterns + + + begin + (?<=;|^)\s*\b([A-Z0-9_]+)(?=\s*(?:,|;|}|\(|$)) + beginCaptures + + 1 + + name + constant.enum.name.groovy + + + end + ,|;|(?=})|^(?!\s*\w+\s*(?:,|$)) + patterns + + + begin + \( + end + \) + name + meta.enum.value.groovy + patterns + + + match + , + name + punctuation.definition.seperator.parameter.groovy + + + include + #groovy-code + + + + + + + + groovy + + patterns + + + include + #comments + + + include + #class + + + include + #variables + + + include + #methods + + + include + #annotations + + + include + #groovy-code + + + + groovy-code + + patterns + + + include + #groovy-code-minus-map-keys + + + include + #map-keys + + + + groovy-code-minus-map-keys + + comment + In some situations, maps can't be declared without enclosing []'s, + therefore we create a collection of everything but that + patterns + + + include + #comments + + + include + #annotations + + + include + #support-functions + + + include + #keyword-language + + + include + #values + + + include + #anonymous-classes-and-new + + + include + #keyword-operator + + + include + #types + + + include + #storage-modifiers + + + include + #parens + + + include + #closures + + + include + #braces + + + + keyword + + patterns + + + include + #keyword-operator + + + include + #keyword-language + + + + keyword-language + + patterns + + + match + \b(try|catch|finally|throw)\b + name + keyword.control.exception.groovy + + + match + \b((?<!\.)(?:return|break|continue|default|do|while|for|switch|if|else))\b + name + keyword.control.groovy + + + begin + \bcase\b + beginCaptures + + 0 + + name + keyword.control.groovy + + + end + : + endCaptures + + 0 + + name + punctuation.definition.case-terminator.groovy + + + name + meta.case.groovy + patterns + + + include + #groovy-code-minus-map-keys + + + + + begin + \b(assert)\s + beginCaptures + + 1 + + name + keyword.control.assert.groovy + + + end + $|;|} + name + meta.declaration.assertion.groovy + patterns + + + match + : + name + keyword.operator.assert.expression-seperator.groovy + + + include + #groovy-code-minus-map-keys + + + + + match + \b(throws)\b + name + keyword.other.throws.groovy + + + + keyword-operator + + patterns + + + match + \b(as)\b + name + keyword.operator.as.groovy + + + match + \b(in)\b + name + keyword.operator.in.groovy + + + match + \?\: + name + keyword.operator.elvis.groovy + + + match + \*\: + name + keyword.operator.spreadmap.groovy + + + match + \.\. + name + keyword.operator.range.groovy + + + match + \-> + name + keyword.operator.arrow.groovy + + + match + << + name + keyword.operator.leftshift.groovy + + + match + (?<=\S)\.(?=\S) + name + keyword.operator.navigation.groovy + + + match + (?<=\S)\?\.(?=\S) + name + keyword.operator.safe-navigation.groovy + + + begin + \? + beginCaptures + + 0 + + name + keyword.operator.ternary.groovy + + + end + (?=$|\)|}|]) + name + meta.evaluation.ternary.groovy + patterns + + + match + : + name + keyword.operator.ternary.expression-seperator.groovy + + + include + #groovy-code-minus-map-keys + + + + + match + ==~ + name + keyword.operator.match.groovy + + + match + =~ + name + keyword.operator.find.groovy + + + match + \b(instanceof)\b + name + keyword.operator.instanceof.groovy + + + match + (===|==|!=|<=|>=|<=>|<>|<|>|<<) + name + keyword.operator.comparison.groovy + + + match + = + name + keyword.operator.assignment.groovy + + + match + (\-\-|\+\+) + name + keyword.operator.increment-decrement.groovy + + + match + (\-|\+|\*|\/|%) + name + keyword.operator.arithmetic.groovy + + + match + (!|&&|\|\|) + name + keyword.operator.logical.groovy + + + + language-variables + + patterns + + + match + \b(this|super)\b + name + variable.language.groovy + + + + map-keys + + patterns + + + captures + + 1 + + name + constant.other.key.groovy + + 2 + + name + punctuation.definition.seperator.key-value.groovy + + + match + (\w+)\s*(:) + + + + method-call + + begin + ([\w$]+)(\() + beginCaptures + + 1 + + name + meta.method.groovy + + 2 + + name + punctuation.definition.method-parameters.begin.groovy + + + end + \) + endCaptures + + 0 + + name + punctuation.definition.method-parameters.end.groovy + + + name + meta.method-call.groovy + patterns + + + match + , + name + punctuation.definition.seperator.parameter.groovy + + + include + #groovy-code + + + + method-content + + patterns + + + match + \s + + + include + #annotations + + + begin + (?=(?:\w|<)[^\(]*\s+(?:[\w$]|<)+\s*\() + end + (?=[\w$]+\s*\() + name + meta.method.return-type.java + patterns + + + include + #storage-modifiers + + + include + #types + + + + + begin + ([\w$]+)\s*\( + beginCaptures + + 1 + + name + entity.name.function.java + + + end + \) + name + meta.definition.method.signature.java + patterns + + + begin + (?=[^)]) + end + (?=\)) + name + meta.method.parameters.groovy + patterns + + + begin + (?=[^,)]) + end + (?=,|\)) + name + meta.method.parameter.groovy + patterns + + + match + , + name + punctuation.definition.separator.groovy + + + begin + = + beginCaptures + + 0 + + name + keyword.operator.assignment.groovy + + + end + (?=,|\)) + name + meta.parameter.default.groovy + patterns + + + include + #groovy-code + + + + + include + #parameters + + + + + + + + + begin + (?=<) + end + (?=\s) + name + meta.method.paramerised-type.groovy + patterns + + + begin + < + end + > + name + storage.type.parameters.groovy + patterns + + + include + #types + + + match + , + name + punctuation.definition.seperator.groovy + + + + + + + begin + throws + beginCaptures + + 0 + + name + storage.modifier.groovy + + + end + (?={|;)|^(?=\s*(?:[^{\s]|$)) + name + meta.throwables.groovy + patterns + + + include + #object-types + + + + + begin + { + end + (?=}) + name + meta.method.body.java + patterns + + + include + #groovy-code + + + + + + methods + + applyEndPatternLast + 1 + begin + (?x:(?<=;|^|{)(?=\s* + (?: + (?:private|protected|public|native|synchronized|abstract|threadsafe|transient|static|final) # visibility/modifier + | + (?:def) + | + (?: + (?: + (?:void|boolean|byte|char|short|int|float|long|double) + | + (?:@?(?:[a-zA-Z]\w*\.)*[A-Z]+\w*) # object type + ) + [\[\]]* + (?:<.*>)? + ) + + ) + \s+ + ([^=]+\s+)?\w+\s*\( + )) + end + }|(?=[^{]) + name + meta.definition.method.groovy + patterns + + + include + #method-content + + + + nest_curly + + begin + \{ + captures + + 0 + + name + punctuation.section.scope.groovy + + + end + \} + patterns + + + include + #nest_curly + + + + numbers + + patterns + + + match + ((0(x|X)[0-9a-fA-F]*)|(\+|-)?\b(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)([LlFfUuDdg]|UL|ul)?\b + name + constant.numeric.groovy + + + + object-types + + patterns + + + begin + \b((?:[a-z]\w*\.)*(?:[A-Z]+\w*[a-z]+\w*|UR[LI]))< + end + >|[^\w\s,\?<\[\]] + name + storage.type.generic.groovy + patterns + + + include + #object-types + + + begin + < + comment + This is just to support <>'s with no actual type prefix + end + >|[^\w\s,\[\]<] + name + storage.type.generic.groovy + + + + + begin + \b((?:[a-z]\w*\.)*[A-Z]+\w*[a-z]+\w*)(?=\[) + end + (?=[^\]\s]) + name + storage.type.object.array.groovy + patterns + + + begin + \[ + end + \] + patterns + + + include + #groovy + + + + + + + match + \b(?:[a-zA-Z]\w*\.)*(?:[A-Z]+\w*[a-z]+\w*|UR[LI])\b + name + storage.type.groovy + + + + object-types-inherited + + patterns + + + begin + \b((?:[a-zA-Z]\w*\.)*[A-Z]+\w*[a-z]+\w*)< + end + >|[^\w\s,\?<\[\]] + name + entity.other.inherited-class.groovy + patterns + + + include + #object-types-inherited + + + begin + < + comment + This is just to support <>'s with no actual type prefix + end + >|[^\w\s,\[\]<] + name + storage.type.generic.groovy + + + + + captures + + 1 + + name + keyword.operator.dereference.groovy + + + match + \b(?:[a-zA-Z]\w*(\.))*[A-Z]+\w*[a-z]+\w*\b + name + entity.other.inherited-class.groovy + + + + parameters + + patterns + + + include + #annotations + + + include + #storage-modifiers + + + include + #types + + + match + \w+ + name + variable.parameter.method.groovy + + + + parens + + begin + \( + end + \) + patterns + + + include + #groovy-code + + + + primitive-arrays + + patterns + + + match + \b(?:void|boolean|byte|char|short|int|float|long|double)(\[\])*\b + name + storage.type.primitive.array.groovy + + + + primitive-types + + patterns + + + match + \b(?:void|boolean|byte|char|short|int|float|long|double)\b + name + storage.type.primitive.groovy + + + + regexp + + patterns + + + begin + /(?=[^/]+/([^>]|$)) + beginCaptures + + 0 + + name + punctuation.definition.string.regexp.begin.groovy + + + end + / + endCaptures + + 0 + + name + punctuation.definition.string.regexp.end.groovy + + + name + string.regexp.groovy + patterns + + + match + \\. + name + constant.character.escape.groovy + + + + + begin + ~" + beginCaptures + + 0 + + name + punctuation.definition.string.regexp.begin.groovy + + + end + " + endCaptures + + 0 + + name + punctuation.definition.string.regexp.end.groovy + + + name + string.regexp.compiled.groovy + patterns + + + match + \\. + name + constant.character.escape.groovy + + + + + + storage-modifiers + + patterns + + + match + \b(private|protected|public)\b + name + storage.modifier.access-control.groovy + + + match + \b(static)\b + name + storage.modifier.static.groovy + + + match + \b(final)\b + name + storage.modifier.final.groovy + + + match + \b(native|synchronized|abstract|threadsafe|transient)\b + name + storage.modifier.other.groovy + + + + string-quoted-double + + begin + " + beginCaptures + + 0 + + name + punctuation.definition.string.begin.groovy + + + end + " + endCaptures + + 0 + + name + punctuation.definition.string.end.groovy + + + name + string.quoted.double.groovy + patterns + + + include + #string-quoted-double-contents + + + + string-quoted-double-contents + + patterns + + + match + \\. + name + constant.character.escape.groovy + + + applyEndPatternLast + 1 + begin + \$\w + end + (?=\W) + name + variable.other.interpolated.groovy + patterns + + + match + \w + name + variable.other.interpolated.groovy + + + match + \. + name + keyword.other.dereference.groovy + + + + + begin + \$\{ + captures + + 0 + + name + punctuation.section.embedded.groovy + + + end + \} + name + source.groovy.embedded.source + patterns + + + include + #nest_curly + + + + + + string-quoted-double-multiline + + begin + """ + beginCaptures + + 0 + + name + punctuation.definition.string.begin.groovy + + + end + """ + endCaptures + + 0 + + name + punctuation.definition.string.end.groovy + + + name + string.quoted.double.multiline.groovy + patterns + + + include + #string-quoted-double-contents + + + + string-quoted-single + + begin + ' + beginCaptures + + 0 + + name + punctuation.definition.string.begin.groovy + + + end + ' + endCaptures + + 0 + + name + punctuation.definition.string.end.groovy + + + name + string.quoted.single.groovy + patterns + + + include + #string-quoted-single-contents + + + + string-quoted-single-contents + + patterns + + + match + \\. + name + constant.character.escape.groovy + + + + string-quoted-single-multiline + + begin + ''' + beginCaptures + + 0 + + name + punctuation.definition.string.begin.groovy + + + end + ''' + endCaptures + + 0 + + name + punctuation.definition.string.end.groovy + + + name + string.quoted.single.multiline.groovy + patterns + + + include + #string-quoted-single-contents + + + + strings + + patterns + + + include + #string-quoted-double-multiline + + + include + #string-quoted-single-multiline + + + include + #string-quoted-double + + + include + #string-quoted-single + + + include + #regexp + + + + structures + + begin + \[ + beginCaptures + + 0 + + name + punctuation.definition.structure.begin.groovy + + + end + \] + endCaptures + + 0 + + name + punctuation.definition.structure.end.groovy + + + name + meta.structure.groovy + patterns + + + include + #groovy-code + + + match + , + name + punctuation.definition.separator.groovy + + + + support-functions + + patterns + + + match + (?x)\b(?:sprintf|print(?:f|ln)?)\b + name + support.function.print.groovy + + + match + (?x)\b(?:shouldFail|fail(?:NotEquals)?|ass(?:ume|ert(?:S(?:cript|ame)|N(?:ot(?:Same| + Null)|ull)|Contains|T(?:hat|oString|rue)|Inspect|Equals|False|Length| + ArrayEquals)))\b + name + support.function.testing.groovy + + + + types + + patterns + + + match + \b(def)\b + name + storage.type.def.groovy + + + include + #primitive-types + + + include + #primitive-arrays + + + include + #object-types + + + + values + + patterns + + + include + #language-variables + + + include + #strings + + + include + #numbers + + + include + #constants + + + include + #types + + + include + #structures + + + include + #method-call + + + + variables + + applyEndPatternLast + 1 + patterns + + + begin + (?x:(?= + (?: + (?:private|protected|public|native|synchronized|abstract|threadsafe|transient|static|final) # visibility/modifier + | + (?:def) + | + (?:void|boolean|byte|char|short|int|float|long|double) + | + (?:(?:[a-z]\w*\.)*[A-Z]+\w*) # object type + ) + \s+ + [\w\d_<>\[\],\s]+ + (?:=|$) + + )) + end + ;|$ + name + meta.definition.variable.groovy + patterns + + + match + \s + + + captures + + 1 + + name + constant.variable.groovy + + + match + ([A-Z_0-9]+)\s+(?=\=) + + + captures + + 1 + + name + meta.definition.variable.name.groovy + + + match + (\w[^\s,]*)\s+(?=\=) + + + begin + = + beginCaptures + + 0 + + name + keyword.operator.assignment.groovy + + + end + $ + patterns + + + include + #groovy-code + + + + + captures + + 1 + + name + meta.definition.variable.name.groovy + + + match + (\w[^\s=]*)(?=\s*($|;)) + + + include + #groovy-code + + + + + + + scopeName + source.groovy + uuid + B3A64888-EBBB-4436-8D9E-F1169C5D7613 + + diff --git a/app/src/main/assets/textmate/java/language-configuration.json b/app/src/main/assets/textmate/java/language-configuration.json new file mode 100644 index 00000000..f339aa97 --- /dev/null +++ b/app/src/main/assets/textmate/java/language-configuration.json @@ -0,0 +1,43 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": ["/*", "*/"] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], { + "open": "\"", + "close": "\"", + "notIn": ["string"] + }, { + "open": "'", + "close": "'", + "notIn": ["string"] + }, { + "open": "/**", + "close": " */", + "notIn": ["string"] + } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"], + ["<", ">"] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/java/syntaxes/java.tmLanguage.json b/app/src/main/assets/textmate/java/syntaxes/java.tmLanguage.json new file mode 100644 index 00000000..e80a6b5b --- /dev/null +++ b/app/src/main/assets/textmate/java/syntaxes/java.tmLanguage.json @@ -0,0 +1,1855 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/atom/language-java/blob/master/grammars/java.cson", + "If you want to provide a fix or improvement, please create a pull request against the original repository.", + "Once accepted there, we are happy to receive an update request." + ], + "version": "https://github.com/atom/language-java/commit/29f977dc42a7e2568b39bb6fb34c4ef108eb59b3", + "name": "Java", + "scopeName": "source.java", + "patterns": [ + { + "begin": "\\b(package)\\b\\s*", + "beginCaptures": { + "1": { + "name": "keyword.other.package.java" + } + }, + "end": "\\s*(;)", + "endCaptures": { + "1": { + "name": "punctuation.terminator.java" + } + }, + "name": "meta.package.java", + "contentName": "storage.modifier.package.java", + "patterns": [ + { + "include": "#comments" + }, + { + "match": "(?<=\\.)\\s*\\.|\\.(?=\\s*;)", + "name": "invalid.illegal.character_not_allowed_here.java" + }, + { + "match": "(?", + "endCaptures": { + "0": { + "name": "punctuation.bracket.angle.java" + } + }, + "patterns": [ + { + "match": "\\b(extends|super)\\b", + "name": "storage.modifier.$1.java" + }, + { + "match": "(?>>?|~|\\^)", + "name": "keyword.operator.bitwise.java" + }, + { + "match": "((&|\\^|\\||<<|>>>?)=)", + "name": "keyword.operator.assignment.bitwise.java" + }, + { + "match": "(===?|!=|<=|>=|<>|<|>)", + "name": "keyword.operator.comparison.java" + }, + { + "match": "([+*/%-]=)", + "name": "keyword.operator.assignment.arithmetic.java" + }, + { + "match": "(=)", + "name": "keyword.operator.assignment.java" + }, + { + "match": "(\\-\\-|\\+\\+)", + "name": "keyword.operator.increment-decrement.java" + }, + { + "match": "(\\-|\\+|\\*|\\/|%)", + "name": "keyword.operator.arithmetic.java" + }, + { + "match": "(!|&&|\\|\\|)", + "name": "keyword.operator.logical.java" + }, + { + "match": "(\\||&)", + "name": "keyword.operator.bitwise.java" + }, + { + "match": "\\b(const|goto)\\b", + "name": "keyword.reserved.java" + } + ] + }, + "lambda-expression": { + "patterns": [ + { + "match": "->", + "name": "storage.type.function.arrow.java" + } + ] + }, + "member-variables": { + "begin": "(?=private|protected|public|native|synchronized|abstract|threadsafe|transient|static|final)", + "end": "(?=\\=|;)", + "patterns": [ + { + "include": "#storage-modifiers" + }, + { + "include": "#variables" + }, + { + "include": "#primitive-arrays" + }, + { + "include": "#object-types" + } + ] + }, + "method-call": { + "begin": "(\\.)\\s*([A-Za-z_$][\\w$]*)\\s*(\\()", + "beginCaptures": { + "1": { + "name": "punctuation.separator.period.java" + }, + "2": { + "name": "entity.name.function.java" + }, + "3": { + "name": "punctuation.definition.parameters.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.java" + } + }, + "name": "meta.method-call.java", + "patterns": [ + { + "include": "#code" + } + ] + }, + "methods": { + "begin": "(?!new)(?=[\\w<].*\\s+)(?=([^=/]|/(?!/))+\\()", + "end": "(})|(?=;)", + "endCaptures": { + "1": { + "name": "punctuation.section.method.end.bracket.curly.java" + } + }, + "name": "meta.method.java", + "patterns": [ + { + "include": "#storage-modifiers" + }, + { + "begin": "(\\w+)\\s*(\\()", + "beginCaptures": { + "1": { + "name": "entity.name.function.java" + }, + "2": { + "name": "punctuation.definition.parameters.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.java" + } + }, + "name": "meta.method.identifier.java", + "patterns": [ + { + "include": "#parameters" + }, + { + "include": "#parens" + }, + { + "include": "#comments" + } + ] + }, + { + "include": "#generics" + }, + { + "begin": "(?=\\w.*\\s+\\w+\\s*\\()", + "end": "(?=\\s+\\w+\\s*\\()", + "name": "meta.method.return-type.java", + "patterns": [ + { + "include": "#all-types" + }, + { + "include": "#parens" + }, + { + "include": "#comments" + } + ] + }, + { + "include": "#throws" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.method.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.method.body.java", + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "include": "#comments" + } + ] + }, + "module": { + "begin": "((open)\\s)?(module)\\s+(\\w+)", + "end": "}", + "beginCaptures": { + "1": { + "name": "storage.modifier.java" + }, + "3": { + "name": "storage.modifier.java" + }, + "4": { + "name": "entity.name.type.module.java" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.module.end.bracket.curly.java" + } + }, + "name": "meta.module.java", + "patterns": [ + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.module.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.module.body.java", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#comments-javadoc" + }, + { + "match": "\\b(requires|transitive|exports|opens|to|uses|provides|with)\\b", + "name": "keyword.module.java" + } + ] + } + ] + }, + "numbers": { + "patterns": [ + { + "match": "(?x)\n\\b(?)?(\\()", + "beginCaptures": { + "1": { + "name": "storage.modifier.java" + }, + "2": { + "name": "entity.name.type.record.java" + }, + "3": { + "patterns": [ + { + "include": "#generics" + } + ] + }, + "4": { + "name": "punctuation.definition.parameters.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.java" + } + }, + "name": "meta.record.identifier.java", + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "begin": "(implements)\\s", + "beginCaptures": { + "1": { + "name": "storage.modifier.implements.java" + } + }, + "end": "(?=\\s*\\{)", + "name": "meta.definition.class.implemented.interfaces.java", + "patterns": [ + { + "include": "#object-types-inherited" + }, + { + "include": "#comments" + } + ] + }, + { + "include": "#record-body" + } + ] + }, + "record-body": { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.class.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "name": "meta.record.body.java", + "patterns": [ + { + "include": "#record-constructor" + }, + { + "include": "#class-body" + } + ] + }, + "record-constructor": { + "begin": "(?!new)(?=[\\w<].*\\s+)(?=([^\\(=/]|/(?!/))+(?={))", + "end": "(})|(?=;)", + "endCaptures": { + "1": { + "name": "punctuation.section.method.end.bracket.curly.java" + } + }, + "name": "meta.method.java", + "patterns": [ + { + "include": "#storage-modifiers" + }, + { + "begin": "(\\w+)", + "beginCaptures": { + "1": { + "name": "entity.name.function.java" + } + }, + "end": "(?=\\s*{)", + "name": "meta.method.identifier.java", + "patterns": [ + { + "include": "#comments" + } + ] + }, + { + "include": "#comments" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.method.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.method.body.java", + "patterns": [ + { + "include": "#code" + } + ] + } + ] + }, + "static-initializer": { + "patterns": [ + { + "include": "#anonymous-block-and-instance-initializer" + }, + { + "match": "static", + "name": "storage.modifier.java" + } + ] + }, + "storage-modifiers": { + "match": "\\b(public|private|protected|static|final|native|synchronized|abstract|threadsafe|transient|volatile|default|strictfp|sealed|non-sealed)\\b", + "name": "storage.modifier.java" + }, + "strings": { + "patterns": [ + { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.java" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.java" + } + }, + "name": "string.quoted.double.java", + "patterns": [ + { + "match": "\\\\.", + "name": "constant.character.escape.java" + } + ] + }, + { + "begin": "'", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.java" + } + }, + "end": "'", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.java" + } + }, + "name": "string.quoted.single.java", + "patterns": [ + { + "match": "\\\\.", + "name": "constant.character.escape.java" + } + ] + } + ] + }, + "throws": { + "begin": "throws", + "beginCaptures": { + "0": { + "name": "storage.modifier.java" + } + }, + "end": "(?={|;)", + "name": "meta.throwables.java", + "patterns": [ + { + "match": ",", + "name": "punctuation.separator.delimiter.java" + }, + { + "match": "[a-zA-Z$_][\\.a-zA-Z0-9$_]*", + "name": "storage.type.java" + } + ] + }, + "try-catch-finally": { + "patterns": [ + { + "begin": "\\btry\\b", + "beginCaptures": { + "0": { + "name": "keyword.control.try.java" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.try.end.bracket.curly.java" + } + }, + "name": "meta.try.java", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.section.try.resources.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.section.try.resources.end.bracket.round.java" + } + }, + "name": "meta.try.resources.java", + "patterns": [ + { + "include": "#code" + } + ] + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.try.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.try.body.java", + "patterns": [ + { + "include": "#code" + } + ] + } + ] + }, + { + "begin": "\\b(catch)\\b", + "beginCaptures": { + "1": { + "name": "keyword.control.catch.java" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.catch.end.bracket.curly.java" + } + }, + "name": "meta.catch.java", + "patterns": [ + { + "include": "#comments" + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.parameters.begin.bracket.round.java" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.java" + } + }, + "contentName": "meta.catch.parameters.java", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#storage-modifiers" + }, + { + "begin": "[a-zA-Z$_][\\.a-zA-Z0-9$_]*", + "beginCaptures": { + "0": { + "name": "storage.type.java" + } + }, + "end": "(\\|)|(?=\\))", + "endCaptures": { + "1": { + "name": "punctuation.catch.separator.java" + } + }, + "patterns": [ + { + "include": "#comments" + }, + { + "match": "\\w+", + "captures": { + "0": { + "name": "variable.parameter.java" + } + } + } + ] + } + ] + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.catch.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.catch.body.java", + "patterns": [ + { + "include": "#code" + } + ] + } + ] + }, + { + "begin": "\\bfinally\\b", + "beginCaptures": { + "0": { + "name": "keyword.control.finally.java" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.finally.end.bracket.curly.java" + } + }, + "name": "meta.finally.java", + "patterns": [ + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.finally.begin.bracket.curly.java" + } + }, + "end": "(?=})", + "contentName": "meta.finally.body.java", + "patterns": [ + { + "include": "#code" + } + ] + } + ] + } + ] + }, + "variables": { + "begin": "(?x)\n(?=\n \\b\n (\n (void|boolean|byte|char|short|int|float|long|double)\n |\n (?>(\\w+\\.)*[A-Z_]+\\w*) # e.g. `javax.ws.rs.Response`, or `String`\n )\n \\b\n \\s*\n (\n <[\\w<>,\\.?\\s\\[\\]]*> # e.g. `HashMap`, or `List`\n )?\n \\s*\n (\n (\\[\\])* # int[][]\n )?\n \\s+\n [A-Za-z_$][\\w$]* # At least one identifier after space\n ([\\w\\[\\],$][\\w\\[\\],\\s]*)? # possibly primitive array or additional identifiers\n \\s*(=|:|;)\n)", + "end": "(?=\\=|:|;)", + "name": "meta.definition.variable.java", + "patterns": [ + { + "match": "([A-Za-z$_][\\w$]*)(?=\\s*(\\[\\])*\\s*(;|:|=|,))", + "captures": { + "1": { + "name": "variable.other.definition.java" + } + } + }, + { + "include": "#all-types" + }, + { + "include": "#code" + } + ] + }, + "variables-local": { + "begin": "(?=\\b(var)\\b\\s+[A-Za-z_$][\\w$]*\\s*(=|:|;))", + "end": "(?=\\=|:|;)", + "name": "meta.definition.variable.local.java", + "patterns": [ + { + "match": "\\bvar\\b", + "name": "storage.type.local.java" + }, + { + "match": "([A-Za-z$_][\\w$]*)(?=\\s*(\\[\\])*\\s*(=|:|;))", + "captures": { + "1": { + "name": "variable.other.definition.java" + } + } + }, + { + "include": "#code" + } + ] + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/json/language-configuration.json b/app/src/main/assets/textmate/json/language-configuration.json new file mode 100644 index 00000000..094b2227 --- /dev/null +++ b/app/src/main/assets/textmate/json/language-configuration.json @@ -0,0 +1,38 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": ["/*", "*/"] + }, + "brackets": [ + ["{", "}"], + ["[", "]"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], { + "open": "\"", + "close": "\"", + "notIn": ["string"] + }, { + "open": "'", + "close": "'", + "notIn": ["string"] + }, { + "open": "/**", + "close": " */", + "notIn": ["string"] + } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["\"", "\""] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/json/syntaxes/json.tmLanguage.json b/app/src/main/assets/textmate/json/syntaxes/json.tmLanguage.json new file mode 100644 index 00000000..46ee87f2 --- /dev/null +++ b/app/src/main/assets/textmate/json/syntaxes/json.tmLanguage.json @@ -0,0 +1,182 @@ +{ + "scopeName": "source.json", + "name": "JSON", + "fileTypes": [ + "avsc", + "babelrc", + "bowerrc", + "composer.lock", + "geojson", + "gltf", + "htmlhintrc", + "ipynb", + "jscsrc", + "jshintrc", + "jslintrc", + "json", + "jsonl", + "jsonld", + "languagebabel", + "ldj", + "ldjson", + "Pipfile.lock", + "schema", + "stylintrc", + "template", + "tern-config", + "tern-project", + "tfstate", + "tfstate.backup", + "topojson", + "webapp", + "webmanifest" + ], + "patterns": [ + { + "include": "#value" + } + ], + "repository": { + "array": { + "begin": "\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.array.begin.json" + } + }, + "end": "(,)?[\\s\\n]*(\\])", + "endCaptures": { + "1": { + "name": "invalid.illegal.trailing-array-separator.json" + }, + "2": { + "name": "punctuation.definition.array.end.json" + } + }, + "name": "meta.structure.array.json", + "patterns": [ + { + "include": "#value" + }, + { + "match": ",", + "name": "punctuation.separator.array.json" + }, + { + "match": "[^\\s\\]]", + "name": "invalid.illegal.expected-array-separator.json" + } + ] + }, + "constant": { + "match": "\\b(true|false|null)\\b", + "name": "constant.language.json" + }, + "number": { + "match": "-?(?=[1-9]|0(?!\\d))\\d+(\\.\\d+)?([eE][+-]?\\d+)?", + "name": "constant.numeric.json" + }, + "object": { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.dictionary.begin.json" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.definition.dictionary.end.json" + } + }, + "name": "meta.structure.dictionary.json", + "patterns": [ + { + "begin": "(?=\")", + "end": "(?<=\")", + "name": "meta.structure.dictionary.key.json", + "patterns": [ + { + "include": "#string" + } + ] + }, + { + "begin": ":", + "beginCaptures": { + "0": { + "name": "punctuation.separator.dictionary.key-value.json" + } + }, + "end": "(,)(?=[\\s\\n]*})|(,)|(?=})", + "endCaptures": { + "1": { + "name": "invalid.illegal.trailing-dictionary-separator.json" + }, + "2": { + "name": "punctuation.separator.dictionary.pair.json" + } + }, + "name": "meta.structure.dictionary.value.json", + "patterns": [ + { + "include": "#value" + }, + { + "match": "[^\\s,]", + "name": "invalid.illegal.expected-dictionary-separator.json" + } + ] + }, + { + "match": "[^\\s}]", + "name": "invalid.illegal.expected-dictionary-separator.json" + } + ] + }, + "string": { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.json" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.json" + } + }, + "name": "string.quoted.double.json", + "patterns": [ + { + "match": "(?x)\n\\\\ # a literal backslash\n( # followed by\n [\"\\\\/bfnrt] # one of these characters\n | # or\n u[0-9a-fA-F]{4} # a u and four hex digits\n)", + "name": "constant.character.escape.json" + }, + { + "match": "\\\\.", + "name": "invalid.illegal.unrecognized-string-escape.json" + } + ] + }, + "value": { + "patterns": [ + { + "include": "#constant" + }, + { + "include": "#number" + }, + { + "include": "#string" + }, + { + "include": "#array" + }, + { + "include": "#object" + } + ] + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/kotlin/language-configuration.json b/app/src/main/assets/textmate/kotlin/language-configuration.json new file mode 100644 index 00000000..b742e9e0 --- /dev/null +++ b/app/src/main/assets/textmate/kotlin/language-configuration.json @@ -0,0 +1,35 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": [ "/*", "*/" ] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["<", ">"] + ], + "autoClosingPairs": [ + { "open": "{", "close": "}" }, + { "open": "[", "close": "]" }, + { "open": "(", "close": ")" }, + { "open": "'", "close": "'", "notIn": ["string", "comment"] }, + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "/*", "close": " */", "notIn": ["string"] } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["<", ">"], + ["'", "'"], + ["\"", "\""] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/kotlin/syntaxes/kotlin.tmLanguage b/app/src/main/assets/textmate/kotlin/syntaxes/kotlin.tmLanguage new file mode 100644 index 00000000..4a61ffeb --- /dev/null +++ b/app/src/main/assets/textmate/kotlin/syntaxes/kotlin.tmLanguage @@ -0,0 +1,1067 @@ + + + + + fileTypes + + kt + kts + + foldingStartMarker + (\{\s*(//.*)?$|^\s*// \{\{\{) + foldingStopMarker + ^\s*(\}|// \}\}\}$) + name + Kotlin + patterns + + + include + #comments + + + captures + + 1 + + name + keyword.other.kotlin + + 2 + + name + entity.name.package.kotlin + + + match + ^\s*(package)\b(?:\s*([^ ;$]+)\s*)? + + + captures + + 1 + + name + keyword.other.import.kotlin + + 2 + + name + storage.modifier.import.java + + 3 + + name + keyword.other.kotlin + + 4 + + name + entity.name.type + + + match + ^\s*(import)\s+([^ $.]+(?:\.(?:[`][^$`]+[`]|[^` $.]+))+)(?:\s+(as)\s+([`][^$`]+[`]|[^` $.]+))? + name + meta.import.kotlin + + + include + #code + + + repository + + annotations + + patterns + + + begin + (@[^ (]+)(\()? + beginCaptures + + 1 + + name + storage.type.annotation.kotlin + + 2 + + name + punctuation.definition.annotation-arguments.begin.kotlin + + + end + (\)|\s|$) + endCaptures + + 1 + + name + punctuation.definition.annotation-arguments.end.kotlin + + + name + meta.declaration.annotation.kotlin + patterns + + + captures + + 1 + + name + constant.other.key.kotlin + + 2 + + name + keyword.operator.assignment.kotlin + + + match + (\w*)\s*(=) + + + include + #code + + + match + , + name + punctuation.seperator.property.kotlin + + + + + match + @\w* + name + storage.type.annotation.kotlin + + + + builtin-functions + + patterns + + + match + \b(apply|also|let|takeIf|run|takeUnless|with|print|println)\b\s*(?={|\() + captures + + 1 + + name + support.function.kotlin + + + + + match + \b(mutableListOf|listOf|mutableMapOf|mapOf|mutableSetOf|setOf)\b\s*(?={|\() + captures + + 1 + + name + support.function.kotlin + + + + + + comments + + patterns + + + captures + + 0 + + name + punctuation.definition.comment.kotlin + + + match + /\*\*/ + name + comment.block.empty.kotlin + + + include + #comments-inline + + + + comments-inline + + patterns + + + begin + /\* + captures + + 0 + + name + punctuation.definition.comment.kotlin + + + end + \*/ + name + comment.block.kotlin + + + captures + + 1 + + name + comment.line.double-slash.kotlin + + 2 + + name + punctuation.definition.comment.kotlin + + + match + \s*((//).*$\n?) + + + + class-literal + + begin + (?=\b(?:class|interface|object)\s+\w+)\b + end + (?=\}|$) + name + meta.class.kotlin + patterns + + + include + #keyword-literal + + + begin + \b(class|object|interface)\b\s+(\w+) + beginCaptures + + 1 + + name + storage.modifier.kotlin + + 2 + + name + entity.name.class.kotlin + + + end + (?=\{|\(|:|$) + patterns + + + include + #keyword-literal + + + include + #annotations + + + include + #types + + + + + begin + (:)\s*(\w+) + beginCaptures + + 1 + + name + keyword.operator.declaration.kotlin + + 2 + + name + entity.other.inherited-class.kotlin + + + end + (?={|=|$) + patterns + + + include + #types + + + + + include + #braces + + + include + #parens + + + + literal-functions + + begin + (?=\b(?:fun)\b) + end + (?=$|=|\}) + patterns + + + begin + \b(fun)\b + beginCaptures + + 1 + + name + keyword.other.kotlin + + + end + (?=\() + patterns + + + captures + + 2 + + name + entity.name.function.kotlin + + + match + ([\.<\?>\w]+\.)?(\w+|(`[^`]*`)) + + + include + #types + + + + + begin + (:) + beginCaptures + + 1 + + name + keyword.operator.declaration.kotlin + + + end + (?={|=|$) + patterns + + + include + #types + + + + + include + #parens + + + include + #braces + + + + parameters + + patterns + + + begin + (:) + beginCaptures + + 1 + + name + keyword.operator.declaration.kotlin + + + end + (?=,|=|\)) + patterns + + + include + #types + + + + + match + \w+(?=:) + name + variable.parameter.function.kotlin + + + include + #keyword-literal + + + + keyword-literal + + patterns + + + match + (\!in|\!is|as\?) + name + keyword.operator.kotlin + + + match + \b(in|is|as|assert)\b + name + keyword.operator.kotlin + + + match + \b(const)\b + name + storage.modifier.kotlin + + + match + \b(val|var)\b + name + storage.type.kotlin + + + match + \b(\_)\b + name + punctuation.definition.variable.kotlin + + + match + \b(data|inline|tailrec|operator|infix|typealias|reified)\b + name + storage.type.kotlin + + + match + \b(external|public|private|protected|internal|abstract|final|sealed|enum|open|annotation|override|vararg|typealias|expect|actual|suspend|yield|out|in)\b + name + storage.modifier.kotlin + + + match + \b(try|catch|finally|throw)\b + name + keyword.control.catch-exception.kotlin + + + match + \b(if|else|when)\b + name + keyword.control.conditional.kotlin + + + match + \b(while|for|do|return|break|continue)\b + name + keyword.control.kotlin + + + match + \b(constructor|init)\b + name + entity.name.function.constructor + + + match + \b(companion|object)\b + name + storage.type.kotlin + + + + keyword-operator + + patterns + + + match + \b(and|or|not|inv)\b + name + keyword.operator.bitwise.kotlin + + + match + (==|!=|===|!==|<=|>=|<|>) + name + keyword.operator.comparison.kotlin + + + match + (=) + name + keyword.operator.assignment.kotlin + + + match + (:(?!:)) + name + keyword.operator.declaration.kotlin + + + match + (\?:) + name + keyword.operator.elvis.kotlin + + + match + (\-\-|\+\+) + name + keyword.operator.increment-decrement.kotlin + + + match + (\-|\+|\*|\/|%) + name + keyword.operator.arithmetic.kotlin + + + match + (\+\=|\-\=|\*\=|\/\=) + name + keyword.operator.arithmetic.assign.kotlin + + + match + (\!|\&\&|\|\|) + name + keyword.operator.logical.kotlin + + + match + (\.\.) + name + keyword.operator.range.kotlin + + + + keyword-punctuation + + patterns + + + match + (::) + name + punctuation.accessor.reference.kotlin + + + match + (\?\.) + name + punctuation.accessor.dot.safe.kotlin + + + match + (\.) + name + punctuation.accessor.dot.kotlin + + + match + (\,) + name + punctuation.seperator.kotlin + + + match + (\;) + name + punctuation.terminator.kotlin + + + + keyword-constant + + patterns + + + match + \b(true|false|null|class)\b + name + constant.language.kotlin + + + match + \b(this|super)\b + name + variable.language.kotlin + + + match + \b(0(x|X)[0-9A-Fa-f_]*)[L]?\b + name + constant.numeric.hex.kotlin + + + match + \b(0(b|B)[0-1_]*)[L]?\b + name + constant.numeric.binary.kotlin + + + match + \b([0-9][0-9_]*\.[0-9][0-9_]*[fFL]?)\b + name + constant.numeric.float.kotlin + + + match + \b([0-9][0-9_]*[fFL]?)\b + name + constant.numeric.integer.kotlin + + + + literal-string + + patterns + + + begin + " + beginCaptures + + 0 + + name + punctuation.definition.string.begin.kotlin + + + end + " + endCaptures + + 0 + + name + punctuation.definition.string.end.kotlin + + + name + string.quoted.double.kotlin + patterns + + + include + #string-content + + + + + + literal-raw-string + + patterns + + + begin + """ + beginCaptures + + 0 + + name + punctuation.definition.string.begin.kotlin + + + end + """ + endCaptures + + 0 + + name + punctuation.definition.string.end.kotlin + + + name + string.quoted.triple.kotlin + patterns + + + include + #string-content + + + + + + string-content + + patterns + + + name + constant.character.escape.newline.kotlin + match + \\\s*\n + + + name + constant.character.escape.kotlin + match + \\(x[\da-fA-F]{2}|u[\da-fA-F]{4}|.) + + + begin + (\$)(\{) + beginCaptures + + 1 + + name + punctuation.definition.keyword.kotlin + + 2 + + name + punctuation.section.block.begin.kotlin + + + end + \} + endCaptures + + 0 + + name + punctuation.section.block.end.kotlin + + + name + entity.string.template.element.kotlin + patterns + + + include + #code + + + + + + types + + patterns + + + match + \b(Nothing|Any|Unit|String|CharSequence|Int|Boolean|Char|Long|Double|Float|Short|Byte|Array|List|Map|Set|dynamic)\b(\?)? + name + support.class.kotlin + + + match + \b(IntArray|BooleanArray|CharArray|LongArray|DoubleArray|FloatArray|ShortArray|ByteArray)\b(\?)? + name + support.class.kotlin + + + match + ((?:[a-zA-Z]\w*\.)*[A-Z]+\w*[a-z]+\w*)(\?) + name + entity.name.type.class.kotlin + patterns + + + include + #keyword-punctuation + + + include + #types + + + + + match + \b(?:[a-z]\w*(\.))*[A-Z]+\w*\b + captures + + 1 + + name + keyword.operator.dereference.kotlin + + + name + entity.name.type.class.kotlin + + + begin + \( + beginCaptures + + 0 + + name + punctuation.section.group.begin.kotlin + + + end + \) + endCaptures + + 0 + + name + punctuation.section.group.end.kotlin + + + patterns + + + include + #types + + + + + include + #keyword-punctuation + + + include + #keyword-operator + + + + parens + + patterns + + + begin + \( + beginCaptures + + 0 + + name + punctuation.section.group.begin.kotlin + + + end + \) + endCaptures + + 0 + + name + punctuation.section.group.end.kotlin + + + name + meta.group.kotlin + patterns + + + include + #keyword-punctuation + + + include + #parameters + + + include + #code + + + + + + braces + + patterns + + + begin + \{ + beginCaptures + + 0 + + name + punctuation.section.group.begin.kotlin + + + end + \} + endCaptures + + 0 + + name + punctuation.section.group.end.kotlin + + + name + meta.block.kotlin + patterns + + + include + #code + + + + + + brackets + + patterns + + + begin + \[ + beginCaptures + + 0 + + name + punctuation.section.brackets.begin.kotlin + + + end + \] + endCaptures + + 0 + + name + punctuation.section.brackets.end.kotlin + + + name + meta.brackets.kotlin + patterns + + + include + #code + + + + + + code + + patterns + + + include + #comments + + + include + #comments-inline + + + include + #annotations + + + include + #class-literal + + + include + #parens + + + include + #braces + + + include + #brackets + + + include + #keyword-literal + + + include + #types + + + include + #keyword-operator + + + include + #keyword-constant + + + include + #keyword-punctuation + + + include + #builtin-functions + + + include + #literal-functions + + + include + #builtin-classes + + + include + #literal-raw-string + + + include + #literal-string + + + + + scopeName + source.kotlin + uuid + d9380650-5edc-447d-8dbd-98838c7d0adf + + \ No newline at end of file diff --git a/app/src/main/assets/textmate/xml/language-configuration.json b/app/src/main/assets/textmate/xml/language-configuration.json new file mode 100644 index 00000000..00f0bd9a --- /dev/null +++ b/app/src/main/assets/textmate/xml/language-configuration.json @@ -0,0 +1,42 @@ +{ + "comments": { + "lineComment": [""], + }, + "brackets": [ + ["<", "/>"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], { + "open": "\"", + "close": "\"", + "notIn": ["string"] + }, { + "open": "'", + "close": "'", + "notIn": ["string"] + }, { + "open": "/**", + "close": " */", + "notIn": ["string"] + } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"], + ["<", ">"] + ], + "folding": { + "offSide": false, + "markers": { + "start": "^\\s*//\\s*#region", + "end": "^\\s*//\\s*#endregion" + } + } +} \ No newline at end of file diff --git a/app/src/main/assets/textmate/xml/syntaxes/xml.tmLanguage.json b/app/src/main/assets/textmate/xml/syntaxes/xml.tmLanguage.json new file mode 100644 index 00000000..d493c6a3 --- /dev/null +++ b/app/src/main/assets/textmate/xml/syntaxes/xml.tmLanguage.json @@ -0,0 +1,430 @@ +{ + "scopeName": "text.xml", + "name": "XML", + "fileTypes": [ + "aiml", + "atom", + "axml", + "bpmn", + "config", + "cpt", + "csl", + "csproj", + "csproj.user", + "dae", + "dia", + "dita", + "ditamap", + "dtml", + "fodg", + "fodp", + "fods", + "fodt", + "fsproj", + "fxml", + "gir", + "glade", + "gpx", + "graphml", + "icls", + "iml", + "isml", + "jmx", + "jsp", + "kml", + "kst", + "launch", + "menu", + "mxml", + "nunit", + "nuspec", + "opml", + "owl", + "pom", + "ppj", + "proj", + "pt", + "pubxml", + "pubxml.user", + "rdf", + "rng", + "rss", + "sdf", + "shproj", + "siml", + "sld", + "storyboard", + "StyleCop", + "svg", + "targets", + "tld", + "vbox", + "vbox-prev", + "vbproj", + "vbproj.user", + "vcproj", + "vcproj.filters", + "vcxproj", + "vcxproj.filters", + "wixmsp", + "wixmst", + "wixobj", + "wixout", + "wsdl", + "wxs", + "xaml", + "xbl", + "xib", + "xlf", + "xliff", + "xml", + "xpdl", + "xsd", + "xul", + "ui" + ], + "firstLineMatch": "(?x)\n# XML declaration\n(?:\n ^ <\\? xml\n\n # VersionInfo\n \\s+ version\n \\s* = \\s*\n (['\"])\n 1 \\. [0-9]+\n \\1\n\n # EncodingDecl\n (?:\n \\s+ encoding\n \\s* = \\s*\n\n # EncName\n (['\"])\n [A-Za-z]\n [-A-Za-z0-9._]*\n \\2\n )?\n\n # SDDecl\n (?:\n \\s+ standalone\n \\s* = \\s*\n (['\"])\n (?:yes|no)\n \\3\n )?\n\n \\s* \\?>\n)\n|\n# Modeline\n(?i:\n # Emacs\n -\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n xml\n (?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n xml\n (?=\\s|:|$)\n)", + "patterns": [ + { + "begin": "(<\\?)\\s*([-_a-zA-Z0-9]+)", + "captures": { + "1": { + "name": "punctuation.definition.tag.xml" + }, + "2": { + "name": "entity.name.tag.xml" + } + }, + "end": "(\\?>)", + "name": "meta.tag.preprocessor.xml", + "patterns": [ + { + "match": " ([a-zA-Z-]+)", + "name": "entity.other.attribute-name.xml" + }, + { + "include": "#doublequotedString" + }, + { + "include": "#singlequotedString" + } + ] + }, + { + "begin": "()", + "name": "meta.tag.sgml.doctype.xml", + "patterns": [ + { + "include": "#internalSubset" + } + ] + }, + { + "include": "#comments" + }, + { + "begin": "(<)((?:([-_a-zA-Z0-9]+)(:))?([-_a-zA-Z0-9:]+))(?=(\\s[^>]*)?>)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.tag.xml" + }, + "2": { + "name": "entity.name.tag.xml" + }, + "3": { + "name": "entity.name.tag.namespace.xml" + }, + "4": { + "name": "punctuation.separator.namespace.xml" + }, + "5": { + "name": "entity.name.tag.localname.xml" + } + }, + "end": "(>)()", + "endCaptures": { + "1": { + "name": "punctuation.definition.tag.xml" + }, + "2": { + "name": "punctuation.definition.tag.xml" + }, + "3": { + "name": "entity.name.tag.xml" + }, + "4": { + "name": "entity.name.tag.namespace.xml" + }, + "5": { + "name": "punctuation.separator.namespace.xml" + }, + "6": { + "name": "entity.name.tag.localname.xml" + }, + "7": { + "name": "punctuation.definition.tag.xml" + } + }, + "name": "meta.tag.no-content.xml", + "patterns": [ + { + "include": "#tagStuff" + } + ] + }, + { + "begin": "()", + "name": "meta.tag.xml", + "patterns": [ + { + "include": "#tagStuff" + } + ] + }, + { + "include": "#entity" + }, + { + "include": "#bare-ampersand" + }, + { + "begin": "<%@", + "beginCaptures": { + "0": { + "name": "punctuation.section.embedded.begin.xml" + } + }, + "end": "%>", + "endCaptures": { + "0": { + "name": "punctuation.section.embedded.end.xml" + } + }, + "name": "source.java-props.embedded.xml", + "patterns": [ + { + "match": "page|include|taglib", + "name": "keyword.other.page-props.xml" + } + ] + }, + { + "begin": "<%[!=]?(?!--)", + "beginCaptures": { + "0": { + "name": "punctuation.section.embedded.begin.xml" + } + }, + "end": "(?!--)%>", + "endCaptures": { + "0": { + "name": "punctuation.section.embedded.end.xml" + } + }, + "name": "source.java.embedded.xml", + "patterns": [ + { + "include": "source.java" + } + ] + }, + { + "begin": "", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.xml" + } + }, + "name": "string.unquoted.cdata.xml" + } + ], + "repository": { + "EntityDecl": { + "begin": "()", + "patterns": [ + { + "include": "#doublequotedString" + }, + { + "include": "#singlequotedString" + } + ] + }, + "bare-ampersand": { + "match": "&", + "name": "invalid.illegal.bad-ampersand.xml" + }, + "doublequotedString": { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.xml" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.xml" + } + }, + "name": "string.quoted.double.xml", + "patterns": [ + { + "include": "#entity" + }, + { + "include": "#bare-ampersand" + } + ] + }, + "entity": { + "captures": { + "1": { + "name": "punctuation.definition.constant.xml" + }, + "3": { + "name": "punctuation.definition.constant.xml" + } + }, + "match": "(&)([:a-zA-Z_][:a-zA-Z0-9_.-]*|#[0-9]+|#x[0-9a-fA-F]+)(;)", + "name": "constant.character.entity.xml" + }, + "internalSubset": { + "begin": "(\\[)", + "captures": { + "1": { + "name": "punctuation.definition.constant.xml" + } + }, + "end": "(\\])", + "name": "meta.internalsubset.xml", + "patterns": [ + { + "include": "#EntityDecl" + }, + { + "include": "#parameterEntity" + }, + { + "include": "#comments" + } + ] + }, + "parameterEntity": { + "captures": { + "1": { + "name": "punctuation.definition.constant.xml" + }, + "3": { + "name": "punctuation.definition.constant.xml" + } + }, + "match": "(%)([:a-zA-Z_][:a-zA-Z0-9_.-]*)(;)", + "name": "constant.character.parameter-entity.xml" + }, + "singlequotedString": { + "begin": "'", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.xml" + } + }, + "end": "'", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.xml" + } + }, + "name": "string.quoted.single.xml", + "patterns": [ + { + "include": "#entity" + }, + { + "include": "#bare-ampersand" + } + ] + }, + "tagStuff": { + "patterns": [ + { + "captures": { + "1": { + "name": "entity.other.attribute-name.namespace.xml" + }, + "2": { + "name": "entity.other.attribute-name.xml" + }, + "3": { + "name": "punctuation.separator.namespace.xml" + }, + "4": { + "name": "entity.other.attribute-name.localname.xml" + } + }, + "match": "(?:^|\\s+)(?:([-\\w.]+)((:)))?([-\\w.:]+)\\s*=" + }, + { + "include": "#doublequotedString" + }, + { + "include": "#singlequotedString" + } + ] + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ApplicationLoader.java b/app/src/main/java/com/tyron/code/ApplicationLoader.java new file mode 100644 index 00000000..8c2d380c --- /dev/null +++ b/app/src/main/java/com/tyron/code/ApplicationLoader.java @@ -0,0 +1,193 @@ +package com.tyron.code; + +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Build; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.preference.PreferenceManager; + +import com.developer.crashx.config.CrashConfig; +import com.tyron.actions.ActionManager; +import com.tyron.builder.BuildModule; +import com.tyron.code.ui.editor.action.CloseAllEditorAction; +import com.tyron.code.ui.editor.action.CloseFileEditorAction; +import com.tyron.code.ui.editor.action.CloseOtherEditorAction; +import com.tyron.code.ui.editor.action.DiagnosticInfoAction; +import com.tyron.code.ui.editor.action.PreviewLayoutAction; +import com.tyron.code.ui.editor.action.text.TextActionGroup; +import com.tyron.code.ui.file.action.NewFileActionGroup; +import com.tyron.code.ui.file.action.ImportFileActionGroup; +import com.tyron.code.ui.file.action.file.DeleteFileAction; +import com.tyron.code.ui.main.action.compile.CompileActionGroup; +import com.tyron.code.ui.main.action.debug.DebugActionGroup; +import com.tyron.code.ui.main.action.other.FormatAction; +import com.tyron.code.ui.main.action.other.OpenSettingsAction; +import com.tyron.code.ui.main.action.project.ProjectActionGroup; +import com.tyron.code.ui.settings.ApplicationSettingsFragment; +import com.tyron.common.ApplicationProvider; +import com.tyron.completion.CompletionProvider; +import com.tyron.completion.index.CompilerService; +import com.tyron.completion.java.CompletionModule; +import com.tyron.completion.java.JavaCompilerProvider; +import com.tyron.completion.java.JavaCompletionProvider; +import com.tyron.completion.main.CompletionEngine; +import com.tyron.completion.xml.XmlCompletionModule; +import com.tyron.completion.xml.XmlIndexProvider; +import com.tyron.completion.xml.providers.AndroidManifestCompletionProvider; +import com.tyron.completion.xml.providers.EmptyCompletionProvider; +import com.tyron.completion.xml.providers.LayoutXmlCompletionProvider; +import com.tyron.editor.selection.ExpandSelectionProvider; +import com.tyron.kotlin_completion.KotlinCompletionModule; +import com.tyron.language.fileTypes.FileTypeManager; +import com.tyron.language.java.JavaFileType; +import com.tyron.language.java.JavaLanguage; +import com.tyron.language.xml.XmlFileType; +import com.tyron.language.xml.XmlLanguage; +import com.tyron.selection.java.JavaExpandSelectionProvider; +import com.tyron.selection.xml.XmlExpandSelectionProvider; + +import com.tyron.code.event.EventManager; + +public class ApplicationLoader extends Application { + + private static ApplicationLoader sInstance; + + public static ApplicationLoader getInstance() { + return sInstance; + } + + private EventManager mEventManager; + + // no memory leaks since applicationContext is a singleton + public static Context applicationContext; + + @Override + public void onCreate() { + super.onCreate(); + setupTheme(); + + mEventManager = new EventManager(); + + sInstance = this; + applicationContext = this; + ApplicationProvider.initialize(applicationContext); + + CompletionModule.initialize(applicationContext); + XmlCompletionModule.initialize(applicationContext); + BuildModule.initialize(applicationContext); + + CrashConfig.Builder.create() + .backgroundMode(CrashConfig.BACKGROUND_MODE_SHOW_CUSTOM) + .enabled(true) + .showErrorDetails(true) + .showRestartButton(true) + .logErrorOnRestart(true) + .trackActivities(true) + .apply(); + + runStartup(); + } + + /** + * Can be used to communicate within the application globally + * @return the EventManager + */ + @NonNull + public EventManager getEventManager() { + return mEventManager; + } + + private void setupTheme() { + ApplicationSettingsFragment.ThemeProvider provider = new ApplicationSettingsFragment.ThemeProvider(this); + int theme = provider.getThemeFromPreferences(); + AppCompatDelegate.setDefaultNightMode(theme); + } + + private void runStartup() { + StartupManager startupManager = new StartupManager(); + startupManager.addStartupActivity(() -> { + FileTypeManager manager = FileTypeManager.getInstance(); + manager.registerFileType(JavaFileType.INSTANCE); + manager.registerFileType(XmlFileType.INSTANCE); + }); + startupManager.addStartupActivity(() -> { + ExpandSelectionProvider.registerProvider(JavaLanguage.INSTANCE, + new JavaExpandSelectionProvider()); + ExpandSelectionProvider.registerProvider(XmlLanguage.INSTANCE, + new XmlExpandSelectionProvider()); + }); + startupManager.addStartupActivity(() -> { + CompletionEngine engine = CompletionEngine.getInstance(); + CompilerService index = CompilerService.getInstance(); + if (index.isEmpty()) { + index.registerIndexProvider(JavaCompilerProvider.KEY, new JavaCompilerProvider()); + index.registerIndexProvider(XmlIndexProvider.KEY, new XmlIndexProvider()); + } + }); + startupManager.addStartupActivity(() -> { + CompletionProvider.registerCompletionProvider(JavaLanguage.INSTANCE, + new JavaCompletionProvider()); + CompletionProvider.registerCompletionProvider(XmlLanguage.INSTANCE, + new LayoutXmlCompletionProvider()); + CompletionProvider.registerCompletionProvider(XmlLanguage.INSTANCE, + new AndroidManifestCompletionProvider()); + CompletionProvider.registerCompletionProvider(XmlLanguage.INSTANCE, + new EmptyCompletionProvider()); + }); + startupManager.addStartupActivity(() -> { + ActionManager manager = ActionManager.getInstance(); + // main toolbar actions + manager.registerAction(CompileActionGroup.ID, new CompileActionGroup()); + manager.registerAction(ProjectActionGroup.ID, new ProjectActionGroup()); + manager.registerAction(PreviewLayoutAction.ID, new PreviewLayoutAction()); + manager.registerAction(OpenSettingsAction.ID, new OpenSettingsAction()); + manager.registerAction(FormatAction.ID, new FormatAction()); + manager.registerAction(DebugActionGroup.ID, new DebugActionGroup()); + + // editor tab actions + manager.registerAction(CloseFileEditorAction.ID, new CloseFileEditorAction()); + manager.registerAction(CloseOtherEditorAction.ID, new CloseOtherEditorAction()); + manager.registerAction(CloseAllEditorAction.ID, new CloseAllEditorAction()); + + // editor actions + manager.registerAction(TextActionGroup.ID, new TextActionGroup()); + manager.registerAction(DiagnosticInfoAction.ID, new DiagnosticInfoAction()); + + // file manager actions + manager.registerAction(NewFileActionGroup.ID, new NewFileActionGroup()); + manager.registerAction(DeleteFileAction.ID, new DeleteFileAction()); + if(Build.VERSION.SDK_INT mStartupActivities; + + public StartupManager() { + mStartupActivities = new ArrayDeque<>(); + } + + public void addStartupActivity(Runnable runnable) { + mStartupActivities.add(runnable); + } + + public void startup() { + Runnable runnable = mStartupActivities.remove(); + while (runnable != null) { + runnable.run(); + if (!mStartupActivities.isEmpty()) { + runnable = mStartupActivities.remove(); + } else { + runnable = null; + } + } + } +} diff --git a/app/src/main/java/com/tyron/code/analyzer/BaseTextmateAnalyzer.java b/app/src/main/java/com/tyron/code/analyzer/BaseTextmateAnalyzer.java new file mode 100644 index 00000000..cf53c3a0 --- /dev/null +++ b/app/src/main/java/com/tyron/code/analyzer/BaseTextmateAnalyzer.java @@ -0,0 +1,192 @@ +package com.tyron.code.analyzer; + +import android.graphics.Color; +import android.os.Bundle; + +import androidx.annotation.NonNull; + +import com.tyron.code.language.textmate.BaseIncrementalAnalyzeManager; +import com.tyron.code.language.textmate.CodeBlockUtils; +import com.tyron.editor.Editor; + +import java.io.InputStream; +import java.io.Reader; +import java.util.List; + +import io.github.rosemoe.sora.lang.analysis.IncrementalAnalyzeManager; +import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; +import io.github.rosemoe.sora.lang.styling.CodeBlock; +import io.github.rosemoe.sora.lang.styling.MappedSpans; +import io.github.rosemoe.sora.lang.styling.Span; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora.lang.styling.TextStyle; +import io.github.rosemoe.sora.langs.textmate.analyzer.TextMateAnalyzer; +import io.github.rosemoe.sora.langs.textmate.folding.FoldingRegions; +import io.github.rosemoe.sora.langs.textmate.folding.IndentRange; +import io.github.rosemoe.sora.text.Content; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.textmate.core.grammar.IGrammar; +import io.github.rosemoe.sora.textmate.core.grammar.ITokenizeLineResult2; +import io.github.rosemoe.sora.textmate.core.grammar.StackElement; +import io.github.rosemoe.sora.textmate.core.internal.grammar.StackElementMetadata; +import io.github.rosemoe.sora.textmate.core.registry.Registry; +import io.github.rosemoe.sora.textmate.core.theme.FontStyle; +import io.github.rosemoe.sora.textmate.core.theme.IRawTheme; +import io.github.rosemoe.sora.textmate.core.theme.Theme; +import io.github.rosemoe.sora.textmate.languageconfiguration.ILanguageConfiguration; +import io.github.rosemoe.sora.textmate.languageconfiguration.internal.LanguageConfigurator; +import io.github.rosemoe.sora.textmate.languageconfiguration.internal.supports.Folding; +import io.github.rosemoe.sora.util.ArrayList; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; + +/** + * A text mate analyzer which does not use a TextMateLanguage + */ +public class BaseTextmateAnalyzer extends BaseIncrementalAnalyzeManager { + + /** + * Maximum for code block count + */ + public static int MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT = 5000; + + private final Registry registry = new Registry(); + private final IGrammar grammar; + private Theme theme; + private final Editor editor; + private final ILanguageConfiguration configuration; + + public BaseTextmateAnalyzer(Editor editor, + String grammarName, + InputStream grammarIns, + Reader languageConfiguration, + IRawTheme theme) throws Exception { + registry.setTheme(theme); + this.editor = editor; + this.theme = Theme.createFromRawTheme(theme); + this.grammar = registry.loadGrammarFromPathSync(grammarName, grammarIns); + if (languageConfiguration != null) { + LanguageConfigurator languageConfigurator = + new LanguageConfigurator(languageConfiguration); + configuration = languageConfigurator.getLanguageConfiguration(); + } else { + configuration = null; + } + } + + public void analyzeCodeBlocks(Content model, + List blocks, + CodeBlockAnalyzeDelegate delegate) { + if (configuration == null) { + return; + } + Folding folding = configuration.getFolding(); + if (folding == null) { + return; + } + try { + FoldingRegions foldingRegions = + CodeBlockUtils.computeRanges(model, editor.getTabCount(), folding.getOffSide(), + folding, MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT, + delegate); + for (int i = 0; i < foldingRegions.length() && !delegate.isCancelled(); i++) { + int startLine = foldingRegions.getStartLineNumber(i); + int endLine = foldingRegions.getEndLineNumber(i); + if (startLine != endLine) { + CodeBlock codeBlock = new CodeBlock(); + codeBlock.toBottomOfEndLine = true; + codeBlock.startLine = startLine; + codeBlock.endLine = endLine; + + // It's safe here to use raw data because the Content is only held by this + // thread + int length = model.getColumnCount(startLine); + char[] chars = model.getLine(startLine) + .getRawData(); + + codeBlock.startColumn = + IndentRange.computeStartColumn(chars, length, editor.useTab() ? 1 : editor.getTabCount()); + codeBlock.endColumn = codeBlock.startColumn; + blocks.add(codeBlock); + } + } + blocks.sort(CodeBlock.COMPARATOR_END); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public StackElement getInitialState() { + return null; + } + + @Override + public boolean stateEquals(StackElement state, StackElement another) { + if (state == null && another == null) { + return true; + } + if (state != null && another != null) { + return state.equals(another); + } + return false; + } + + @Override + public Result tokenizeLine(CharSequence lineC, StackElement state) { + String line = lineC.toString(); + ArrayList tokens = new ArrayList<>(); + ITokenizeLineResult2 lineTokens = grammar.tokenizeLine2(line, state); + int tokensLength = lineTokens.getTokens().length / 2; + for (int i = 0; i < tokensLength; i++) { + int startIndex = lineTokens.getTokens()[2 * i]; + if (i == 0 && startIndex != 0) { + tokens.add(Span.obtain(0, EditorColorScheme.TEXT_NORMAL)); + } + int metadata = lineTokens.getTokens()[2 * i + 1]; + int foreground = StackElementMetadata.getForeground(metadata); + int fontStyle = StackElementMetadata.getFontStyle(metadata); + Span span = Span.obtain(startIndex, TextStyle + .makeStyle(foreground + 255, 0, (fontStyle & FontStyle.Bold) != 0, + (fontStyle & FontStyle.Italic) != 0, false)); + + if ((fontStyle & FontStyle.Underline) != 0) { + String color = theme.getColor(foreground); + if (color != null) { + span.underlineColor = Color.parseColor(color); + } + } + + tokens.add(span); + } + return new Result<>(lineTokens.getRuleStack(), null, tokens); + } + + @Override + public List generateSpansForLine(LineTokenizeResult tokens) { + return null; + } + + @Override + public List computeBlocks(Content text, CodeBlockAnalyzeDelegate delegate) { + List list = new java.util.ArrayList<>(); + analyzeCodeBlocks(text, list, delegate); + return list; + } + + @Override + public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { + if (!extraArguments.getBoolean("loaded", false)) { + return; + } + super.reset(content, extraArguments); + } + + public void updateTheme(IRawTheme theme) { + registry.setTheme(theme); + this.theme = Theme.createFromRawTheme(theme); + } + + protected Theme getTheme() { + return theme; + } +} diff --git a/app/src/main/java/com/tyron/code/analyzer/DiagnosticTextmateAnalyzer.java b/app/src/main/java/com/tyron/code/analyzer/DiagnosticTextmateAnalyzer.java new file mode 100644 index 00000000..8ecb0bd4 --- /dev/null +++ b/app/src/main/java/com/tyron/code/analyzer/DiagnosticTextmateAnalyzer.java @@ -0,0 +1,168 @@ +package com.tyron.code.analyzer; + +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; +import com.tyron.code.language.DiagnosticSpanMapUpdater; +import com.tyron.code.language.HighlightUtil; +import com.tyron.editor.Editor; + +import java.io.InputStream; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.analysis.StyleReceiver; +import io.github.rosemoe.sora.lang.styling.CodeBlock; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.Content; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.textmate.core.grammar.StackElement; +import io.github.rosemoe.sora.textmate.core.theme.IRawTheme; + +public abstract class DiagnosticTextmateAnalyzer extends BaseTextmateAnalyzer { + + protected List mDiagnostics = new ArrayList<>(); + private boolean mShouldAnalyzeInBg; + private ContentReference ref; + protected Editor mEditor; + + private final Consumer mStyleModifier; + + public DiagnosticTextmateAnalyzer(Editor editor, + String grammarName, + InputStream grammarIns, + Reader languageConfiguration, + IRawTheme theme) throws Exception { + super(editor, grammarName, grammarIns, languageConfiguration, theme); + mEditor = editor; + mStyleModifier = this::modifyStyles; + } + + protected void modifyStyles(Styles styles) { + if (styles == null) { + return; + } + HighlightUtil.clearDiagnostics(styles); + HighlightUtil.markDiagnostics(mEditor, mDiagnostics, styles); + } + + public void setDiagnostics(CodeEditorView codeEditorView, + @NonNull List diagnostics) { + mDiagnostics = diagnostics; + } + + @Override + public void setReceiver(@Nullable StyleReceiver receiver) { + if (receiver != null) { + super.setReceiver(new StyleReceiverInterceptor(receiver, mStyleModifier)); + } else { + super.setReceiver(null); + } + } + + @Override + public void insert(CharPosition start, CharPosition end, CharSequence insertedText) { + super.insert(start, end, insertedText); + + if (getExtraArguments().getBoolean("bg", false)) { + if (!mShouldAnalyzeInBg) { + mShouldAnalyzeInBg = true; + } else { + analyzeInBackground(ref.getReference()); + } + } + + if (start.getLine() != end.getLine()) { + DiagnosticSpanMapUpdater + .shiftDiagnosticsOnMultiLineInsert(mDiagnostics, ref, start, end); + } else { + DiagnosticSpanMapUpdater + .shiftDiagnosticsOnSingleLineInsert(mDiagnostics, ref, start, end); + } + } + + @Override + public void delete(CharPosition start, CharPosition end, CharSequence deletedText) { + super.delete(start, end, deletedText); + + if (getExtraArguments().getBoolean("bg", false)) { + if (!mShouldAnalyzeInBg) { + mShouldAnalyzeInBg = true; + } else { + analyzeInBackground(ref.getReference()); + } + } + + if (start.getLine() != end.getLine()) { + DiagnosticSpanMapUpdater + .shiftDiagnosticsOnMultiLineDelete(mDiagnostics, ref, start, end); + } else { + DiagnosticSpanMapUpdater + .shiftDiagnosticsOnSingleLineDelete(mDiagnostics, ref, start, end); + } + } + + @Override + public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { + // it CAN be null. + //noinspection ConstantConditions + if (extraArguments == null) { + extraArguments = new Bundle(); + } + this.ref = content; + super.reset(content, extraArguments); + } + + @Override + public Bundle getExtraArguments() { + Bundle extraArguments = super.getExtraArguments(); + if (extraArguments == null) { + extraArguments = new Bundle(); + } + return extraArguments; + } + + @Override + public void destroy() { + mEditor = null; + super.destroy(); + } + + public abstract void analyzeInBackground(CharSequence contents); + + public void rerunWithoutBg() { + mShouldAnalyzeInBg = false; + super.rerun(); + } + + public void rerunWithBg() { + super.rerun(); + + analyzeInBackground(ref.getReference()); + } + + public static class StyleReceiverInterceptor implements StyleReceiver { + + private final StyleReceiver mReceiver; + private final Consumer mConsumer; + + public StyleReceiverInterceptor(@NonNull StyleReceiver base, @NonNull Consumer consumer) { + mReceiver = base; + mConsumer = consumer; + } + + @Override + public void setStyles(AnalyzeManager sourceManager, Styles styles) { + mConsumer.accept(styles); + mReceiver.setStyles(sourceManager, styles); + } + } +} diff --git a/app/src/main/java/com/tyron/code/analyzer/SemanticAnalyzeManager.java b/app/src/main/java/com/tyron/code/analyzer/SemanticAnalyzeManager.java new file mode 100644 index 00000000..71342707 --- /dev/null +++ b/app/src/main/java/com/tyron/code/analyzer/SemanticAnalyzeManager.java @@ -0,0 +1,81 @@ +package com.tyron.code.analyzer; + +import com.tyron.code.analyzer.semantic.SemanticToken; +import com.tyron.code.language.HighlightUtil; +import com.tyron.editor.CharPosition; +import com.tyron.editor.Content; +import com.tyron.editor.Editor; + +import java.io.InputStream; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +import io.github.rosemoe.sora.lang.styling.MappedSpans; +import io.github.rosemoe.sora.lang.styling.Span; +import io.github.rosemoe.sora.lang.styling.Spans; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora.lang.styling.TextStyle; +import io.github.rosemoe.sora.textmate.core.grammar.StackElement; +import io.github.rosemoe.sora.textmate.core.theme.FontStyle; +import io.github.rosemoe.sora.textmate.core.theme.IRawTheme; +import io.github.rosemoe.sora.textmate.core.theme.ThemeTrieElementRule; + +public abstract class SemanticAnalyzeManager extends DiagnosticTextmateAnalyzer { + + private List mSemanticTokens; + + public SemanticAnalyzeManager(Editor editor, + String grammarName, + InputStream grammarIns, + Reader languageConfiguration, + IRawTheme theme) throws Exception { + super(editor, grammarName, grammarIns, languageConfiguration, theme); + } + + public abstract List analyzeSpansAsync(CharSequence contents); + + @Override + public void insert(io.github.rosemoe.sora.text.CharPosition start, + io.github.rosemoe.sora.text.CharPosition end, + CharSequence insertedText) { + super.insert(start, end, insertedText); + } + + @Override + protected void modifyStyles(Styles styles) { + super.modifyStyles(styles); + + Content content = mEditor.getContent(); + + if (mSemanticTokens != null) { + for (int i = mSemanticTokens.size() - 1; i >= 0; i--) { + SemanticToken token = mSemanticTokens.get(i); + if (token.getOffset() > content.length()) { + continue; + } + CharPosition start = mEditor.getCharPosition(token.getOffset()); + CharPosition end = + mEditor.getCharPosition(token.getOffset() + token.getLength()); + + Span span = Span.obtain(0, getStyle(token)); + HighlightUtil.replaceSpan(styles, span, start.getLine(), start.getColumn(), + end.getLine(), end.getColumn()); + } + } + } + + private long getStyle(SemanticToken token) { + List match = getTheme().match(token.getTokenType().toString()); + if (!match.isEmpty()) { + ThemeTrieElementRule next = match.iterator().next(); + int foreground = next.foreground; + int fontStyle = next.fontStyle; + return TextStyle.makeStyle(foreground + 255, 0, + (fontStyle & FontStyle.Bold) == FontStyle.Bold, + (fontStyle & FontStyle.Italic) == FontStyle.Italic, + false); + } + return 0; + } +} diff --git a/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticHighlighter.java b/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticHighlighter.java new file mode 100644 index 00000000..e2ea859e --- /dev/null +++ b/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticHighlighter.java @@ -0,0 +1,48 @@ +package com.tyron.code.analyzer.semantic; + +import com.sun.source.tree.Tree; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class SemanticHighlighter extends TreePathScanner { + + private final AtomicBoolean mCancelFlag = new AtomicBoolean(false); + + public void cancel() { + mCancelFlag.set(true); + } + + @Override + public Void scan(Tree tree, Void unused) { + if (mCancelFlag.get()) { + return null; + } + return super.scan(tree, unused); + } + + @Override + public Void scan(Iterable iterable, Void unused) { + if (mCancelFlag.get()) { + return null; + } + return super.scan(iterable, unused); + } + + @Override + public Void scan(TreePath treePath, Void unused) { + if (mCancelFlag.get()) { + return null; + } + return super.scan(treePath, unused); + } + + @Override + public Void reduce(Void unused, Void r1) { + if (mCancelFlag.get()) { + return null; + } + return super.reduce(unused, r1); + } +} diff --git a/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticToken.java b/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticToken.java new file mode 100644 index 00000000..029af8a2 --- /dev/null +++ b/app/src/main/java/com/tyron/code/analyzer/semantic/SemanticToken.java @@ -0,0 +1,50 @@ +package com.tyron.code.analyzer.semantic; + +import androidx.annotation.NonNull; + +import com.tyron.code.language.java.JavaSemanticHighlighter; + +public class SemanticToken { + private final TokenType tokenType; + private final int tokenModifiers; + private final int offset; + private final int length; + + public SemanticToken(int offset, int length, TokenType tokenType, int tokenModifiers) { + this.offset = offset; + this.length = length; + this.tokenType = tokenType; + this.tokenModifiers = tokenModifiers; + } + + public TokenType getTokenType() { + return tokenType; + } + + public int getTokenModifiers() { + return tokenModifiers; + } + + public int getOffset() { + return offset; + } + + public int getLength() { + return length; + } + + @NonNull + @Override + public String toString() { + return "SemanticToken{" + + "tokenType=" + + tokenType + + ", tokenModifiers=" + + tokenModifiers + + ", offset=" + + offset + + ", length=" + + length + + '}'; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/analyzer/semantic/TokenType.java b/app/src/main/java/com/tyron/code/analyzer/semantic/TokenType.java new file mode 100644 index 00000000..62794616 --- /dev/null +++ b/app/src/main/java/com/tyron/code/analyzer/semantic/TokenType.java @@ -0,0 +1,35 @@ +package com.tyron.code.analyzer.semantic; + +import androidx.annotation.NonNull; + +public class TokenType { + + public static final TokenType UNKNOWN = create("token.error-token"); + + public static TokenType create(String scope, String... fallbackScopes) { + return new TokenType(scope, fallbackScopes); + } + + private final String scope; + private final String[] fallbackScopes; + + public TokenType(@NonNull String scope, String[] fallbackScopes) { + this.scope = scope; + this.fallbackScopes = fallbackScopes; + } + + public String getScope() { + return scope; + } + + public String[] getFallbackScopes() { + return fallbackScopes; + } + + @NonNull + @Override + public String toString() { + return scope; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/event/Event.java b/app/src/main/java/com/tyron/code/event/Event.java new file mode 100644 index 00000000..01e3a0f0 --- /dev/null +++ b/app/src/main/java/com/tyron/code/event/Event.java @@ -0,0 +1,76 @@ +package com.tyron.code.event; + +import androidx.annotation.NonNull; + +import java.util.Objects; + +import io.github.rosemoe.sora.event.ClickEvent; +import io.github.rosemoe.sora.event.DoubleClickEvent; +import io.github.rosemoe.sora.event.EditorKeyEvent; +import io.github.rosemoe.sora.event.LongPressEvent; +import io.github.rosemoe.sora.widget.CodeEditor; + +/** + * An Event object describes an event of editor. + * It includes several attributes such as time and the editor object. + * Subclasses of Event will define their own fields or methods. + * + * @author Rosemoe + */ +public abstract class Event { + + private final long mEventTime; + private boolean mIntercepted; + + public Event() { + this(System.currentTimeMillis()); + } + + public Event(long eventTime) { + mEventTime = eventTime; + mIntercepted = false; + } + + /** + * Get event time + */ + public long getEventTime() { + return mEventTime; + } + + /** + * Check whether this event can be intercepted (so that the event is not sent to other + * receivers after being intercepted) + * Intercept-able events: + * + * @see LongPressEvent + * @see ClickEvent + * @see DoubleClickEvent + * @see EditorKeyEvent + */ + public boolean canIntercept() { + return false; + } + + /** + * Intercept the event. + *

+ * Make sure {@link #canIntercept()} returns true. Otherwise, an + * {@link UnsupportedOperationException} + * will be thrown. + */ + public void intercept() { + if (!canIntercept()) { + throw new UnsupportedOperationException("intercept() not supported"); + } + mIntercepted = true; + } + + /** + * Check whether this event is intercepted + */ + public boolean isIntercepted() { + return mIntercepted; + } + +} diff --git a/app/src/main/java/com/tyron/code/event/EventManager.java b/app/src/main/java/com/tyron/code/event/EventManager.java new file mode 100644 index 00000000..36caab62 --- /dev/null +++ b/app/src/main/java/com/tyron/code/event/EventManager.java @@ -0,0 +1,302 @@ +package com.tyron.code.event; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Vector; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * This class manages event dispatching in editor. + * Users can either register their event receivers here or dispatch event to + * the receivers in this manager. + *

+ * There may be several EventManagers in one editor instance. For example, each plugin + * will have it own EventManager and the editor also has a root event manager for external + * listeners. + *

+ * Note that the event type must be exact. That's to say, you need to use a terminal class instead + * of using its parent classes. For instance, if you register a receiver with the event type + * {@link Event}, + * no event will be sent to your receiver. + * + * @author Rosemoe + */ +public final class EventManager { + + @SuppressWarnings("rawtypes") + private final Map, Receivers> receivers; + private final ReadWriteLock lock; + private boolean enabled; + private final EventManager parent; + private final List children; + private boolean detached = false; + + /** + * Create an EventManager with no parent + */ + public EventManager() { + this(null); + } + + /** + * Create an EventManager with the given parent. + * Null for no parent. + */ + public EventManager(@Nullable EventManager parent) { + receivers = new HashMap<>(); + this.parent = parent; + lock = new ReentrantReadWriteLock(); + children = new Vector<>(); + if (parent != null) { + parent.children.add(this); + } + } + + /** + * Set enabled. + * Disabled EventManager will not deliver event to its subscribers or children. + * Root EventManager can not be disabled. + */ + public void setEnabled(boolean enabled) { + if (parent == null && !enabled) { + throw new IllegalStateException( + "The event manager is set to be root, and can not be disabled"); + } + this.enabled = enabled; + } + + /** + * Check is the manager enabled + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Get root node + */ + public EventManager getRootManager() { + checkDetached(); + return parent == null ? this : parent.getRootManager(); + } + + /** + * Get root manager and dispatch the given event + * + * @see #dispatchEvent(Event) + */ + public boolean dispatchEventFromRoot(@NonNull T event) { + return getRootManager().dispatchEvent(event); + } + + /** + * Detached from parent. + * This manager will not receive future events from parent + */ + public void detach() { + if (parent == null) { + throw new IllegalStateException("root manager can not be detached"); + } + checkDetached(); + detached = true; + parent.children.remove(this); + } + + private void checkDetached() { + if (detached) { + throw new IllegalStateException("already detached"); + } + } + + /** + * Get receivers container of a given event type safely + */ + @NonNull + @SuppressWarnings("unchecked") + Receivers getReceivers(@NonNull Class type) { + lock.readLock() + .lock(); + Receivers result; + try { + result = receivers.get(type); + } finally { + lock.readLock() + .unlock(); + } + if (result == null) { + lock.writeLock() + .lock(); + try { + result = receivers.get(type); + if (result == null) { + result = new Receivers<>(); + receivers.put(type, result); + } + } finally { + lock.writeLock() + .unlock(); + } + } + return result; + } + + /** + * Register a receiver of the given event. + * + * @param eventType Event type to be received + * @param receiver Receiver of event + * @param Event type + */ + public SubscriptionReceipt subscribeEvent(@NonNull Class eventType, + @NonNull EventReceiver receiver) { + Receivers receivers = getReceivers(eventType); + receivers.lock.writeLock() + .lock(); + try { + List> list = receivers.receivers; + if (list.contains(receiver)) { + throw new IllegalArgumentException( + "the receiver is already registered for this type"); + } + list.add(receiver); + } finally { + receivers.lock.writeLock() + .unlock(); + } + return new SubscriptionReceipt<>(eventType, receiver, this); + } + + public void subscribeEvent(@NonNull LifecycleOwner lifecycleOwner, + @NonNull Class eventType, + @NonNull EventReceiver receiver) { + SubscriptionReceipt receipt = subscribeEvent(eventType, receiver); + lifecycleOwner.getLifecycle().addObserver(new DefaultLifecycleObserver() { + @Override + public void onDestroy(@NonNull LifecycleOwner owner) { + receipt.unsubscribe(); + lifecycleOwner.getLifecycle().removeObserver(this); + } + }); + } + + /** + * Dispatch the given event to its receivers registered in this manager. + * + * @param event Event to dispatch + * @param Event type + * @return Whether the event's intercept flag is set + */ + @SuppressWarnings("unchecked") + public boolean dispatchEvent(@NonNull T event) { + // Safe cast + Receivers receivers = getReceivers((Class) event.getClass()); + receivers.lock.readLock() + .lock(); + EventReceiver[] receiverArr; + int count; + try { + count = receivers.receivers.size(); + receiverArr = obtainBuffer(count); + receivers.receivers.toArray(receiverArr); + } finally { + receivers.lock.readLock() + .unlock(); + } + List> unsubscribedReceivers = null; + try { + Unsubscribe unsubscribe = new Unsubscribe(); + for (int i = 0; i < count && !event.isIntercepted(); i++) { + EventReceiver receiver = receiverArr[i]; + receiver.onReceive(event, unsubscribe); + if (unsubscribe.isUnsubscribed()) { + if (unsubscribedReceivers == null) { + unsubscribedReceivers = new LinkedList<>(); + } + unsubscribedReceivers.add(receiver); + } + unsubscribe.reset(); + } + } finally { + if (unsubscribedReceivers != null) { + receivers.lock.writeLock() + .lock(); + try { + receivers.receivers.removeAll(unsubscribedReceivers); + } finally { + receivers.lock.writeLock() + .unlock(); + } + } + recycleBuffer(receiverArr); + } + for (int i = 0; i < children.size() && !event.isIntercepted(); i++) { + EventManager sub = null; + try { + sub = children.get(i); + } catch (IndexOutOfBoundsException e) { + // concurrent mod ignored + } + if (sub != null) { + sub.dispatchEvent(event); + } + } + return event.isIntercepted(); + } + + /** + * Internal class for saving receivers of each type + * + * @param Event type + */ + static class Receivers { + + ReadWriteLock lock = new ReentrantReadWriteLock(); + + List> receivers = new ArrayList<>(); + + } + + private final EventReceiver[][] caches = new EventReceiver[5][]; + + @SuppressWarnings("unchecked") + private EventReceiver[] obtainBuffer(int size) { + EventReceiver[] res = null; + synchronized (this) { + for (int i = 0; i < caches.length; i++) { + if (caches[i] != null && caches[i].length >= size) { + res = (EventReceiver[]) caches[i]; + caches[i] = null; + break; + } + } + } + if (res == null) { + res = new EventReceiver[size]; + } + return res; + } + + private synchronized void recycleBuffer(EventReceiver[] array) { + if (array == null) { + return; + } + for (int i = 0; i < caches.length; i++) { + if (caches[i] == null) { + Arrays.fill(array, null); + caches[i] = array; + break; + } + } + } + +} diff --git a/app/src/main/java/com/tyron/code/event/EventReceiver.java b/app/src/main/java/com/tyron/code/event/EventReceiver.java new file mode 100644 index 00000000..abc91234 --- /dev/null +++ b/app/src/main/java/com/tyron/code/event/EventReceiver.java @@ -0,0 +1,6 @@ +package com.tyron.code.event; + +public interface EventReceiver { + + void onReceive(T event, Unsubscribe unsubscribe); +} diff --git a/app/src/main/java/com/tyron/code/event/SubscriptionReceipt.java b/app/src/main/java/com/tyron/code/event/SubscriptionReceipt.java new file mode 100644 index 00000000..f6e4e542 --- /dev/null +++ b/app/src/main/java/com/tyron/code/event/SubscriptionReceipt.java @@ -0,0 +1,32 @@ +package com.tyron.code.event; + +import java.lang.ref.WeakReference; + +public class SubscriptionReceipt { + + private final Class clazz; + private final WeakReference> receiver; + private final EventManager manager; + + public SubscriptionReceipt(Class clazz, + EventReceiver receiver, + EventManager manager) { + this.clazz = clazz; + this.receiver = new WeakReference<>(receiver); + this.manager = manager; + } + + public void unsubscribe() { + EventManager.Receivers receivers = manager.getReceivers(clazz); + receivers.lock.writeLock().lock(); + try { + EventReceiver target = receiver.get(); + if (target != null) { + receivers.receivers.remove(target); + } + } finally { + receivers.lock.writeLock().unlock(); + } + + } +} diff --git a/app/src/main/java/com/tyron/code/event/Unsubscribe.java b/app/src/main/java/com/tyron/code/event/Unsubscribe.java new file mode 100644 index 00000000..cc71b930 --- /dev/null +++ b/app/src/main/java/com/tyron/code/event/Unsubscribe.java @@ -0,0 +1,39 @@ +package com.tyron.code.event; + +import io.github.rosemoe.sora.event.Event; +import io.github.rosemoe.sora.event.EventReceiver; + +/** + * Instance for unsubscribing for a receiver. + * + * Note that this instance can be reused during an event dispatch, so + * it is not a valid behavior to save the instance in event receivers. + * Always use the one given by {@link EventReceiver#onReceive(Event, Unsubscribe)}. + */ +public class Unsubscribe { + + private boolean unsubscribeFlag = false; + + /** + * Unsubscribe the event. And current receiver will not get event again. + * References to the receiver are also removed. + */ + public void unsubscribe() { + unsubscribeFlag = true; + } + + /** + * Checks whether unsubscribe flag is set + */ + public boolean isUnsubscribed() { + return unsubscribeFlag; + } + + /** + * Reset the flag + */ + public void reset() { + unsubscribeFlag = false; + } + +} diff --git a/app/src/main/java/com/tyron/code/language/AbstractAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/language/AbstractAutoCompleteProvider.java new file mode 100644 index 00000000..6df3095d --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/AbstractAutoCompleteProvider.java @@ -0,0 +1,27 @@ +package com.tyron.code.language; + +import com.tyron.completion.model.CompletionList; + +import java.util.List; +import java.util.stream.Collectors; + +import io.github.rosemoe.sora.lang.completion.CompletionItem; + +/** + * An auto complete provider that supports cancellation as the user types + */ +public abstract class AbstractAutoCompleteProvider { + + public final List getAutoCompleteItems(String prefix, int line, int column) { + CompletionList list = getCompletionList(prefix, line, column); + if (list == null) { + return null; + } + + return list.items.stream() + .map(CompletionItemWrapper::new) + .collect(Collectors.toList()); + } + + public abstract CompletionList getCompletionList(String prefix, int line, int column); +} diff --git a/app/src/main/java/com/tyron/code/language/AbstractCodeAnalyzer.java b/app/src/main/java/com/tyron/code/language/AbstractCodeAnalyzer.java new file mode 100644 index 00000000..27c4f4e0 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/AbstractCodeAnalyzer.java @@ -0,0 +1,200 @@ +package com.tyron.code.language; + +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.editor.Editor; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.Token; +import org.apache.commons.io.input.CharSequenceReader; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.github.rosemoe.sora.lang.analysis.StyleReceiver; +import io.github.rosemoe.sora.lang.styling.MappedSpans; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; + +public abstract class AbstractCodeAnalyzer extends DiagnosticAnalyzeManager { + + private final Map mColorMap = new HashMap<>(); + + private StyleReceiver mReceiver; + private Token mPreviousToken; + private Styles mLastStyles; + protected List mDiagnostics = new ArrayList<>(); + + public AbstractCodeAnalyzer() { + setup(); + } + + @Override + public void setReceiver(@Nullable StyleReceiver receiver) { + super.setReceiver(receiver); + + mReceiver = receiver; + } + + @Override + public void insert(CharPosition start, CharPosition end, CharSequence insertedContent) { + rerunWithBg(); + } + + @Override + public void delete(CharPosition start, CharPosition end, CharSequence deletedContent) { + rerunWithBg(); + } + + @Override + public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { + super.reset(content, extraArguments); + } + + @Override + public void setDiagnostics(Editor editor, List diagnostics) { + mDiagnostics = diagnostics; + } + + public void setup() { + + } + + /** + * Convenience method to map a color id to multiple token types + * @param id EditorColorScheme id + * @param tokenTypes the Token types from the lexer + */ + protected void putColor(int id, int... tokenTypes) { + for (int tokenType : tokenTypes) { + putColor(id, tokenType); + } + } + + /** + * Map a specific EditorColorScheme id to a token type + * @param id The color id from {@link EditorColorScheme} + * @param tokenType the token type from the provided lexer + */ + protected void putColor(int id, int tokenType) { + mColorMap.put(tokenType, id); + } + + /** + * @return The lexer that will be used to generate the tokens + */ + public abstract Lexer getLexer(CharStream input); + + public abstract void analyzeInBackground(CharSequence contents); + + public Integer getColor(int tokenType) { + return mColorMap.get(tokenType); + } + + /** + * Called before {@link #analyze(StringBuilder, Delegate)} + * is called, commonly used to clear object caches before starting the analysis + */ + protected void beforeAnalyze() { + + } + + @Override + protected Styles analyze(StringBuilder text, Delegate delegate) { + Styles styles = new Styles(); + boolean loaded = getExtraArguments().getBoolean("loaded", false); + if (!loaded) { + return styles; + } + beforeAnalyze(); + + MappedSpans.Builder result = new MappedSpans.Builder(1024); + + try { + Lexer lexer = getLexer(CharStreams.fromReader(new CharSequenceReader(text))); + while (!delegate.isCancelled()) { + Token token = lexer.nextToken(); + if (token == null) { + break; + } + if (token.getType() == Token.EOF) { + break; + } + + boolean skip = onNextToken(token, styles, result); + if (skip) { + mPreviousToken = token; + continue; + } + + Integer id = getColor(token.getType()); + if (id == null) { + id = EditorColorScheme.TEXT_NORMAL; + } + result.addIfNeeded(token.getLine() - 1, token.getCharPositionInLine(), id); + + mPreviousToken = token; + } + + if (mPreviousToken != null) { + result.determine(mPreviousToken.getLine() - 1); + } + + styles.spans = result.build(); + styles.finishBuilding(); + afterAnalyze(text, styles, result); + + if (mShouldAnalyzeInBg) { + analyzeInBackground(text); + } + } catch (IOException e) { + // ignored + } + + mLastStyles = styles; + return styles; + } + + @Nullable + protected Styles getLastStyles() { + return mLastStyles; + } + + /** + * Called after the analysis has been done, used to finalize the {@link MappedSpans.Builder} + */ + protected void afterAnalyze(CharSequence content, Styles styles, MappedSpans.Builder colors) { + + } + + @Nullable + public Token getPreviousToken() { + return mPreviousToken; + } + + /** + * Called when the lexer has moved to the next token + * @param currentToken the current token + * @param styles + * @param colors the current colors object, can be modified + * @return true if the analyzer should skip on the next token + */ + public boolean onNextToken(Token currentToken, Styles styles, MappedSpans.Builder colors) { + return false; + } + + public void update(Styles styles) { + mReceiver.setStyles(this, styles); + } +} diff --git a/app/src/main/java/com/tyron/code/language/BaseAnalyzeManager.java b/app/src/main/java/com/tyron/code/language/BaseAnalyzeManager.java new file mode 100644 index 00000000..0ca24a35 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/BaseAnalyzeManager.java @@ -0,0 +1,224 @@ +package com.tyron.code.language; + +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.analysis.StyleReceiver; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.ContentReference; + +/** + * Built-in implementation of {@link AnalyzeManager}. + *

+ * This is a simple version without any incremental actions. + *

+ * The analysis will always re-run when the text changes. Hopefully, it will stop previous outdated + * runs by provide a {@link io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager.Delegate} + * object. + * + * @param The shared object type that we get for auto-completion. + */ +public abstract class BaseAnalyzeManager implements AnalyzeManager { + + private final static String LOG_TAG = "SimpleAnalyzeManager"; + private static int sThreadId = 0; + + private StyleReceiver receiver; + private ContentReference ref; + private Bundle extraArguments; + private volatile long newestRequestId; + private final Object lock = new Object(); + private V data; + + private AnalyzeThread thread; + + @Override + public void setReceiver(@Nullable StyleReceiver receiver) { + this.receiver = receiver; + } + + @Override + public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { + this.ref = content; + this.extraArguments = extraArguments; + rerun(); + } + + @Override + public void insert(CharPosition start, CharPosition end, CharSequence insertedContent) { + rerun(); + } + + @Override + public void delete(CharPosition start, CharPosition end, CharSequence deletedContent) { + rerun(); + } + + @Override + public synchronized void rerun() { + newestRequestId++; + if (thread == null || !thread.isAlive()) { + // Create new thread + Log.v(LOG_TAG, "Starting a new thread for analysis"); + thread = new AnalyzeThread(); + thread.setDaemon(true); + thread.setName("SplAnalyzer-" + nextThreadId()); + thread.start(); + } + synchronized (lock) { + lock.notify(); + } + } + + @Override + public void destroy() { + ref = null; + extraArguments = null; + newestRequestId = 0; + data = null; + if (thread != null && thread.isAlive()) { + thread.cancel(); + } + thread = null; + } + + private synchronized static int nextThreadId() { + sThreadId++; + return sThreadId; + } + + /** + * Get extra arguments set by + * {@link io.github.rosemoe.sora.widget.CodeEditor#setText(CharSequence, Bundle)} + */ + public Bundle getExtraArguments() { + return extraArguments; + } + + /** + * Get data set by analyze thread + */ + @Nullable + public V getData() { + return data; + } + + public AnalyzeThread getAnalyzeThread() { + return thread; + } + + /** + * Analyze the given input. + * + * @param text A {@link StringBuilder} instance containing the text in editor. DO NOT + * SAVE THE INSTANCE OR + * UPDATE IT. It is continuously used by this analyzer. + * @param delegate A delegate used to check whether this invocation is outdated. You should + * stop your logic + * if {@link Delegate#isCancelled()} returns true. + * @return Styles created according to the text. + */ + protected abstract Styles analyze(StringBuilder text, Delegate delegate); + + /** + * Analyze thread. + *

+ * The thread will keep alive unless there is any exception or {@link AnalyzeManager#destroy()} + * is called. + */ + private class AnalyzeThread extends Thread { + + private volatile boolean cancelled = false; + /** + * Single instance for text storing + */ + private final StringBuilder textContainer = new StringBuilder(); + + public void cancel() { + cancelled = true; + } + + @Override + public void run() { + Log.v(LOG_TAG, "Analyze thread started"); + try { + while (!cancelled) { + final ContentReference text = ref; + if (text != null) { + long requestId = 0L; + Styles result; + V newData; + // Do the analysis, until the requestId matches + do { + requestId = newestRequestId; + Delegate delegate = new Delegate<>(requestId); + + // Collect line contents + textContainer.setLength(0); + textContainer.ensureCapacity(text.length()); + for (int i = 0; i < text.getLineCount() && + requestId == newestRequestId; i++) { + if (i != 0) { + textContainer.append('\n'); + } + text.appendLineTo(textContainer, i); + } + + // Invoke the implementation + result = analyze(textContainer, delegate); + + newData = delegate.data; + } while (requestId != newestRequestId); + // Send result + final StyleReceiver receiver = BaseAnalyzeManager.this.receiver; + if (receiver != null) { + receiver.setStyles(BaseAnalyzeManager.this, result); + } + data = newData; + } + // Wait for next time + synchronized (lock) { + lock.wait(); + } + } + } catch (InterruptedException e) { + Log.v(LOG_TAG, "Thread is interrupted."); + } catch (Exception e) { + Log.e(LOG_TAG, "Unexpected exception is thrown in the thread.", e); + } + } + + } + + /** + * Delegate between manager and analysis implementation + */ + public final class Delegate { + + private final long myRequestId; + private T data; + + public Delegate(long requestId) { + myRequestId = requestId; + } + + /** + * Set shared data + */ + public void setData(T value) { + data = value; + } + + /** + * Check whether the operation is cancelled + */ + public boolean isCancelled() { + return myRequestId != newestRequestId; + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/CompletionItemWrapper.java b/app/src/main/java/com/tyron/code/language/CompletionItemWrapper.java new file mode 100644 index 00000000..37fd64d2 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/CompletionItemWrapper.java @@ -0,0 +1,31 @@ +package com.tyron.code.language; + +import com.tyron.completion.java.drawable.CircleDrawable; +import com.tyron.editor.Editor; + +import io.github.rosemoe.sora.lang.completion.CompletionItem; +import io.github.rosemoe.sora.text.Content; +import io.github.rosemoe.sora.widget.CodeEditor; + +/** + * A wrapper for {@link com.tyron.completion.model.CompletionItem} + */ +public class CompletionItemWrapper extends CompletionItem { + + private final com.tyron.completion.model.CompletionItem item; + + public CompletionItemWrapper(com.tyron.completion.model.CompletionItem item) { + super(item.label, item.detail, new CircleDrawable(item.iconKind)); + this.item = item; + } + + @Override + public void performCompletion(CodeEditor editor, Content text, int line, int column) { + if (!(editor instanceof Editor)) { + throw new IllegalArgumentException("Cannot use CompletionItemWrapper on an editor that does not implement com.tyron.editor.Editor"); + } + + Editor rawEditor = ((Editor) editor); + item.handleInsert(rawEditor); + } +} diff --git a/app/src/main/java/com/tyron/code/language/DiagnosticAnalyzeManager.java b/app/src/main/java/com/tyron/code/language/DiagnosticAnalyzeManager.java new file mode 100644 index 00000000..289981b7 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/DiagnosticAnalyzeManager.java @@ -0,0 +1,54 @@ +package com.tyron.code.language; + +import android.os.Bundle; + +import androidx.annotation.NonNull; + +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.editor.Editor; + +import java.util.List; + +import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; +import io.github.rosemoe.sora.text.ContentReference; + +public abstract class DiagnosticAnalyzeManager extends SimpleAnalyzeManager { + + protected boolean mShouldAnalyzeInBg = false; + + public abstract void setDiagnostics(Editor editor, List diagnostics); + + public void rerunWithoutBg() { + mShouldAnalyzeInBg = false; + super.rerun(); + } + + public void rerunWithBg() { + mShouldAnalyzeInBg = true; + super.rerun(); + } + + @Override + public void rerun() { + mShouldAnalyzeInBg = false; + super.rerun(); + } + + @Override + public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { + if (extraArguments == null) { + extraArguments = new Bundle(); + } + mShouldAnalyzeInBg = extraArguments.getBoolean("bg", false); + super.reset(content, extraArguments); + } + + @Override + public Bundle getExtraArguments() { + Bundle extraArguments = super.getExtraArguments(); + if (extraArguments == null) { + extraArguments = new Bundle(); + } + return extraArguments; + } +} diff --git a/app/src/main/java/com/tyron/code/language/DiagnosticSpanMapUpdater.java b/app/src/main/java/com/tyron/code/language/DiagnosticSpanMapUpdater.java new file mode 100644 index 00000000..bd66061e --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/DiagnosticSpanMapUpdater.java @@ -0,0 +1,85 @@ +package com.tyron.code.language; + +import com.tyron.builder.model.DiagnosticWrapper; + +import java.util.List; + +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.ContentReference; + +public class DiagnosticSpanMapUpdater { + + public static void shiftDiagnosticsOnSingleLineInsert(List diagnostics, + ContentReference ref, + CharPosition start, + CharPosition end) { + int length = end.index - start.index; + for (DiagnosticWrapper diagnostic : diagnostics) { + if (!isValid(diagnostic)) { + continue; + } + // diagnostic is located before the insertion index, its not included + if (diagnostic.getEndPosition() <= end.index) { + continue; + } + diagnostic.setStartPosition(diagnostic.getStartPosition() + length); + diagnostic.setEndPosition(diagnostic.getEndPosition() + length); + } + } + + public static void shiftDiagnosticsOnSingleLineDelete(List diagnostics, + ContentReference ref, + CharPosition start, + CharPosition end) { + int length = end.index - start.index; + for (DiagnosticWrapper diagnostic : diagnostics) { + if (!isValid(diagnostic)) { + continue; + } + if (diagnostic.getStartPosition() > start.index) { + diagnostic.setStartPosition(diagnostic.getStartPosition() - length); + } + if (diagnostic.getEndPosition() > end.index) { + diagnostic.setEndPosition(diagnostic.getEndPosition() - length); + } + } + } + + public static void shiftDiagnosticsOnMultiLineDelete(List diagnostics, + ContentReference ref, + CharPosition start, + CharPosition end) { + int length = end.index - start.index; + for (DiagnosticWrapper diagnostic : diagnostics) { + if (!isValid(diagnostic)) { + continue; + } + if (diagnostic.getStartPosition() < end.index) { + continue; + } + diagnostic.setStartPosition(diagnostic.getStartPosition() - length); + diagnostic.setEndPosition(diagnostic.getEndPosition() - length); + } + } + + public static void shiftDiagnosticsOnMultiLineInsert(List diagnostics, + ContentReference ref, + CharPosition start, + CharPosition end) { + int length = end.index - start.index; + for (DiagnosticWrapper diagnostic : diagnostics) { + if (!isValid(diagnostic)) { + continue; + } + if (diagnostic.getEndPosition() < end.index) { + continue; + } + diagnostic.setStartPosition(diagnostic.getStartPosition() + length); + diagnostic.setEndPosition(diagnostic.getEndPosition() + length); + } + } + + public static boolean isValid(DiagnosticWrapper d) { + return d.getStartPosition() >= 0 && d.getEndPosition() >= 0; + } +} diff --git a/app/src/main/java/com/tyron/code/language/EditorFormatter.java b/app/src/main/java/com/tyron/code/language/EditorFormatter.java new file mode 100644 index 00000000..f7c41bc3 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/EditorFormatter.java @@ -0,0 +1,19 @@ +package com.tyron.code.language; + +import androidx.annotation.NonNull; + +/** + * Marker interface for languages that support formatting + */ +public interface EditorFormatter { + + /** + * Formats the given CharSequence on the specified start and end indices. + * @param text The text to format. + * @param startIndex The 0-based index of where the format starts + * @param endIndex The 0-based index of where the format ends + * @return The formatted text + */ + @NonNull + CharSequence format(@NonNull CharSequence text, int startIndex, int endIndex); +} diff --git a/app/src/main/java/com/tyron/code/language/HighlightUtil.java b/app/src/main/java/com/tyron/code/language/HighlightUtil.java new file mode 100644 index 00000000..804d0b74 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/HighlightUtil.java @@ -0,0 +1,226 @@ +package com.tyron.code.language; + +import android.util.Log; + +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.editor.CharPosition; +import com.tyron.editor.Editor; + +import javax.tools.Diagnostic; + +import java.util.ArrayList; +import java.util.List; + +import io.github.rosemoe.sora.lang.styling.Span; +import io.github.rosemoe.sora.lang.styling.Spans; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora2.BuildConfig; + +public class HighlightUtil { + + public static void replaceSpan(Styles styles, Span newSpan, int startLine, int startColumn, int endLine, int endColumn) { + for (int line = startLine; line <= endLine; line++) { + ProgressManager.checkCanceled(); + int start = (line == startLine ? startColumn : 0); + int end = (line == endLine ? endColumn : Integer.MAX_VALUE); + Spans.Reader read = styles.getSpans().read(); + List spans = new ArrayList<>(read.getSpansOnLine(line)); + int increment; + for (int i = 0; i < spans.size(); i += increment) { + ProgressManager.checkCanceled(); + io.github.rosemoe.sora.lang.styling.Span span = spans.get(i); + increment = 1; + if (span.column >= end) { + break; + } + int spanEnd = (i + 1 >= spans.size() ? Integer.MAX_VALUE : spans.get(i + 1).column); + if (spanEnd >= start) { + int regionStartInSpan = Math.max(span.column, start); + int regionEndInSpan = Math.min(end, spanEnd); + if (regionStartInSpan == span.column) { + if (regionEndInSpan != spanEnd) { + increment = 2; + io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); + nSpan.column = regionEndInSpan; + spans.add(i + 1, nSpan); + } + span.problemFlags = newSpan.problemFlags; + span.underlineColor = newSpan.underlineColor; + span.style = newSpan.style; + span.renderer = newSpan.renderer; + } else { + //regionStartInSpan > span.column + if (regionEndInSpan == spanEnd - 1) { + increment = 2; + io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); + nSpan.column = regionStartInSpan; + spans.add(i + 1, nSpan); + span.problemFlags = newSpan.problemFlags; + span.underlineColor = newSpan.underlineColor; + span.style = newSpan.style; + span.renderer = newSpan.renderer; + } else { + increment = 3; + io.github.rosemoe.sora.lang.styling.Span span1 = span.copy(); + span1.column = regionStartInSpan; + span1.problemFlags = newSpan.problemFlags; + span1.underlineColor = newSpan.underlineColor; + span1.style = newSpan.style; + span1.renderer = newSpan.renderer; + io.github.rosemoe.sora.lang.styling.Span span2 = span.copy(); + span2.column = regionEndInSpan; + spans.add(i + 1, span1); + spans.add(i + 2, span2); + } + } + } + } + + Spans.Modifier modify = styles.getSpans().modify(); + modify.setSpansOnLine(line, spans); + } + } + + public static void markProblemRegion(Styles styles, int newFlag, int startLine, int startColumn, int endLine, int endColumn) { + for (int line = startLine; line <= endLine; line++) { + ProgressManager.checkCanceled(); + int start = (line == startLine ? startColumn : 0); + int end = (line == endLine ? endColumn : Integer.MAX_VALUE); + Spans.Reader read = styles.getSpans().read(); + List spans = new ArrayList<>(read.getSpansOnLine(line)); + int increment; + for (int i = 0; i < spans.size(); i += increment) { + ProgressManager.checkCanceled(); + io.github.rosemoe.sora.lang.styling.Span span = spans.get(i); + increment = 1; + if (span.column >= end) { + break; + } + int spanEnd = (i + 1 >= spans.size() ? Integer.MAX_VALUE : spans.get(i + 1).column); + if (spanEnd >= start) { + int regionStartInSpan = Math.max(span.column, start); + int regionEndInSpan = Math.min(end, spanEnd); + if (regionStartInSpan == span.column) { + if (regionEndInSpan != spanEnd) { + increment = 2; + io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); + nSpan.column = regionEndInSpan; + spans.add(i + 1, nSpan); + } + span.problemFlags |= newFlag; + } else { + //regionStartInSpan > span.column + if (regionEndInSpan == spanEnd) { + increment = 2; + io.github.rosemoe.sora.lang.styling.Span nSpan = span.copy(); + nSpan.column = regionStartInSpan; + spans.add(i + 1, nSpan); + nSpan.problemFlags |= newFlag; + } else { + increment = 3; + io.github.rosemoe.sora.lang.styling.Span span1 = span.copy(); + span1.column = regionStartInSpan; + span1.problemFlags |= newFlag; + io.github.rosemoe.sora.lang.styling.Span span2 = span.copy(); + span2.column = regionEndInSpan; + spans.add(i + 1, span1); + spans.add(i + 2, span2); + } + } + } + } + + Spans.Modifier modify = styles.getSpans().modify(); + modify.setSpansOnLine(line, spans); + } + } + + + /** + * Highlights the list of given diagnostics, taking care of conversion between 1-based offsets + * to 0-based offsets. + * It also makes the Diagnostic eligible for shifting as the user types. + */ + public static void markDiagnostics(Editor editor, List diagnostics, + Styles styles) { + diagnostics.forEach(it -> { + ProgressManager.checkCanceled(); + try { + int startLine; + int startColumn; + int endLine; + int endColumn; + if (it.getPosition() != DiagnosticWrapper.USE_LINE_POS) { + if (it.getStartPosition() == -1) { + it.setStartPosition(it.getPosition()); + } + if (it.getEndPosition() == -1) { + it.setEndPosition(it.getPosition()); + } + CharPosition start = editor.getCharPosition((int) it.getStartPosition()); + CharPosition end = editor.getCharPosition((int) it.getEndPosition()); + + int sLine = start.getLine(); + int sColumn = start.getColumn(); + int eLine = end.getLine(); + int eColumn = end.getColumn(); + + // the editor does not support marking underline spans for the same start and end + // index + // to work around this, we just subtract one to the start index + if (sLine == eLine && eColumn == sColumn) { + sColumn--; + eColumn++; + } + + it.setStartLine(sLine); + it.setEndLine(eLine); + it.setStartColumn(sColumn); + it.setEndColumn(eColumn); + } + startLine = it.getStartLine(); + startColumn = it.getStartColumn(); + endLine = it.getEndLine(); + endColumn = it.getEndColumn(); + + int flag = it.getKind() == Diagnostic.Kind.ERROR ? Span.FLAG_ERROR : + Span.FLAG_WARNING; + markProblemRegion(styles, flag, startLine, startColumn, endLine, endColumn); + } catch (IllegalArgumentException | IndexOutOfBoundsException e) { + if (BuildConfig.DEBUG) { + Log.d("HighlightUtil", "Failed to mark diagnostics", e); + } + } + }); + } + + /** + * Used in xml diagnostics where line is only given + */ + public static void setErrorSpan(Styles colors, int line) { + try { + Spans.Reader reader = colors.getSpans().read(); + int realLine = line - 1; + List spans = reader.getSpansOnLine(realLine); + + for (io.github.rosemoe.sora.lang.styling.Span span : spans) { + span.problemFlags = Span.FLAG_ERROR; + } + } catch (IndexOutOfBoundsException e) { + // ignored + } + } + + public static void clearDiagnostics(Styles styles) { + Spans spans = styles.getSpans(); + Spans.Reader read = spans.read(); + for (int i = 0; i < spans.getLineCount(); i++) { + List spansOnLine = new ArrayList<>(read.getSpansOnLine(i)); + for (Span span : spansOnLine) { + span.problemFlags = 0; + } + spans.modify().setSpansOnLine(i, spansOnLine); + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/Language.java b/app/src/main/java/com/tyron/code/language/Language.java new file mode 100644 index 00000000..bb9337d5 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/Language.java @@ -0,0 +1,20 @@ +package com.tyron.code.language; + +import com.tyron.editor.Editor; + +import java.io.File; + +public interface Language { + + /** + * Subclasses return whether they support this file extension + */ + boolean isApplicable(File ext); + + /** + * + * @param editor the editor instance + * @return The specific language instance for this editor + */ + io.github.rosemoe.sora.lang.Language get(Editor editor); +} diff --git a/app/src/main/java/com/tyron/code/language/LanguageManager.java b/app/src/main/java/com/tyron/code/language/LanguageManager.java new file mode 100644 index 00000000..d273ef29 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/LanguageManager.java @@ -0,0 +1,59 @@ +package com.tyron.code.language; + +import com.tyron.code.language.groovy.Groovy; +import com.tyron.code.language.java.Java; +import com.tyron.code.language.json.Json; +import com.tyron.code.language.kotlin.Kotlin; +import com.tyron.code.language.xml.Xml; +import com.tyron.editor.Editor; + +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class LanguageManager { + + private static LanguageManager Instance = null; + + public static LanguageManager getInstance() { + if (Instance == null) { + Instance = new LanguageManager(); + } + return Instance; + } + + private final Set mLanguages = new HashSet<>(); + + private LanguageManager() { + initLanguages(); + } + + private void initLanguages() { + mLanguages.addAll( + Arrays.asList( + new Xml(), + new Java(), + new Kotlin(), + new Groovy(), + new Json())); + } + + public boolean supports(File file) { + for (Language language : mLanguages) { + if (language.isApplicable(file)) { + return true; + } + } + return false; + } + + public io.github.rosemoe.sora.lang.Language get(Editor editor, File file) { + for (Language lang : mLanguages) { + if (lang.isApplicable(file)) { + return lang.get(editor); + } + } + return null; + } +} diff --git a/app/src/main/java/com/tyron/code/language/groovy/Groovy.java b/app/src/main/java/com/tyron/code/language/groovy/Groovy.java new file mode 100644 index 00000000..a9cd79c0 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/Groovy.java @@ -0,0 +1,19 @@ +package com.tyron.code.language.groovy; + +import com.tyron.code.language.Language; +import com.tyron.editor.Editor; + +import java.io.File; + +public class Groovy implements Language { + @Override + public boolean isApplicable(File ext) { + return ext.getName().endsWith(".groovy") || ext.getName().endsWith(".gradle"); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return new GroovyLanguage(editor); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/groovy/GroovyAnalyzer.java b/app/src/main/java/com/tyron/code/language/groovy/GroovyAnalyzer.java new file mode 100644 index 00000000..49894e53 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/GroovyAnalyzer.java @@ -0,0 +1,43 @@ +package com.tyron.code.language.groovy; + +import android.content.res.AssetManager; + +import com.tyron.code.ApplicationLoader; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; +import com.tyron.code.analyzer.BaseTextmateAnalyzer; +import com.tyron.editor.Editor; + +import java.io.InputStream; +import java.io.InputStreamReader; + +import io.github.rosemoe.sora.langs.textmate.theme.TextMateColorScheme; +import io.github.rosemoe.sora.textmate.core.theme.IRawTheme; + +public class GroovyAnalyzer extends BaseTextmateAnalyzer { + + private static final String GRAMMAR_NAME = "groovy.tmLanguage"; + private static final String LANGUAGE_PATH = "textmate/groovy/syntaxes/groovy.tmLanguage"; + private static final String CONFIG_PATH = "textmate/groovy/language-configuration.json"; + + public GroovyAnalyzer(Editor editor, + String grammarName, + InputStream open, + InputStreamReader config, + IRawTheme rawTheme) throws Exception { + super(editor, grammarName, open, config, rawTheme); + } + + public static GroovyAnalyzer create(Editor editor) { + try { + AssetManager assetManager = ApplicationLoader.applicationContext.getAssets(); + + try (InputStreamReader config = new InputStreamReader(assetManager.open(CONFIG_PATH))) { + return new GroovyAnalyzer(editor, GRAMMAR_NAME, assetManager.open(LANGUAGE_PATH), + config, ((TextMateColorScheme) ((CodeEditorView) editor) + .getColorScheme()).getRawTheme()); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/groovy/GroovyLanguage.java b/app/src/main/java/com/tyron/code/language/groovy/GroovyLanguage.java new file mode 100644 index 00000000..31260082 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/GroovyLanguage.java @@ -0,0 +1,73 @@ +package com.tyron.code.language.groovy; + +import android.os.Bundle; + +import androidx.annotation.NonNull; + +import com.tyron.editor.Editor; + +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; +import io.github.rosemoe.sora.lang.completion.CompletionPublisher; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.widget.SymbolPairMatch; + +public class GroovyLanguage implements Language { + + private final Editor mEditor; + private final GroovyAnalyzer mAnalyzer; + + public GroovyLanguage(Editor editor) { + mEditor = editor; + mAnalyzer = GroovyAnalyzer.create(editor); + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return mAnalyzer; + } + + @Override + public int getInterruptionLevel() { + return INTERRUPTION_LEVEL_STRONG; + } + + @Override + public void requireAutoComplete(@NonNull ContentReference content, @NonNull CharPosition position, @NonNull CompletionPublisher publisher, @NonNull Bundle extraArguments) throws CompletionCancelledException { + + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + return 0; + } + + @Override + public boolean useTab() { + return true; + } + + @Override + public CharSequence format(CharSequence text) { + return text; + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return null; + } + + @Override + public NewlineHandler[] getNewlineHandlers() { + return new NewlineHandler[0]; + } + + @Override + public void destroy() { + + } +} diff --git a/app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.g4 b/app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.g4 new file mode 100644 index 00000000..b5cc7827 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.g4 @@ -0,0 +1,346 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +lexer grammar GroovyLexer; + +@header { + import java.util.ArrayDeque; + import java.util.Arrays; + import java.util.Deque; + import java.util.Set; + import java.util.HashSet; +} + +@members { + private static final Set ALLOWED_OP_SET = new HashSet(Arrays.asList(COMMA, NOT, BNOT, PLUS, ASSIGN, PLUS_ASSIGN, LT, GT, LTE, GTE, EQUAL, UNEQUAL, FIND, MATCH, DOT, SAFE_DOT, STAR_DOT, ATTR_DOT, MEMBER_POINTER, ELVIS, QUESTION, COLON, AND, OR, KW_ASSERT, KW_RETURN)); // the allowed ops before slashy string. e.g. p1=/ab/; p2=~/ab/; p3=!/ab/ + private static enum Brace { + ROUND, + SQUARE, + CURVE + }; + private Deque braceStack = new ArrayDeque(); + private Brace topBrace = null; + private int lastTokenType = 0; + private long tokenIndex = 0; + private long tlePos = 0; + + @Override + public void emit(Token token) { + tokenIndex++; + int tokenType = token.getType(); + if (NL != tokenType) { // newline should be ignored + lastTokenType = tokenType; + } + + //System.out.println("EM: " + tokenNames[lastTokenType != -1 ? lastTokenType : 0] + ": " + lastTokenType + " TLE = " + (tlePos == tokenIndex) + " " + tlePos + "/" + tokenIndex + " " + token.getText()); + if (tokenType == ROLLBACK_ONE) { + this.rollbackOneChar(); + } + + super.emit(token); + } + + // just a hook, which will be overrided by GroovyLangLexer + protected void rollbackOneChar() {} + + private void pushBrace(Brace b) { + braceStack.push(b); + topBrace = braceStack.peekFirst(); + //System.out.println("> " + topBrace); + } + + private void popBrace() { + braceStack.pop(); + topBrace = braceStack.peekFirst(); + //System.out.println("> " + topBrace); + } + + + private boolean isSlashyStringAllowed() { + //System.out.println("SP: " + " TLECheck = " + (tlePos == tokenIndex) + " " + tlePos + "/" + tokenIndex); + boolean isLastTokenOp = ALLOWED_OP_SET.contains(Integer.valueOf(lastTokenType)); + boolean res = isLastTokenOp || tlePos == tokenIndex; + //System.out.println("SP: " + tokenNames[lastTokenType] + ": " + lastTokenType + " res " + res + (res ? ( isLastTokenOp ? " op" : " tle") : "")); + return res; + } + +} + + + +LINE_COMMENT: '//' .*? ('\n' | EOF) -> type(NL) ; +DOC_COMMENT: '/**' .*? '*/' -> type(NL) ; +BLOCK_COMMENT: '/*' .*? '*/' -> type(NL) ; +SHEBANG_COMMENT: { tokenIndex == 0 }? '#!' .*? '\n' -> skip ; + +WS: [ \t]+ -> skip ; + +LPAREN : '(' { pushBrace(Brace.ROUND); tlePos = tokenIndex + 1; } -> pushMode(DEFAULT_MODE) ; +RPAREN : ')' { popBrace(); } -> popMode ; +LBRACK : '[' { pushBrace(Brace.SQUARE); tlePos = tokenIndex + 1; } -> pushMode(DEFAULT_MODE) ; +RBRACK : ']' { popBrace(); } -> popMode ; +LCURVE : '{' { pushBrace(Brace.CURVE); tlePos = tokenIndex + 1; } -> pushMode(DEFAULT_MODE) ; +RCURVE : '}' { popBrace(); } -> popMode ; + + +MULTILINE_STRING: (TSQ TSQ_STRING_ELEMENT*? TSQ + | TDQ TDQ_STRING_ELEMENT*? TDQ + ) -> type(STRING) + ; + +MULTILINE_GSTRING_START : TDQ TDQ_STRING_ELEMENT*? '$' -> type(GSTRING_START), pushMode(TRIPLE_QUOTED_GSTRING_MODE), pushMode(GSTRING_TYPE_SELECTOR_MODE); + + +STRING: '"' DQ_STRING_ELEMENT*? '"' + | '\'' SQ_STRING_ELEMENT*? '\'' + ; +SLASHY_STRING: '/' { isSlashyStringAllowed() }? SLASHY_STRING_ELEMENT+? '/' -> type(STRING) ; +DOLLAR_SLASHY_STRING: LDS { isSlashyStringAllowed() }? DOLLAR_SLASHY_STRING_ELEMENT*? RDS -> type(STRING) ; + +GSTRING_START: '"' DQ_STRING_ELEMENT*? '$' -> pushMode(DOUBLE_QUOTED_GSTRING_MODE), pushMode(GSTRING_TYPE_SELECTOR_MODE) ; +SLASHY_GSTRING_START: '/' { isSlashyStringAllowed() }? SLASHY_STRING_ELEMENT*? '$' -> type(GSTRING_START), pushMode(SLASHY_GSTRING_MODE), pushMode(GSTRING_TYPE_SELECTOR_MODE) ; +DOLLAR_SLASHY_GSTRING_START: LDS { isSlashyStringAllowed() }? DOLLAR_SLASHY_STRING_ELEMENT*? '$' -> type(GSTRING_START), pushMode(DOLLAR_SLASHY_GSTRING_MODE), pushMode(GSTRING_TYPE_SELECTOR_MODE) ; + + +fragment SLASHY_STRING_ELEMENT: SLASHY_ESCAPE + | '$' { !GrammarPredicates.isFollowedByJavaLetterInGString(_input) }? + | ~('/' | '$' | '\u0000' | '\n') + ; +fragment DOLLAR_SLASHY_STRING_ELEMENT: (SLASHY_ESCAPE + | '/' { _input.LA(1) != '$' }? + | '$' { !GrammarPredicates.isFollowedByJavaLetterInGString(_input) }? + | ~('/' | '$') + ) + ; +fragment TSQ_STRING_ELEMENT: (ESC_SEQUENCE | DOLLAR_ESCAPE | JOIN_LINE_ESCAPE + | '\'' { !(_input.LA(1) == '\'' && _input.LA(2) == '\'') }? + | ~('\'' | '\\') + ) + ; +fragment SQ_STRING_ELEMENT: ESC_SEQUENCE | DOLLAR_ESCAPE | JOIN_LINE_ESCAPE + | ~('\'' | '\\') + ; +fragment TDQ_STRING_ELEMENT: (ESC_SEQUENCE | DOLLAR_ESCAPE | JOIN_LINE_ESCAPE + | '"' { !(_input.LA(1) == '"' && _input.LA(2) == '"') }? + | ~('"' | '\\' | '$') + ) + ; +fragment DQ_STRING_ELEMENT: ESC_SEQUENCE | DOLLAR_ESCAPE | JOIN_LINE_ESCAPE + | ~('"' | '\\' | '$') + ; + +fragment TSQ: '\'\'\''; +fragment TDQ: '"""'; +fragment LDS: '$/'; +fragment RDS: '/$'; + +fragment IDENTIFIER_IN_GSTRING: JavaLetterInGString JavaLetterOrDigitInGString*; + +mode TRIPLE_QUOTED_GSTRING_MODE ; + MULTILINE_GSTRING_END: TDQ -> type(GSTRING_END), popMode ; + MULTILINE_GSTRING_PART: '$' -> type(GSTRING_PART), pushMode(GSTRING_TYPE_SELECTOR_MODE) ; + MULTILINE_GSTRING_ELEMENT: TDQ_STRING_ELEMENT -> more ; + +mode DOUBLE_QUOTED_GSTRING_MODE ; + GSTRING_END: '"' -> popMode ; + GSTRING_PART: '$' -> pushMode(GSTRING_TYPE_SELECTOR_MODE) ; + GSTRING_ELEMENT: DQ_STRING_ELEMENT -> more ; + +mode SLASHY_GSTRING_MODE ; + SLASHY_GSTRING_END: ('$'? '/') -> type(GSTRING_END), popMode ; + SLASHY_GSTRING_PART: '$' -> type(GSTRING_PART), pushMode(GSTRING_TYPE_SELECTOR_MODE) ; + SLASHY_GSTRING_ELEMENT: SLASHY_STRING_ELEMENT -> more ; + +mode DOLLAR_SLASHY_GSTRING_MODE; + DOLLAR_SLASHY_GSTRING_END: RDS -> type(GSTRING_END), popMode ; + DOLLAR_SLASHY_GSTRING_PART: '$' -> type(GSTRING_PART), pushMode(GSTRING_TYPE_SELECTOR_MODE) ; + DOLLAR_SLASHY_GSTRING_ELEMENT: DOLLAR_SLASHY_STRING_ELEMENT -> more ; + +mode GSTRING_TYPE_SELECTOR_MODE ; // We drop here after exiting curved brace? + GSTRING_BRACE_L: '{' { pushBrace(Brace.CURVE); tlePos = tokenIndex + 1; } -> type(LCURVE), popMode, pushMode(DEFAULT_MODE) ; + GSTRING_ID: IDENTIFIER_IN_GSTRING -> type(IDENTIFIER), popMode, pushMode(GSTRING_PATH) ; + +mode GSTRING_PATH ; + GSTRING_PATH_PART: '.' IDENTIFIER_IN_GSTRING ; + ROLLBACK_ONE: . -> popMode, channel(HIDDEN) ; // This magic is for exit this state if + +mode DEFAULT_MODE ; + +fragment SLASHY_ESCAPE: '\\' '/' ; +fragment DOLLAR_ESCAPE: '\\' '$' ; +fragment JOIN_LINE_ESCAPE: '\\' '\r'? '\n'; +fragment ESC_SEQUENCE: '\\' [btnfr"'\\] | OCTAL_ESC_SEQ | UNICODE_ESCAPE; +fragment OCTAL_ESC_SEQ: '\\' [0-3]? ZERO_TO_SEVEN? ZERO_TO_SEVEN ; +fragment UNICODE_ESCAPE: '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT; +// Numbers +DECIMAL: (DIGITS ('.' DIGITS EXP_PART? | EXP_PART) DECIMAL_TYPE_MODIFIER? ) | DIGITS DECIMAL_ONLY_TYPE_MODIFIER ; +INTEGER: (('0x' | '0X') HEX_DIGITS | ('0b' | '0B') BIN_DIGITS | '0' OCT_DIGITS | DEC_DIGITS) INTEGER_TYPE_MODIFIER? ; +fragment DIGITS: DIGIT | DIGIT (DIGIT | '_')* DIGIT ; +fragment DEC_DIGITS: DIGIT | [1-9] (DIGIT | '_')* DIGIT ; +fragment OCT_DIGITS: ZERO_TO_SEVEN | ZERO_TO_SEVEN (ZERO_TO_SEVEN | '_')* ZERO_TO_SEVEN ; +fragment ZERO_TO_SEVEN: [0-7]; +fragment HEX_DIGITS: HEX_DIGIT | HEX_DIGIT (HEX_DIGIT | '_')* HEX_DIGIT ; +fragment HEX_DIGIT : [0-9a-fA-F]; +fragment BIN_DIGITS: BIN_DIGIT | BIN_DIGIT (BIN_DIGIT | '_')* BIN_DIGIT ; +fragment BIN_DIGIT : [01]; +fragment SIGN: ('-'|'+') ; +fragment EXP_PART: ([eE] SIGN? DIGIT+) ; +fragment DIGIT: [0-9]; +fragment INTEGER_TYPE_MODIFIER: ('G' | 'L' | 'I' | 'g' | 'l' | 'i') ; +fragment DECIMAL_TYPE_MODIFIER: ('G' | 'D' | 'F' | 'g' | 'd' | 'f') ; +fragment DECIMAL_ONLY_TYPE_MODIFIER: ( 'D' | 'F' | 'd' | 'f') ; +KW_CLASS: 'class' ; +KW_INTERFACE: 'interface' ; +KW_TRAIT: 'trait' ; +KW_ENUM: 'enum' ; +KW_PACKAGE: 'package' ; +KW_IMPORT: 'import' ; +KW_EXTENDS: 'extends' ; +KW_IMPLEMENTS: 'implements' ; +KW_DEF: 'def' ; +KW_NULL: 'null' ; +KW_TRUE: 'true' ; +KW_FALSE: 'false' ; +KW_NEW: 'new' ; +KW_SUPER: 'super' ; +KW_THIS: 'this' ; +KW_IN: 'in' ; +KW_FOR: 'for' ; +KW_IF: 'if' ; +KW_ELSE: 'else' ; +KW_DO: 'do' ; +KW_WHILE: 'while' ; +KW_SWITCH: 'switch' ; +KW_CASE: 'case' ; +KW_DEFAULT: 'default' ; +KW_CONTINUE: 'continue' ; +KW_BREAK: 'break' ; +KW_RETURN: 'return' ; +KW_TRY: 'try' ; +KW_CATCH: 'catch' ; +KW_FINALLY: 'finally' ; +KW_THROW: 'throw' ; +KW_THROWS: 'throws' ; +KW_ASSERT: 'assert' ; +KW_CONST: 'const'; +KW_GOTO: 'goto'; +RUSHIFT_ASSIGN: '>>>=' ; +RSHIFT_ASSIGN: '>>=' ; +LSHIFT_ASSIGN: '<<=' ; +//RUSHIFT: '>>>' ; +SPACESHIP: '<=>' ; +ELVIS: '?:' ; +SAFE_DOT: '?.' ; +STAR_DOT: '*.' ; +ATTR_DOT: '.@' ; +MEMBER_POINTER: '.&' ; +LTE: '<=' ; +GTE: '>=' ; +CLOSURE_ARG_SEPARATOR: '->' ; +DECREMENT: '--' ; +INCREMENT: '++' ; +POWER: '**' ; +LSHIFT: '<<' ; +//RSHIFT: '>>' ; +RANGE: '..' ; +ORANGE: '..<' ; +EQUAL: '==' ; +UNEQUAL: '!=' ; +MATCH: '==~' ; +FIND: '=~' ; +AND: '&&' ; +OR: '||' ; +PLUS_ASSIGN: '+=' ; +MINUS_ASSIGN: '-=' ; +MULT_ASSIGN: '*=' ; +DIV_ASSIGN: '/=' ; +MOD_ASSIGN: '%=' ; +BAND_ASSIGN: '&=' ; +XOR_ASSIGN: '^=' ; +BOR_ASSIGN: '|=' ; +SEMICOLON: ';' ; +DOT: '.' ; +COMMA: ',' ; +AT: '@' ; +ASSIGN: '=' ; +LT: '<' ; +GT: '>' ; +COLON: ':' ; +BOR: '|' ; +NOT: '!' ; +BNOT: '~' ; +MULT: '*' ; +DIV: '/' ; +MOD: '%' ; +PLUS: '+' ; +MINUS: '-' ; +BAND: '&' ; +XOR: '^' ; +QUESTION: '?' ; +ELLIPSIS: '...'; +KW_AS: 'as' ; +KW_INSTANCEOF: 'instanceof' ; +BUILT_IN_TYPE: 'void' | 'boolean' | 'byte' | 'char' | 'short' | 'int' | 'float' | 'long' | 'double'; +// Modifiers +VISIBILITY_MODIFIER: (KW_PUBLIC | KW_PROTECTED | KW_PRIVATE) ; +fragment KW_PUBLIC: 'public' ; +fragment KW_PROTECTED: 'protected' ; +fragment KW_PRIVATE: 'private' ; +KW_ABSTRACT: 'abstract' ; +KW_STATIC: 'static' ; +KW_FINAL: 'final' ; // Class +KW_TRANSIENT: 'transient' ; // methods and fields +KW_NATIVE: 'native' ; // Methods and fields, as fields are accesors in Groovy. +KW_VOLATILE: 'volatile' ; // Fields only +KW_SYNCHRONIZED: 'synchronized' ; // Methods and fields. +KW_STRICTFP: 'strictfp'; +KW_THREADSAFE: 'threadsafe'; +/** Nested newline within a (..) or [..] are ignored. */ +IGNORE_NEWLINE : '\r'? '\n' { topBrace == Brace.ROUND || topBrace == Brace.SQUARE }? -> skip ; +// Match both UNIX and Windows newlines +NL: '\r'? '\n'; +IDENTIFIER: JavaLetter JavaLetterOrDigit*; // reference https://github.com/antlr/grammars-v4/blob/master/java8/Java8.g4 +fragment +JavaLetter + : [a-zA-Z$_] // these are the "java letters" below 0x7F + | JavaUnicodeChar + ; +fragment +JavaLetterOrDigit + : [a-zA-Z0-9$_] // these are the "java letters or digits" below 0x7F + | JavaUnicodeChar + ; +fragment +JavaLetterInGString + : [a-zA-Z_] + | JavaUnicodeChar + ; +fragment +JavaLetterOrDigitInGString + : [a-zA-Z0-9_] + | JavaUnicodeChar + ; +fragment +JavaUnicodeChar + : // covers all characters above 0x7F which are not a surrogate + ~[\u0000-\u007F\uD800-\uDBFF] + {Character.isJavaIdentifierPart(_input.LA(-1))}? + | // covers UTF-16 surrogate pairs encodings for U+10000 to U+10FFFF + [\uD800-\uDBFF] [\uDC00-\uDFFF] + {Character.isJavaIdentifierPart(Character.toCodePoint((char)_input.LA(-2), (char)_input.LA(-1)))}? + ; \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.java b/app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.java new file mode 100644 index 00000000..bd90d825 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/groovy/GroovyLexer.java @@ -0,0 +1,989 @@ +// Generated from C:/Users/bounc/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/groovy\GroovyLexer.g4 by ANTLR 4.9.1 +package com.tyron.code.language.groovy; + + import java.util.ArrayDeque; + import java.util.Arrays; + import java.util.Deque; + import java.util.Set; + import java.util.HashSet; + +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Token; + import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.dfa.DFA; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) +public class GroovyLexer extends Lexer { + static { RuntimeMetaData.checkVersion("4.9.1", RuntimeMetaData.VERSION); } + + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + public static final int + SHEBANG_COMMENT=1, WS=2, LPAREN=3, RPAREN=4, LBRACK=5, RBRACK=6, LCURVE=7, + RCURVE=8, STRING=9, GSTRING_START=10, GSTRING_END=11, GSTRING_PART=12, + GSTRING_PATH_PART=13, ROLLBACK_ONE=14, DECIMAL=15, INTEGER=16, KW_CLASS=17, + KW_INTERFACE=18, KW_TRAIT=19, KW_ENUM=20, KW_PACKAGE=21, KW_IMPORT=22, + KW_EXTENDS=23, KW_IMPLEMENTS=24, KW_DEF=25, KW_NULL=26, KW_TRUE=27, KW_FALSE=28, + KW_NEW=29, KW_SUPER=30, KW_THIS=31, KW_IN=32, KW_FOR=33, KW_IF=34, KW_ELSE=35, + KW_DO=36, KW_WHILE=37, KW_SWITCH=38, KW_CASE=39, KW_DEFAULT=40, KW_CONTINUE=41, + KW_BREAK=42, KW_RETURN=43, KW_TRY=44, KW_CATCH=45, KW_FINALLY=46, KW_THROW=47, + KW_THROWS=48, KW_ASSERT=49, KW_CONST=50, KW_GOTO=51, RUSHIFT_ASSIGN=52, + RSHIFT_ASSIGN=53, LSHIFT_ASSIGN=54, SPACESHIP=55, ELVIS=56, SAFE_DOT=57, + STAR_DOT=58, ATTR_DOT=59, MEMBER_POINTER=60, LTE=61, GTE=62, CLOSURE_ARG_SEPARATOR=63, + DECREMENT=64, INCREMENT=65, POWER=66, LSHIFT=67, RANGE=68, ORANGE=69, + EQUAL=70, UNEQUAL=71, MATCH=72, FIND=73, AND=74, OR=75, PLUS_ASSIGN=76, + MINUS_ASSIGN=77, MULT_ASSIGN=78, DIV_ASSIGN=79, MOD_ASSIGN=80, BAND_ASSIGN=81, + XOR_ASSIGN=82, BOR_ASSIGN=83, SEMICOLON=84, DOT=85, COMMA=86, AT=87, ASSIGN=88, + LT=89, GT=90, COLON=91, BOR=92, NOT=93, BNOT=94, MULT=95, DIV=96, MOD=97, + PLUS=98, MINUS=99, BAND=100, XOR=101, QUESTION=102, ELLIPSIS=103, KW_AS=104, + KW_INSTANCEOF=105, BUILT_IN_TYPE=106, VISIBILITY_MODIFIER=107, KW_ABSTRACT=108, + KW_STATIC=109, KW_FINAL=110, KW_TRANSIENT=111, KW_NATIVE=112, KW_VOLATILE=113, + KW_SYNCHRONIZED=114, KW_STRICTFP=115, KW_THREADSAFE=116, IGNORE_NEWLINE=117, + NL=118, IDENTIFIER=119; + public static final int + TRIPLE_QUOTED_GSTRING_MODE=1, DOUBLE_QUOTED_GSTRING_MODE=2, SLASHY_GSTRING_MODE=3, + DOLLAR_SLASHY_GSTRING_MODE=4, GSTRING_TYPE_SELECTOR_MODE=5, GSTRING_PATH=6; + public static String[] channelNames = { + "DEFAULT_TOKEN_CHANNEL", "HIDDEN" + }; + + public static String[] modeNames = { + "DEFAULT_MODE", "TRIPLE_QUOTED_GSTRING_MODE", "DOUBLE_QUOTED_GSTRING_MODE", + "SLASHY_GSTRING_MODE", "DOLLAR_SLASHY_GSTRING_MODE", "GSTRING_TYPE_SELECTOR_MODE", + "GSTRING_PATH" + }; + + private static String[] makeRuleNames() { + return new String[] { + "LINE_COMMENT", "DOC_COMMENT", "BLOCK_COMMENT", "SHEBANG_COMMENT", "WS", + "LPAREN", "RPAREN", "LBRACK", "RBRACK", "LCURVE", "RCURVE", "MULTILINE_STRING", + "MULTILINE_GSTRING_START", "STRING", "SLASHY_STRING", "DOLLAR_SLASHY_STRING", + "GSTRING_START", "SLASHY_GSTRING_START", "DOLLAR_SLASHY_GSTRING_START", + "SLASHY_STRING_ELEMENT", "DOLLAR_SLASHY_STRING_ELEMENT", "TSQ_STRING_ELEMENT", + "SQ_STRING_ELEMENT", "TDQ_STRING_ELEMENT", "DQ_STRING_ELEMENT", "TSQ", + "TDQ", "LDS", "RDS", "IDENTIFIER_IN_GSTRING", "MULTILINE_GSTRING_END", + "MULTILINE_GSTRING_PART", "MULTILINE_GSTRING_ELEMENT", "GSTRING_END", + "GSTRING_PART", "GSTRING_ELEMENT", "SLASHY_GSTRING_END", "SLASHY_GSTRING_PART", + "SLASHY_GSTRING_ELEMENT", "DOLLAR_SLASHY_GSTRING_END", "DOLLAR_SLASHY_GSTRING_PART", + "DOLLAR_SLASHY_GSTRING_ELEMENT", "GSTRING_BRACE_L", "GSTRING_ID", "GSTRING_PATH_PART", + "ROLLBACK_ONE", "SLASHY_ESCAPE", "DOLLAR_ESCAPE", "JOIN_LINE_ESCAPE", + "ESC_SEQUENCE", "OCTAL_ESC_SEQ", "UNICODE_ESCAPE", "DECIMAL", "INTEGER", + "DIGITS", "DEC_DIGITS", "OCT_DIGITS", "ZERO_TO_SEVEN", "HEX_DIGITS", + "HEX_DIGIT", "BIN_DIGITS", "BIN_DIGIT", "SIGN", "EXP_PART", "DIGIT", + "INTEGER_TYPE_MODIFIER", "DECIMAL_TYPE_MODIFIER", "DECIMAL_ONLY_TYPE_MODIFIER", + "KW_CLASS", "KW_INTERFACE", "KW_TRAIT", "KW_ENUM", "KW_PACKAGE", "KW_IMPORT", + "KW_EXTENDS", "KW_IMPLEMENTS", "KW_DEF", "KW_NULL", "KW_TRUE", "KW_FALSE", + "KW_NEW", "KW_SUPER", "KW_THIS", "KW_IN", "KW_FOR", "KW_IF", "KW_ELSE", + "KW_DO", "KW_WHILE", "KW_SWITCH", "KW_CASE", "KW_DEFAULT", "KW_CONTINUE", + "KW_BREAK", "KW_RETURN", "KW_TRY", "KW_CATCH", "KW_FINALLY", "KW_THROW", + "KW_THROWS", "KW_ASSERT", "KW_CONST", "KW_GOTO", "RUSHIFT_ASSIGN", "RSHIFT_ASSIGN", + "LSHIFT_ASSIGN", "SPACESHIP", "ELVIS", "SAFE_DOT", "STAR_DOT", "ATTR_DOT", + "MEMBER_POINTER", "LTE", "GTE", "CLOSURE_ARG_SEPARATOR", "DECREMENT", + "INCREMENT", "POWER", "LSHIFT", "RANGE", "ORANGE", "EQUAL", "UNEQUAL", + "MATCH", "FIND", "AND", "OR", "PLUS_ASSIGN", "MINUS_ASSIGN", "MULT_ASSIGN", + "DIV_ASSIGN", "MOD_ASSIGN", "BAND_ASSIGN", "XOR_ASSIGN", "BOR_ASSIGN", + "SEMICOLON", "DOT", "COMMA", "AT", "ASSIGN", "LT", "GT", "COLON", "BOR", + "NOT", "BNOT", "MULT", "DIV", "MOD", "PLUS", "MINUS", "BAND", "XOR", + "QUESTION", "ELLIPSIS", "KW_AS", "KW_INSTANCEOF", "BUILT_IN_TYPE", "VISIBILITY_MODIFIER", + "KW_PUBLIC", "KW_PROTECTED", "KW_PRIVATE", "KW_ABSTRACT", "KW_STATIC", + "KW_FINAL", "KW_TRANSIENT", "KW_NATIVE", "KW_VOLATILE", "KW_SYNCHRONIZED", + "KW_STRICTFP", "KW_THREADSAFE", "IGNORE_NEWLINE", "NL", "IDENTIFIER", + "JavaLetter", "JavaLetterOrDigit", "JavaLetterInGString", "JavaLetterOrDigitInGString", + "JavaUnicodeChar" + }; + } + public static final String[] ruleNames = makeRuleNames(); + + private static String[] makeLiteralNames() { + return new String[] { + null, null, null, null, null, null, null, null, null, null, null, "'\"'", + "'$'", null, null, null, null, "'class'", "'interface'", "'trait'", "'enum'", + "'package'", "'import'", "'extends'", "'implements'", "'def'", "'null'", + "'true'", "'false'", "'new'", "'super'", "'this'", "'in'", "'for'", "'if'", + "'else'", "'do'", "'while'", "'switch'", "'case'", "'default'", "'continue'", + "'break'", "'return'", "'try'", "'catch'", "'finally'", "'throw'", "'throws'", + "'assert'", "'const'", "'goto'", "'>>>='", "'>>='", "'<<='", "'<=>'", + "'?:'", "'?.'", "'*.'", "'.@'", "'.&'", "'<='", "'>='", "'->'", "'--'", + "'++'", "'**'", "'<<'", "'..'", "'..<'", "'=='", "'!='", "'==~'", "'=~'", + "'&&'", "'||'", "'+='", "'-='", "'*='", "'/='", "'%='", "'&='", "'^='", + "'|='", "';'", "'.'", "','", "'@'", "'='", "'<'", "'>'", "':'", "'|'", + "'!'", "'~'", "'*'", "'/'", "'%'", "'+'", "'-'", "'&'", "'^'", "'?'", + "'...'", "'as'", "'instanceof'", null, null, "'abstract'", "'static'", + "'final'", "'transient'", "'native'", "'volatile'", "'synchronized'", + "'strictfp'", "'threadsafe'" + }; + } + private static final String[] _LITERAL_NAMES = makeLiteralNames(); + private static String[] makeSymbolicNames() { + return new String[] { + null, "SHEBANG_COMMENT", "WS", "LPAREN", "RPAREN", "LBRACK", "RBRACK", + "LCURVE", "RCURVE", "STRING", "GSTRING_START", "GSTRING_END", "GSTRING_PART", + "GSTRING_PATH_PART", "ROLLBACK_ONE", "DECIMAL", "INTEGER", "KW_CLASS", + "KW_INTERFACE", "KW_TRAIT", "KW_ENUM", "KW_PACKAGE", "KW_IMPORT", "KW_EXTENDS", + "KW_IMPLEMENTS", "KW_DEF", "KW_NULL", "KW_TRUE", "KW_FALSE", "KW_NEW", + "KW_SUPER", "KW_THIS", "KW_IN", "KW_FOR", "KW_IF", "KW_ELSE", "KW_DO", + "KW_WHILE", "KW_SWITCH", "KW_CASE", "KW_DEFAULT", "KW_CONTINUE", "KW_BREAK", + "KW_RETURN", "KW_TRY", "KW_CATCH", "KW_FINALLY", "KW_THROW", "KW_THROWS", + "KW_ASSERT", "KW_CONST", "KW_GOTO", "RUSHIFT_ASSIGN", "RSHIFT_ASSIGN", + "LSHIFT_ASSIGN", "SPACESHIP", "ELVIS", "SAFE_DOT", "STAR_DOT", "ATTR_DOT", + "MEMBER_POINTER", "LTE", "GTE", "CLOSURE_ARG_SEPARATOR", "DECREMENT", + "INCREMENT", "POWER", "LSHIFT", "RANGE", "ORANGE", "EQUAL", "UNEQUAL", + "MATCH", "FIND", "AND", "OR", "PLUS_ASSIGN", "MINUS_ASSIGN", "MULT_ASSIGN", + "DIV_ASSIGN", "MOD_ASSIGN", "BAND_ASSIGN", "XOR_ASSIGN", "BOR_ASSIGN", + "SEMICOLON", "DOT", "COMMA", "AT", "ASSIGN", "LT", "GT", "COLON", "BOR", + "NOT", "BNOT", "MULT", "DIV", "MOD", "PLUS", "MINUS", "BAND", "XOR", + "QUESTION", "ELLIPSIS", "KW_AS", "KW_INSTANCEOF", "BUILT_IN_TYPE", "VISIBILITY_MODIFIER", + "KW_ABSTRACT", "KW_STATIC", "KW_FINAL", "KW_TRANSIENT", "KW_NATIVE", + "KW_VOLATILE", "KW_SYNCHRONIZED", "KW_STRICTFP", "KW_THREADSAFE", "IGNORE_NEWLINE", + "NL", "IDENTIFIER" + }; + } + private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + static { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) { + tokenNames[i] = ""; + } + } + } + + @Override + @Deprecated + public String[] getTokenNames() { + return tokenNames; + } + + @Override + + public Vocabulary getVocabulary() { + return VOCABULARY; + } + + + private static final Set ALLOWED_OP_SET = new HashSet(Arrays.asList(COMMA, NOT, BNOT, PLUS, ASSIGN, PLUS_ASSIGN, LT, GT, LTE, GTE, EQUAL, UNEQUAL, FIND, MATCH, DOT, SAFE_DOT, STAR_DOT, ATTR_DOT, MEMBER_POINTER, ELVIS, QUESTION, COLON, AND, OR, KW_ASSERT, KW_RETURN)); // the allowed ops before slashy string. e.g. p1=/ab/; p2=~/ab/; p3=!/ab/ + private static enum Brace { + ROUND, + SQUARE, + CURVE + }; + private Deque braceStack = new ArrayDeque(); + private Brace topBrace = null; + private int lastTokenType = 0; + private long tokenIndex = 0; + private long tlePos = 0; + + @Override + public void emit(Token token) { + tokenIndex++; + int tokenType = token.getType(); + if (NL != tokenType) { // newline should be ignored + lastTokenType = tokenType; + } + + //System.out.println("EM: " + tokenNames[lastTokenType != -1 ? lastTokenType : 0] + ": " + lastTokenType + " TLE = " + (tlePos == tokenIndex) + " " + tlePos + "/" + tokenIndex + " " + token.getText()); + if (tokenType == ROLLBACK_ONE) { + this.rollbackOneChar(); + } + + super.emit(token); + } + + // just a hook, which will be overrided by GroovyLangLexer + protected void rollbackOneChar() {} + + private void pushBrace(Brace b) { + braceStack.push(b); + topBrace = braceStack.peekFirst(); + //System.out.println("> " + topBrace); + } + + private void popBrace() { + braceStack.pop(); + topBrace = braceStack.peekFirst(); + //System.out.println("> " + topBrace); + } + + + private boolean isSlashyStringAllowed() { + //System.out.println("SP: " + " TLECheck = " + (tlePos == tokenIndex) + " " + tlePos + "/" + tokenIndex); + boolean isLastTokenOp = ALLOWED_OP_SET.contains(Integer.valueOf(lastTokenType)); + boolean res = isLastTokenOp || tlePos == tokenIndex; + //System.out.println("SP: " + tokenNames[lastTokenType] + ": " + lastTokenType + " res " + res + (res ? ( isLastTokenOp ? " op" : " tle") : "")); + return res; + } + + + + public GroovyLexer(CharStream input) { + super(input); + _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + + @Override + public String getGrammarFileName() { return "GroovyLexer.g4"; } + + @Override + public String[] getRuleNames() { return ruleNames; } + + @Override + public String getSerializedATN() { return _serializedATN; } + + @Override + public String[] getChannelNames() { return channelNames; } + + @Override + public String[] getModeNames() { return modeNames; } + + @Override + public ATN getATN() { return _ATN; } + + @Override + public void action(RuleContext _localctx, int ruleIndex, int actionIndex) { + switch (ruleIndex) { + case 5: + LPAREN_action((RuleContext)_localctx, actionIndex); + break; + case 6: + RPAREN_action((RuleContext)_localctx, actionIndex); + break; + case 7: + LBRACK_action((RuleContext)_localctx, actionIndex); + break; + case 8: + RBRACK_action((RuleContext)_localctx, actionIndex); + break; + case 9: + LCURVE_action((RuleContext)_localctx, actionIndex); + break; + case 10: + RCURVE_action((RuleContext)_localctx, actionIndex); + break; + case 42: + GSTRING_BRACE_L_action((RuleContext)_localctx, actionIndex); + break; + } + } + private void LPAREN_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 0: + pushBrace(Brace.ROUND); tlePos = tokenIndex + 1; + break; + } + } + private void RPAREN_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 1: + popBrace(); + break; + } + } + private void LBRACK_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 2: + pushBrace(Brace.SQUARE); tlePos = tokenIndex + 1; + break; + } + } + private void RBRACK_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 3: + popBrace(); + break; + } + } + private void LCURVE_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 4: + pushBrace(Brace.CURVE); tlePos = tokenIndex + 1; + break; + } + } + private void RCURVE_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 5: + popBrace(); + break; + } + } + private void GSTRING_BRACE_L_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 6: + pushBrace(Brace.CURVE); tlePos = tokenIndex + 1; + break; + } + } + @Override + public boolean sempred(RuleContext _localctx, int ruleIndex, int predIndex) { + switch (ruleIndex) { + case 3: + return SHEBANG_COMMENT_sempred((RuleContext)_localctx, predIndex); + case 14: + return SLASHY_STRING_sempred((RuleContext)_localctx, predIndex); + case 15: + return DOLLAR_SLASHY_STRING_sempred((RuleContext)_localctx, predIndex); + case 17: + return SLASHY_GSTRING_START_sempred((RuleContext)_localctx, predIndex); + case 18: + return DOLLAR_SLASHY_GSTRING_START_sempred((RuleContext)_localctx, predIndex); + case 19: + return SLASHY_STRING_ELEMENT_sempred((RuleContext)_localctx, predIndex); + case 20: + return DOLLAR_SLASHY_STRING_ELEMENT_sempred((RuleContext)_localctx, predIndex); + case 21: + return TSQ_STRING_ELEMENT_sempred((RuleContext)_localctx, predIndex); + case 23: + return TDQ_STRING_ELEMENT_sempred((RuleContext)_localctx, predIndex); + case 171: + return IGNORE_NEWLINE_sempred((RuleContext)_localctx, predIndex); + case 178: + return JavaUnicodeChar_sempred((RuleContext)_localctx, predIndex); + } + return true; + } + private boolean SHEBANG_COMMENT_sempred(RuleContext _localctx, int predIndex) { + switch (predIndex) { + case 0: + return tokenIndex == 0 ; + } + return true; + } + private boolean SLASHY_STRING_sempred(RuleContext _localctx, int predIndex) { + switch (predIndex) { + case 1: + return isSlashyStringAllowed() ; + } + return true; + } + private boolean DOLLAR_SLASHY_STRING_sempred(RuleContext _localctx, int predIndex) { + switch (predIndex) { + case 2: + return isSlashyStringAllowed() ; + } + return true; + } + private boolean SLASHY_GSTRING_START_sempred(RuleContext _localctx, int predIndex) { + switch (predIndex) { + case 3: + return isSlashyStringAllowed() ; + } + return true; + } + private boolean DOLLAR_SLASHY_GSTRING_START_sempred(RuleContext _localctx, int predIndex) { + switch (predIndex) { + case 4: + return isSlashyStringAllowed() ; + } + return true; + } + private boolean SLASHY_STRING_ELEMENT_sempred(RuleContext _localctx, int predIndex) { + switch (predIndex) { + case 5: + return false; + } + return true; + } + private boolean DOLLAR_SLASHY_STRING_ELEMENT_sempred(RuleContext _localctx, int predIndex) { + switch (predIndex) { + case 6: + return _input.LA(1) != '$' ; + case 7: + return false; + } + return true; + } + private boolean TSQ_STRING_ELEMENT_sempred(RuleContext _localctx, int predIndex) { + switch (predIndex) { + case 8: + return !(_input.LA(1) == '\'' && _input.LA(2) == '\'') ; + } + return true; + } + private boolean TDQ_STRING_ELEMENT_sempred(RuleContext _localctx, int predIndex) { + switch (predIndex) { + case 9: + return !(_input.LA(1) == '"' && _input.LA(2) == '"') ; + } + return true; + } + private boolean IGNORE_NEWLINE_sempred(RuleContext _localctx, int predIndex) { + switch (predIndex) { + case 10: + return topBrace == Brace.ROUND || topBrace == Brace.SQUARE ; + } + return true; + } + private boolean JavaUnicodeChar_sempred(RuleContext _localctx, int predIndex) { + switch (predIndex) { + case 11: + return Character.isJavaIdentifierPart(_input.LA(-1)); + case 12: + return Character.isJavaIdentifierPart(Character.toCodePoint((char)_input.LA(-2), (char)_input.LA(-1))); + } + return true; + } + + public static final String _serializedATN = + "\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2y\u05a8\b\1\b\1\b"+ + "\1\b\1\b\1\b\1\b\1\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b"+ + "\t\b\4\t\t\t\4\n\t\n\4\13\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20"+ + "\t\20\4\21\t\21\4\22\t\22\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27"+ + "\t\27\4\30\t\30\4\31\t\31\4\32\t\32\4\33\t\33\4\34\t\34\4\35\t\35\4\36"+ + "\t\36\4\37\t\37\4 \t \4!\t!\4\"\t\"\4#\t#\4$\t$\4%\t%\4&\t&\4\'\t\'\4"+ + "(\t(\4)\t)\4*\t*\4+\t+\4,\t,\4-\t-\4.\t.\4/\t/\4\60\t\60\4\61\t\61\4\62"+ + "\t\62\4\63\t\63\4\64\t\64\4\65\t\65\4\66\t\66\4\67\t\67\48\t8\49\t9\4"+ + ":\t:\4;\t;\4<\t<\4=\t=\4>\t>\4?\t?\4@\t@\4A\tA\4B\tB\4C\tC\4D\tD\4E\t"+ + "E\4F\tF\4G\tG\4H\tH\4I\tI\4J\tJ\4K\tK\4L\tL\4M\tM\4N\tN\4O\tO\4P\tP\4"+ + "Q\tQ\4R\tR\4S\tS\4T\tT\4U\tU\4V\tV\4W\tW\4X\tX\4Y\tY\4Z\tZ\4[\t[\4\\\t"+ + "\\\4]\t]\4^\t^\4_\t_\4`\t`\4a\ta\4b\tb\4c\tc\4d\td\4e\te\4f\tf\4g\tg\4"+ + "h\th\4i\ti\4j\tj\4k\tk\4l\tl\4m\tm\4n\tn\4o\to\4p\tp\4q\tq\4r\tr\4s\t"+ + "s\4t\tt\4u\tu\4v\tv\4w\tw\4x\tx\4y\ty\4z\tz\4{\t{\4|\t|\4}\t}\4~\t~\4"+ + "\177\t\177\4\u0080\t\u0080\4\u0081\t\u0081\4\u0082\t\u0082\4\u0083\t\u0083"+ + "\4\u0084\t\u0084\4\u0085\t\u0085\4\u0086\t\u0086\4\u0087\t\u0087\4\u0088"+ + "\t\u0088\4\u0089\t\u0089\4\u008a\t\u008a\4\u008b\t\u008b\4\u008c\t\u008c"+ + "\4\u008d\t\u008d\4\u008e\t\u008e\4\u008f\t\u008f\4\u0090\t\u0090\4\u0091"+ + "\t\u0091\4\u0092\t\u0092\4\u0093\t\u0093\4\u0094\t\u0094\4\u0095\t\u0095"+ + "\4\u0096\t\u0096\4\u0097\t\u0097\4\u0098\t\u0098\4\u0099\t\u0099\4\u009a"+ + "\t\u009a\4\u009b\t\u009b\4\u009c\t\u009c\4\u009d\t\u009d\4\u009e\t\u009e"+ + "\4\u009f\t\u009f\4\u00a0\t\u00a0\4\u00a1\t\u00a1\4\u00a2\t\u00a2\4\u00a3"+ + "\t\u00a3\4\u00a4\t\u00a4\4\u00a5\t\u00a5\4\u00a6\t\u00a6\4\u00a7\t\u00a7"+ + "\4\u00a8\t\u00a8\4\u00a9\t\u00a9\4\u00aa\t\u00aa\4\u00ab\t\u00ab\4\u00ac"+ + "\t\u00ac\4\u00ad\t\u00ad\4\u00ae\t\u00ae\4\u00af\t\u00af\4\u00b0\t\u00b0"+ + "\4\u00b1\t\u00b1\4\u00b2\t\u00b2\4\u00b3\t\u00b3\4\u00b4\t\u00b4\3\2\3"+ + "\2\3\2\3\2\7\2\u0174\n\2\f\2\16\2\u0177\13\2\3\2\5\2\u017a\n\2\3\2\3\2"+ + "\3\3\3\3\3\3\3\3\3\3\7\3\u0183\n\3\f\3\16\3\u0186\13\3\3\3\3\3\3\3\3\3"+ + "\3\3\3\4\3\4\3\4\3\4\7\4\u0191\n\4\f\4\16\4\u0194\13\4\3\4\3\4\3\4\3\4"+ + "\3\4\3\5\3\5\3\5\3\5\3\5\7\5\u01a0\n\5\f\5\16\5\u01a3\13\5\3\5\3\5\3\5"+ + "\3\5\3\6\6\6\u01aa\n\6\r\6\16\6\u01ab\3\6\3\6\3\7\3\7\3\7\3\7\3\7\3\b"+ + "\3\b\3\b\3\b\3\b\3\t\3\t\3\t\3\t\3\t\3\n\3\n\3\n\3\n\3\n\3\13\3\13\3\13"+ + "\3\13\3\13\3\f\3\f\3\f\3\f\3\f\3\r\3\r\7\r\u01d0\n\r\f\r\16\r\u01d3\13"+ + "\r\3\r\3\r\3\r\3\r\7\r\u01d9\n\r\f\r\16\r\u01dc\13\r\3\r\3\r\5\r\u01e0"+ + "\n\r\3\r\3\r\3\16\3\16\7\16\u01e6\n\16\f\16\16\16\u01e9\13\16\3\16\3\16"+ + "\3\16\3\16\3\16\3\16\3\17\3\17\7\17\u01f3\n\17\f\17\16\17\u01f6\13\17"+ + "\3\17\3\17\3\17\7\17\u01fb\n\17\f\17\16\17\u01fe\13\17\3\17\5\17\u0201"+ + "\n\17\3\20\3\20\3\20\6\20\u0206\n\20\r\20\16\20\u0207\3\20\3\20\3\20\3"+ + "\20\3\21\3\21\3\21\7\21\u0211\n\21\f\21\16\21\u0214\13\21\3\21\3\21\3"+ + "\21\3\21\3\22\3\22\7\22\u021c\n\22\f\22\16\22\u021f\13\22\3\22\3\22\3"+ + "\22\3\22\3\22\3\23\3\23\3\23\7\23\u0229\n\23\f\23\16\23\u022c\13\23\3"+ + "\23\3\23\3\23\3\23\3\23\3\23\3\24\3\24\3\24\7\24\u0237\n\24\f\24\16\24"+ + "\u023a\13\24\3\24\3\24\3\24\3\24\3\24\3\24\3\25\3\25\3\25\3\25\5\25\u0246"+ + "\n\25\3\26\3\26\3\26\3\26\3\26\3\26\5\26\u024e\n\26\3\27\3\27\3\27\3\27"+ + "\3\27\3\27\5\27\u0256\n\27\3\30\3\30\3\30\3\30\5\30\u025c\n\30\3\31\3"+ + "\31\3\31\3\31\3\31\3\31\5\31\u0264\n\31\3\32\3\32\3\32\3\32\5\32\u026a"+ + "\n\32\3\33\3\33\3\33\3\33\3\34\3\34\3\34\3\34\3\35\3\35\3\35\3\36\3\36"+ + "\3\36\3\37\3\37\7\37\u027c\n\37\f\37\16\37\u027f\13\37\3 \3 \3 \3 \3 "+ + "\3!\3!\3!\3!\3!\3\"\3\"\3\"\3\"\3#\3#\3#\3#\3$\3$\3$\3$\3%\3%\3%\3%\3"+ + "&\5&\u029c\n&\3&\3&\3&\3&\3&\3\'\3\'\3\'\3\'\3\'\3(\3(\3(\3(\3)\3)\3)"+ + "\3)\3)\3*\3*\3*\3*\3*\3+\3+\3+\3+\3,\3,\3,\3,\3,\3,\3,\3-\3-\3-\3-\3-"+ + "\3-\3.\3.\3.\3/\3/\3/\3/\3/\3\60\3\60\3\60\3\61\3\61\3\61\3\62\3\62\5"+ + "\62\u02d7\n\62\3\62\3\62\3\63\3\63\3\63\3\63\5\63\u02df\n\63\3\64\3\64"+ + "\5\64\u02e3\n\64\3\64\5\64\u02e6\n\64\3\64\3\64\3\65\3\65\3\65\3\65\3"+ + "\65\3\65\3\65\3\66\3\66\3\66\3\66\5\66\u02f5\n\66\3\66\5\66\u02f8\n\66"+ + "\3\66\5\66\u02fb\n\66\3\66\3\66\3\66\5\66\u0300\n\66\3\67\3\67\3\67\3"+ + "\67\5\67\u0306\n\67\3\67\3\67\3\67\3\67\3\67\5\67\u030d\n\67\3\67\3\67"+ + "\3\67\3\67\5\67\u0313\n\67\3\67\5\67\u0316\n\67\38\38\38\38\78\u031c\n"+ + "8\f8\168\u031f\138\38\38\58\u0323\n8\39\39\39\39\79\u0329\n9\f9\169\u032c"+ + "\139\39\59\u032f\n9\3:\3:\3:\3:\7:\u0335\n:\f:\16:\u0338\13:\3:\3:\5:"+ + "\u033c\n:\3;\3;\3<\3<\3<\3<\7<\u0344\n<\f<\16<\u0347\13<\3<\3<\5<\u034b"+ + "\n<\3=\3=\3>\3>\3>\3>\7>\u0353\n>\f>\16>\u0356\13>\3>\3>\5>\u035a\n>\3"+ + "?\3?\3@\3@\3A\3A\5A\u0362\nA\3A\6A\u0365\nA\rA\16A\u0366\3B\3B\3C\3C\3"+ + "D\3D\3E\3E\3F\3F\3F\3F\3F\3F\3G\3G\3G\3G\3G\3G\3G\3G\3G\3G\3H\3H\3H\3"+ + "H\3H\3H\3I\3I\3I\3I\3I\3J\3J\3J\3J\3J\3J\3J\3J\3K\3K\3K\3K\3K\3K\3K\3"+ + "L\3L\3L\3L\3L\3L\3L\3L\3M\3M\3M\3M\3M\3M\3M\3M\3M\3M\3M\3N\3N\3N\3N\3"+ + "O\3O\3O\3O\3O\3P\3P\3P\3P\3P\3Q\3Q\3Q\3Q\3Q\3Q\3R\3R\3R\3R\3S\3S\3S\3"+ + "S\3S\3S\3T\3T\3T\3T\3T\3U\3U\3U\3V\3V\3V\3V\3W\3W\3W\3X\3X\3X\3X\3X\3"+ + "Y\3Y\3Y\3Z\3Z\3Z\3Z\3Z\3Z\3[\3[\3[\3[\3[\3[\3[\3\\\3\\\3\\\3\\\3\\\3]"+ + "\3]\3]\3]\3]\3]\3]\3]\3^\3^\3^\3^\3^\3^\3^\3^\3^\3_\3_\3_\3_\3_\3_\3`"+ + "\3`\3`\3`\3`\3`\3`\3a\3a\3a\3a\3b\3b\3b\3b\3b\3b\3c\3c\3c\3c\3c\3c\3c"+ + "\3c\3d\3d\3d\3d\3d\3d\3e\3e\3e\3e\3e\3e\3e\3f\3f\3f\3f\3f\3f\3f\3g\3g"+ + "\3g\3g\3g\3g\3h\3h\3h\3h\3h\3i\3i\3i\3i\3i\3j\3j\3j\3j\3k\3k\3k\3k\3l"+ + "\3l\3l\3l\3m\3m\3m\3n\3n\3n\3o\3o\3o\3p\3p\3p\3q\3q\3q\3r\3r\3r\3s\3s"+ + "\3s\3t\3t\3t\3u\3u\3u\3v\3v\3v\3w\3w\3w\3x\3x\3x\3y\3y\3y\3z\3z\3z\3z"+ + "\3{\3{\3{\3|\3|\3|\3}\3}\3}\3}\3~\3~\3~\3\177\3\177\3\177\3\u0080\3\u0080"+ + "\3\u0080\3\u0081\3\u0081\3\u0081\3\u0082\3\u0082\3\u0082\3\u0083\3\u0083"+ + "\3\u0083\3\u0084\3\u0084\3\u0084\3\u0085\3\u0085\3\u0085\3\u0086\3\u0086"+ + "\3\u0086\3\u0087\3\u0087\3\u0087\3\u0088\3\u0088\3\u0088\3\u0089\3\u0089"+ + "\3\u008a\3\u008a\3\u008b\3\u008b\3\u008c\3\u008c\3\u008d\3\u008d\3\u008e"+ + "\3\u008e\3\u008f\3\u008f\3\u0090\3\u0090\3\u0091\3\u0091\3\u0092\3\u0092"+ + "\3\u0093\3\u0093\3\u0094\3\u0094\3\u0095\3\u0095\3\u0096\3\u0096\3\u0097"+ + "\3\u0097\3\u0098\3\u0098\3\u0099\3\u0099\3\u009a\3\u009a\3\u009b\3\u009b"+ + "\3\u009c\3\u009c\3\u009c\3\u009c\3\u009d\3\u009d\3\u009d\3\u009e\3\u009e"+ + "\3\u009e\3\u009e\3\u009e\3\u009e\3\u009e\3\u009e\3\u009e\3\u009e\3\u009e"+ + "\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f"+ + "\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f"+ + "\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f"+ + "\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f"+ + "\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\3\u009f\5\u009f\u050d\n\u009f"+ + "\3\u00a0\3\u00a0\3\u00a0\5\u00a0\u0512\n\u00a0\3\u00a1\3\u00a1\3\u00a1"+ + "\3\u00a1\3\u00a1\3\u00a1\3\u00a1\3\u00a2\3\u00a2\3\u00a2\3\u00a2\3\u00a2"+ + "\3\u00a2\3\u00a2\3\u00a2\3\u00a2\3\u00a2\3\u00a3\3\u00a3\3\u00a3\3\u00a3"+ + "\3\u00a3\3\u00a3\3\u00a3\3\u00a3\3\u00a4\3\u00a4\3\u00a4\3\u00a4\3\u00a4"+ + "\3\u00a4\3\u00a4\3\u00a4\3\u00a4\3\u00a5\3\u00a5\3\u00a5\3\u00a5\3\u00a5"+ + "\3\u00a5\3\u00a5\3\u00a6\3\u00a6\3\u00a6\3\u00a6\3\u00a6\3\u00a6\3\u00a7"+ + "\3\u00a7\3\u00a7\3\u00a7\3\u00a7\3\u00a7\3\u00a7\3\u00a7\3\u00a7\3\u00a7"+ + "\3\u00a8\3\u00a8\3\u00a8\3\u00a8\3\u00a8\3\u00a8\3\u00a8\3\u00a9\3\u00a9"+ + "\3\u00a9\3\u00a9\3\u00a9\3\u00a9\3\u00a9\3\u00a9\3\u00a9\3\u00aa\3\u00aa"+ + "\3\u00aa\3\u00aa\3\u00aa\3\u00aa\3\u00aa\3\u00aa\3\u00aa\3\u00aa\3\u00aa"+ + "\3\u00aa\3\u00aa\3\u00ab\3\u00ab\3\u00ab\3\u00ab\3\u00ab\3\u00ab\3\u00ab"+ + "\3\u00ab\3\u00ab\3\u00ac\3\u00ac\3\u00ac\3\u00ac\3\u00ac\3\u00ac\3\u00ac"+ + "\3\u00ac\3\u00ac\3\u00ac\3\u00ac\3\u00ad\5\u00ad\u057f\n\u00ad\3\u00ad"+ + "\3\u00ad\3\u00ad\3\u00ad\3\u00ad\3\u00ae\5\u00ae\u0587\n\u00ae\3\u00ae"+ + "\3\u00ae\3\u00af\3\u00af\7\u00af\u058d\n\u00af\f\u00af\16\u00af\u0590"+ + "\13\u00af\3\u00b0\3\u00b0\5\u00b0\u0594\n\u00b0\3\u00b1\3\u00b1\5\u00b1"+ + "\u0598\n\u00b1\3\u00b2\3\u00b2\5\u00b2\u059c\n\u00b2\3\u00b3\3\u00b3\5"+ + "\u00b3\u05a0\n\u00b3\3\u00b4\3\u00b4\3\u00b4\3\u00b4\3\u00b4\5\u00b4\u05a7"+ + "\n\u00b4\20\u0175\u0184\u0192\u01a1\u01d1\u01da\u01e7\u01f4\u01fc\u0207"+ + "\u0212\u021d\u022a\u0238\2\u00b5\t\2\13\2\r\2\17\3\21\4\23\5\25\6\27\7"+ + "\31\b\33\t\35\n\37\2!\2#\13%\2\'\2)\f+\2-\2/\2\61\2\63\2\65\2\67\29\2"+ + ";\2=\2?\2A\2C\2E\2G\2I\2K\rM\16O\2Q\2S\2U\2W\2Y\2[\2]\2_\2a\17c\20e\2"+ + "g\2i\2k\2m\2o\2q\21s\22u\2w\2y\2{\2}\2\177\2\u0081\2\u0083\2\u0085\2\u0087"+ + "\2\u0089\2\u008b\2\u008d\2\u008f\2\u0091\23\u0093\24\u0095\25\u0097\26"+ + "\u0099\27\u009b\30\u009d\31\u009f\32\u00a1\33\u00a3\34\u00a5\35\u00a7"+ + "\36\u00a9\37\u00ab \u00ad!\u00af\"\u00b1#\u00b3$\u00b5%\u00b7&\u00b9\'"+ + "\u00bb(\u00bd)\u00bf*\u00c1+\u00c3,\u00c5-\u00c7.\u00c9/\u00cb\60\u00cd"+ + "\61\u00cf\62\u00d1\63\u00d3\64\u00d5\65\u00d7\66\u00d9\67\u00db8\u00dd"+ + "9\u00df:\u00e1;\u00e3<\u00e5=\u00e7>\u00e9?\u00eb@\u00edA\u00efB\u00f1"+ + "C\u00f3D\u00f5E\u00f7F\u00f9G\u00fbH\u00fdI\u00ffJ\u0101K\u0103L\u0105"+ + "M\u0107N\u0109O\u010bP\u010dQ\u010fR\u0111S\u0113T\u0115U\u0117V\u0119"+ + "W\u011bX\u011dY\u011fZ\u0121[\u0123\\\u0125]\u0127^\u0129_\u012b`\u012d"+ + "a\u012fb\u0131c\u0133d\u0135e\u0137f\u0139g\u013bh\u013di\u013fj\u0141"+ + "k\u0143l\u0145m\u0147\2\u0149\2\u014b\2\u014dn\u014fo\u0151p\u0153q\u0155"+ + "r\u0157s\u0159t\u015bu\u015dv\u015fw\u0161x\u0163y\u0165\2\u0167\2\u0169"+ + "\2\u016b\2\u016d\2\t\2\3\4\5\6\7\b\33\3\3\f\f\4\2\13\13\"\"\6\2\2\2\f"+ + "\f&&\61\61\4\2&&\61\61\4\2))^^\5\2$$&&^^\n\2$$))^^ddhhppttvv\3\2\62\65"+ + "\3\2\63;\3\2\629\5\2\62;CHch\3\2\62\63\4\2--//\4\2GGgg\3\2\62;\b\2IIK"+ + "KNNiikknn\6\2FFHIffhi\6\2FFHHffhh\6\2&&C\\aac|\7\2&&\62;C\\aac|\5\2C\\"+ + "aac|\6\2\62;C\\aac|\4\2\2\u0081\ud802\udc01\3\2\ud802\udc01\3\2\udc02"+ + "\ue001\2\u05d2\2\t\3\2\2\2\2\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21"+ + "\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2\27\3\2\2\2\2\31\3\2\2\2\2\33\3\2"+ + "\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2\2\2\2#\3\2\2\2\2%\3\2\2\2\2\'\3"+ + "\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2\2\2q\3\2\2\2\2s\3\2\2\2\2\u0091"+ + "\3\2\2\2\2\u0093\3\2\2\2\2\u0095\3\2\2\2\2\u0097\3\2\2\2\2\u0099\3\2\2"+ + "\2\2\u009b\3\2\2\2\2\u009d\3\2\2\2\2\u009f\3\2\2\2\2\u00a1\3\2\2\2\2\u00a3"+ + "\3\2\2\2\2\u00a5\3\2\2\2\2\u00a7\3\2\2\2\2\u00a9\3\2\2\2\2\u00ab\3\2\2"+ + "\2\2\u00ad\3\2\2\2\2\u00af\3\2\2\2\2\u00b1\3\2\2\2\2\u00b3\3\2\2\2\2\u00b5"+ + "\3\2\2\2\2\u00b7\3\2\2\2\2\u00b9\3\2\2\2\2\u00bb\3\2\2\2\2\u00bd\3\2\2"+ + "\2\2\u00bf\3\2\2\2\2\u00c1\3\2\2\2\2\u00c3\3\2\2\2\2\u00c5\3\2\2\2\2\u00c7"+ + "\3\2\2\2\2\u00c9\3\2\2\2\2\u00cb\3\2\2\2\2\u00cd\3\2\2\2\2\u00cf\3\2\2"+ + "\2\2\u00d1\3\2\2\2\2\u00d3\3\2\2\2\2\u00d5\3\2\2\2\2\u00d7\3\2\2\2\2\u00d9"+ + "\3\2\2\2\2\u00db\3\2\2\2\2\u00dd\3\2\2\2\2\u00df\3\2\2\2\2\u00e1\3\2\2"+ + "\2\2\u00e3\3\2\2\2\2\u00e5\3\2\2\2\2\u00e7\3\2\2\2\2\u00e9\3\2\2\2\2\u00eb"+ + "\3\2\2\2\2\u00ed\3\2\2\2\2\u00ef\3\2\2\2\2\u00f1\3\2\2\2\2\u00f3\3\2\2"+ + "\2\2\u00f5\3\2\2\2\2\u00f7\3\2\2\2\2\u00f9\3\2\2\2\2\u00fb\3\2\2\2\2\u00fd"+ + "\3\2\2\2\2\u00ff\3\2\2\2\2\u0101\3\2\2\2\2\u0103\3\2\2\2\2\u0105\3\2\2"+ + "\2\2\u0107\3\2\2\2\2\u0109\3\2\2\2\2\u010b\3\2\2\2\2\u010d\3\2\2\2\2\u010f"+ + "\3\2\2\2\2\u0111\3\2\2\2\2\u0113\3\2\2\2\2\u0115\3\2\2\2\2\u0117\3\2\2"+ + "\2\2\u0119\3\2\2\2\2\u011b\3\2\2\2\2\u011d\3\2\2\2\2\u011f\3\2\2\2\2\u0121"+ + "\3\2\2\2\2\u0123\3\2\2\2\2\u0125\3\2\2\2\2\u0127\3\2\2\2\2\u0129\3\2\2"+ + "\2\2\u012b\3\2\2\2\2\u012d\3\2\2\2\2\u012f\3\2\2\2\2\u0131\3\2\2\2\2\u0133"+ + "\3\2\2\2\2\u0135\3\2\2\2\2\u0137\3\2\2\2\2\u0139\3\2\2\2\2\u013b\3\2\2"+ + "\2\2\u013d\3\2\2\2\2\u013f\3\2\2\2\2\u0141\3\2\2\2\2\u0143\3\2\2\2\2\u0145"+ + "\3\2\2\2\2\u014d\3\2\2\2\2\u014f\3\2\2\2\2\u0151\3\2\2\2\2\u0153\3\2\2"+ + "\2\2\u0155\3\2\2\2\2\u0157\3\2\2\2\2\u0159\3\2\2\2\2\u015b\3\2\2\2\2\u015d"+ + "\3\2\2\2\2\u015f\3\2\2\2\2\u0161\3\2\2\2\2\u0163\3\2\2\2\3E\3\2\2\2\3"+ + "G\3\2\2\2\3I\3\2\2\2\4K\3\2\2\2\4M\3\2\2\2\4O\3\2\2\2\5Q\3\2\2\2\5S\3"+ + "\2\2\2\5U\3\2\2\2\6W\3\2\2\2\6Y\3\2\2\2\6[\3\2\2\2\7]\3\2\2\2\7_\3\2\2"+ + "\2\ba\3\2\2\2\bc\3\2\2\2\t\u016f\3\2\2\2\13\u017d\3\2\2\2\r\u018c\3\2"+ + "\2\2\17\u019a\3\2\2\2\21\u01a9\3\2\2\2\23\u01af\3\2\2\2\25\u01b4\3\2\2"+ + "\2\27\u01b9\3\2\2\2\31\u01be\3\2\2\2\33\u01c3\3\2\2\2\35\u01c8\3\2\2\2"+ + "\37\u01df\3\2\2\2!\u01e3\3\2\2\2#\u0200\3\2\2\2%\u0202\3\2\2\2\'\u020d"+ + "\3\2\2\2)\u0219\3\2\2\2+\u0225\3\2\2\2-\u0233\3\2\2\2/\u0245\3\2\2\2\61"+ + "\u024d\3\2\2\2\63\u0255\3\2\2\2\65\u025b\3\2\2\2\67\u0263\3\2\2\29\u0269"+ + "\3\2\2\2;\u026b\3\2\2\2=\u026f\3\2\2\2?\u0273\3\2\2\2A\u0276\3\2\2\2C"+ + "\u0279\3\2\2\2E\u0280\3\2\2\2G\u0285\3\2\2\2I\u028a\3\2\2\2K\u028e\3\2"+ + "\2\2M\u0292\3\2\2\2O\u0296\3\2\2\2Q\u029b\3\2\2\2S\u02a2\3\2\2\2U\u02a7"+ + "\3\2\2\2W\u02ab\3\2\2\2Y\u02b0\3\2\2\2[\u02b5\3\2\2\2]\u02b9\3\2\2\2_"+ + "\u02c0\3\2\2\2a\u02c6\3\2\2\2c\u02c9\3\2\2\2e\u02ce\3\2\2\2g\u02d1\3\2"+ + "\2\2i\u02d4\3\2\2\2k\u02de\3\2\2\2m\u02e0\3\2\2\2o\u02e9\3\2\2\2q\u02ff"+ + "\3\2\2\2s\u0312\3\2\2\2u\u0322\3\2\2\2w\u032e\3\2\2\2y\u033b\3\2\2\2{"+ + "\u033d\3\2\2\2}\u034a\3\2\2\2\177\u034c\3\2\2\2\u0081\u0359\3\2\2\2\u0083"+ + "\u035b\3\2\2\2\u0085\u035d\3\2\2\2\u0087\u035f\3\2\2\2\u0089\u0368\3\2"+ + "\2\2\u008b\u036a\3\2\2\2\u008d\u036c\3\2\2\2\u008f\u036e\3\2\2\2\u0091"+ + "\u0370\3\2\2\2\u0093\u0376\3\2\2\2\u0095\u0380\3\2\2\2\u0097\u0386\3\2"+ + "\2\2\u0099\u038b\3\2\2\2\u009b\u0393\3\2\2\2\u009d\u039a\3\2\2\2\u009f"+ + "\u03a2\3\2\2\2\u00a1\u03ad\3\2\2\2\u00a3\u03b1\3\2\2\2\u00a5\u03b6\3\2"+ + "\2\2\u00a7\u03bb\3\2\2\2\u00a9\u03c1\3\2\2\2\u00ab\u03c5\3\2\2\2\u00ad"+ + "\u03cb\3\2\2\2\u00af\u03d0\3\2\2\2\u00b1\u03d3\3\2\2\2\u00b3\u03d7\3\2"+ + "\2\2\u00b5\u03da\3\2\2\2\u00b7\u03df\3\2\2\2\u00b9\u03e2\3\2\2\2\u00bb"+ + "\u03e8\3\2\2\2\u00bd\u03ef\3\2\2\2\u00bf\u03f4\3\2\2\2\u00c1\u03fc\3\2"+ + "\2\2\u00c3\u0405\3\2\2\2\u00c5\u040b\3\2\2\2\u00c7\u0412\3\2\2\2\u00c9"+ + "\u0416\3\2\2\2\u00cb\u041c\3\2\2\2\u00cd\u0424\3\2\2\2\u00cf\u042a\3\2"+ + "\2\2\u00d1\u0431\3\2\2\2\u00d3\u0438\3\2\2\2\u00d5\u043e\3\2\2\2\u00d7"+ + "\u0443\3\2\2\2\u00d9\u0448\3\2\2\2\u00db\u044c\3\2\2\2\u00dd\u0450\3\2"+ + "\2\2\u00df\u0454\3\2\2\2\u00e1\u0457\3\2\2\2\u00e3\u045a\3\2\2\2\u00e5"+ + "\u045d\3\2\2\2\u00e7\u0460\3\2\2\2\u00e9\u0463\3\2\2\2\u00eb\u0466\3\2"+ + "\2\2\u00ed\u0469\3\2\2\2\u00ef\u046c\3\2\2\2\u00f1\u046f\3\2\2\2\u00f3"+ + "\u0472\3\2\2\2\u00f5\u0475\3\2\2\2\u00f7\u0478\3\2\2\2\u00f9\u047b\3\2"+ + "\2\2\u00fb\u047f\3\2\2\2\u00fd\u0482\3\2\2\2\u00ff\u0485\3\2\2\2\u0101"+ + "\u0489\3\2\2\2\u0103\u048c\3\2\2\2\u0105\u048f\3\2\2\2\u0107\u0492\3\2"+ + "\2\2\u0109\u0495\3\2\2\2\u010b\u0498\3\2\2\2\u010d\u049b\3\2\2\2\u010f"+ + "\u049e\3\2\2\2\u0111\u04a1\3\2\2\2\u0113\u04a4\3\2\2\2\u0115\u04a7\3\2"+ + "\2\2\u0117\u04aa\3\2\2\2\u0119\u04ac\3\2\2\2\u011b\u04ae\3\2\2\2\u011d"+ + "\u04b0\3\2\2\2\u011f\u04b2\3\2\2\2\u0121\u04b4\3\2\2\2\u0123\u04b6\3\2"+ + "\2\2\u0125\u04b8\3\2\2\2\u0127\u04ba\3\2\2\2\u0129\u04bc\3\2\2\2\u012b"+ + "\u04be\3\2\2\2\u012d\u04c0\3\2\2\2\u012f\u04c2\3\2\2\2\u0131\u04c4\3\2"+ + "\2\2\u0133\u04c6\3\2\2\2\u0135\u04c8\3\2\2\2\u0137\u04ca\3\2\2\2\u0139"+ + "\u04cc\3\2\2\2\u013b\u04ce\3\2\2\2\u013d\u04d0\3\2\2\2\u013f\u04d4\3\2"+ + "\2\2\u0141\u04d7\3\2\2\2\u0143\u050c\3\2\2\2\u0145\u0511\3\2\2\2\u0147"+ + "\u0513\3\2\2\2\u0149\u051a\3\2\2\2\u014b\u0524\3\2\2\2\u014d\u052c\3\2"+ + "\2\2\u014f\u0535\3\2\2\2\u0151\u053c\3\2\2\2\u0153\u0542\3\2\2\2\u0155"+ + "\u054c\3\2\2\2\u0157\u0553\3\2\2\2\u0159\u055c\3\2\2\2\u015b\u0569\3\2"+ + "\2\2\u015d\u0572\3\2\2\2\u015f\u057e\3\2\2\2\u0161\u0586\3\2\2\2\u0163"+ + "\u058a\3\2\2\2\u0165\u0593\3\2\2\2\u0167\u0597\3\2\2\2\u0169\u059b\3\2"+ + "\2\2\u016b\u059f\3\2\2\2\u016d\u05a6\3\2\2\2\u016f\u0170\7\61\2\2\u0170"+ + "\u0171\7\61\2\2\u0171\u0175\3\2\2\2\u0172\u0174\13\2\2\2\u0173\u0172\3"+ + "\2\2\2\u0174\u0177\3\2\2\2\u0175\u0176\3\2\2\2\u0175\u0173\3\2\2\2\u0176"+ + "\u0179\3\2\2\2\u0177\u0175\3\2\2\2\u0178\u017a\t\2\2\2\u0179\u0178\3\2"+ + "\2\2\u017a\u017b\3\2\2\2\u017b\u017c\b\2\2\2\u017c\n\3\2\2\2\u017d\u017e"+ + "\7\61\2\2\u017e\u017f\7,\2\2\u017f\u0180\7,\2\2\u0180\u0184\3\2\2\2\u0181"+ + "\u0183\13\2\2\2\u0182\u0181\3\2\2\2\u0183\u0186\3\2\2\2\u0184\u0185\3"+ + "\2\2\2\u0184\u0182\3\2\2\2\u0185\u0187\3\2\2\2\u0186\u0184\3\2\2\2\u0187"+ + "\u0188\7,\2\2\u0188\u0189\7\61\2\2\u0189\u018a\3\2\2\2\u018a\u018b\b\3"+ + "\2\2\u018b\f\3\2\2\2\u018c\u018d\7\61\2\2\u018d\u018e\7,\2\2\u018e\u0192"+ + "\3\2\2\2\u018f\u0191\13\2\2\2\u0190\u018f\3\2\2\2\u0191\u0194\3\2\2\2"+ + "\u0192\u0193\3\2\2\2\u0192\u0190\3\2\2\2\u0193\u0195\3\2\2\2\u0194\u0192"+ + "\3\2\2\2\u0195\u0196\7,\2\2\u0196\u0197\7\61\2\2\u0197\u0198\3\2\2\2\u0198"+ + "\u0199\b\4\2\2\u0199\16\3\2\2\2\u019a\u019b\6\5\2\2\u019b\u019c\7%\2\2"+ + "\u019c\u019d\7#\2\2\u019d\u01a1\3\2\2\2\u019e\u01a0\13\2\2\2\u019f\u019e"+ + "\3\2\2\2\u01a0\u01a3\3\2\2\2\u01a1\u01a2\3\2\2\2\u01a1\u019f\3\2\2\2\u01a2"+ + "\u01a4\3\2\2\2\u01a3\u01a1\3\2\2\2\u01a4\u01a5\7\f\2\2\u01a5\u01a6\3\2"+ + "\2\2\u01a6\u01a7\b\5\3\2\u01a7\20\3\2\2\2\u01a8\u01aa\t\3\2\2\u01a9\u01a8"+ + "\3\2\2\2\u01aa\u01ab\3\2\2\2\u01ab\u01a9\3\2\2\2\u01ab\u01ac\3\2\2\2\u01ac"+ + "\u01ad\3\2\2\2\u01ad\u01ae\b\6\3\2\u01ae\22\3\2\2\2\u01af\u01b0\7*\2\2"+ + "\u01b0\u01b1\b\7\4\2\u01b1\u01b2\3\2\2\2\u01b2\u01b3\b\7\5\2\u01b3\24"+ + "\3\2\2\2\u01b4\u01b5\7+\2\2\u01b5\u01b6\b\b\6\2\u01b6\u01b7\3\2\2\2\u01b7"+ + "\u01b8\b\b\7\2\u01b8\26\3\2\2\2\u01b9\u01ba\7]\2\2\u01ba\u01bb\b\t\b\2"+ + "\u01bb\u01bc\3\2\2\2\u01bc\u01bd\b\t\5\2\u01bd\30\3\2\2\2\u01be\u01bf"+ + "\7_\2\2\u01bf\u01c0\b\n\t\2\u01c0\u01c1\3\2\2\2\u01c1\u01c2\b\n\7\2\u01c2"+ + "\32\3\2\2\2\u01c3\u01c4\7}\2\2\u01c4\u01c5\b\13\n\2\u01c5\u01c6\3\2\2"+ + "\2\u01c6\u01c7\b\13\5\2\u01c7\34\3\2\2\2\u01c8\u01c9\7\177\2\2\u01c9\u01ca"+ + "\b\f\13\2\u01ca\u01cb\3\2\2\2\u01cb\u01cc\b\f\7\2\u01cc\36\3\2\2\2\u01cd"+ + "\u01d1\5;\33\2\u01ce\u01d0\5\63\27\2\u01cf\u01ce\3\2\2\2\u01d0\u01d3\3"+ + "\2\2\2\u01d1\u01d2\3\2\2\2\u01d1\u01cf\3\2\2\2\u01d2\u01d4\3\2\2\2\u01d3"+ + "\u01d1\3\2\2\2\u01d4\u01d5\5;\33\2\u01d5\u01e0\3\2\2\2\u01d6\u01da\5="+ + "\34\2\u01d7\u01d9\5\67\31\2\u01d8\u01d7\3\2\2\2\u01d9\u01dc\3\2\2\2\u01da"+ + "\u01db\3\2\2\2\u01da\u01d8\3\2\2\2\u01db\u01dd\3\2\2\2\u01dc\u01da\3\2"+ + "\2\2\u01dd\u01de\5=\34\2\u01de\u01e0\3\2\2\2\u01df\u01cd\3\2\2\2\u01df"+ + "\u01d6\3\2\2\2\u01e0\u01e1\3\2\2\2\u01e1\u01e2\b\r\f\2\u01e2 \3\2\2\2"+ + "\u01e3\u01e7\5=\34\2\u01e4\u01e6\5\67\31\2\u01e5\u01e4\3\2\2\2\u01e6\u01e9"+ + "\3\2\2\2\u01e7\u01e8\3\2\2\2\u01e7\u01e5\3\2\2\2\u01e8\u01ea\3\2\2\2\u01e9"+ + "\u01e7\3\2\2\2\u01ea\u01eb\7&\2\2\u01eb\u01ec\3\2\2\2\u01ec\u01ed\b\16"+ + "\r\2\u01ed\u01ee\b\16\16\2\u01ee\u01ef\b\16\17\2\u01ef\"\3\2\2\2\u01f0"+ + "\u01f4\7$\2\2\u01f1\u01f3\59\32\2\u01f2\u01f1\3\2\2\2\u01f3\u01f6\3\2"+ + "\2\2\u01f4\u01f5\3\2\2\2\u01f4\u01f2\3\2\2\2\u01f5\u01f7\3\2\2\2\u01f6"+ + "\u01f4\3\2\2\2\u01f7\u0201\7$\2\2\u01f8\u01fc\7)\2\2\u01f9\u01fb\5\65"+ + "\30\2\u01fa\u01f9\3\2\2\2\u01fb\u01fe\3\2\2\2\u01fc\u01fd\3\2\2\2\u01fc"+ + "\u01fa\3\2\2\2\u01fd\u01ff\3\2\2\2\u01fe\u01fc\3\2\2\2\u01ff\u0201\7)"+ + "\2\2\u0200\u01f0\3\2\2\2\u0200\u01f8\3\2\2\2\u0201$\3\2\2\2\u0202\u0203"+ + "\7\61\2\2\u0203\u0205\6\20\3\2\u0204\u0206\5/\25\2\u0205\u0204\3\2\2\2"+ + "\u0206\u0207\3\2\2\2\u0207\u0208\3\2\2\2\u0207\u0205\3\2\2\2\u0208\u0209"+ + "\3\2\2\2\u0209\u020a\7\61\2\2\u020a\u020b\3\2\2\2\u020b\u020c\b\20\f\2"+ + "\u020c&\3\2\2\2\u020d\u020e\5?\35\2\u020e\u0212\6\21\4\2\u020f\u0211\5"+ + "\61\26\2\u0210\u020f\3\2\2\2\u0211\u0214\3\2\2\2\u0212\u0213\3\2\2\2\u0212"+ + "\u0210\3\2\2\2\u0213\u0215\3\2\2\2\u0214\u0212\3\2\2\2\u0215\u0216\5A"+ + "\36\2\u0216\u0217\3\2\2\2\u0217\u0218\b\21\f\2\u0218(\3\2\2\2\u0219\u021d"+ + "\7$\2\2\u021a\u021c\59\32\2\u021b\u021a\3\2\2\2\u021c\u021f\3\2\2\2\u021d"+ + "\u021e\3\2\2\2\u021d\u021b\3\2\2\2\u021e\u0220\3\2\2\2\u021f\u021d\3\2"+ + "\2\2\u0220\u0221\7&\2\2\u0221\u0222\3\2\2\2\u0222\u0223\b\22\20\2\u0223"+ + "\u0224\b\22\17\2\u0224*\3\2\2\2\u0225\u0226\7\61\2\2\u0226\u022a\6\23"+ + "\5\2\u0227\u0229\5/\25\2\u0228\u0227\3\2\2\2\u0229\u022c\3\2\2\2\u022a"+ + "\u022b\3\2\2\2\u022a\u0228\3\2\2\2\u022b\u022d\3\2\2\2\u022c\u022a\3\2"+ + "\2\2\u022d\u022e\7&\2\2\u022e\u022f\3\2\2\2\u022f\u0230\b\23\r\2\u0230"+ + "\u0231\b\23\21\2\u0231\u0232\b\23\17\2\u0232,\3\2\2\2\u0233\u0234\5?\35"+ + "\2\u0234\u0238\6\24\6\2\u0235\u0237\5\61\26\2\u0236\u0235\3\2\2\2\u0237"+ + "\u023a\3\2\2\2\u0238\u0239\3\2\2\2\u0238\u0236\3\2\2\2\u0239\u023b\3\2"+ + "\2\2\u023a\u0238\3\2\2\2\u023b\u023c\7&\2\2\u023c\u023d\3\2\2\2\u023d"+ + "\u023e\b\24\r\2\u023e\u023f\b\24\22\2\u023f\u0240\b\24\17\2\u0240.\3\2"+ + "\2\2\u0241\u0246\5e\60\2\u0242\u0243\7&\2\2\u0243\u0246\6\25\7\2\u0244"+ + "\u0246\n\4\2\2\u0245\u0241\3\2\2\2\u0245\u0242\3\2\2\2\u0245\u0244\3\2"+ + "\2\2\u0246\60\3\2\2\2\u0247\u024e\5e\60\2\u0248\u0249\7\61\2\2\u0249\u024e"+ + "\6\26\b\2\u024a\u024b\7&\2\2\u024b\u024e\6\26\t\2\u024c\u024e\n\5\2\2"+ + "\u024d\u0247\3\2\2\2\u024d\u0248\3\2\2\2\u024d\u024a\3\2\2\2\u024d\u024c"+ + "\3\2\2\2\u024e\62\3\2\2\2\u024f\u0256\5k\63\2\u0250\u0256\5g\61\2\u0251"+ + "\u0256\5i\62\2\u0252\u0253\7)\2\2\u0253\u0256\6\27\n\2\u0254\u0256\n\6"+ + "\2\2\u0255\u024f\3\2\2\2\u0255\u0250\3\2\2\2\u0255\u0251\3\2\2\2\u0255"+ + "\u0252\3\2\2\2\u0255\u0254\3\2\2\2\u0256\64\3\2\2\2\u0257\u025c\5k\63"+ + "\2\u0258\u025c\5g\61\2\u0259\u025c\5i\62\2\u025a\u025c\n\6\2\2\u025b\u0257"+ + "\3\2\2\2\u025b\u0258\3\2\2\2\u025b\u0259\3\2\2\2\u025b\u025a\3\2\2\2\u025c"+ + "\66\3\2\2\2\u025d\u0264\5k\63\2\u025e\u0264\5g\61\2\u025f\u0264\5i\62"+ + "\2\u0260\u0261\7$\2\2\u0261\u0264\6\31\13\2\u0262\u0264\n\7\2\2\u0263"+ + "\u025d\3\2\2\2\u0263\u025e\3\2\2\2\u0263\u025f\3\2\2\2\u0263\u0260\3\2"+ + "\2\2\u0263\u0262\3\2\2\2\u02648\3\2\2\2\u0265\u026a\5k\63\2\u0266\u026a"+ + "\5g\61\2\u0267\u026a\5i\62\2\u0268\u026a\n\7\2\2\u0269\u0265\3\2\2\2\u0269"+ + "\u0266\3\2\2\2\u0269\u0267\3\2\2\2\u0269\u0268\3\2\2\2\u026a:\3\2\2\2"+ + "\u026b\u026c\7)\2\2\u026c\u026d\7)\2\2\u026d\u026e\7)\2\2\u026e<\3\2\2"+ + "\2\u026f\u0270\7$\2\2\u0270\u0271\7$\2\2\u0271\u0272\7$\2\2\u0272>\3\2"+ + "\2\2\u0273\u0274\7&\2\2\u0274\u0275\7\61\2\2\u0275@\3\2\2\2\u0276\u0277"+ + "\7\61\2\2\u0277\u0278\7&\2\2\u0278B\3\2\2\2\u0279\u027d\5\u0169\u00b2"+ + "\2\u027a\u027c\5\u016b\u00b3\2\u027b\u027a\3\2\2\2\u027c\u027f\3\2\2\2"+ + "\u027d\u027b\3\2\2\2\u027d\u027e\3\2\2\2\u027eD\3\2\2\2\u027f\u027d\3"+ + "\2\2\2\u0280\u0281\5=\34\2\u0281\u0282\3\2\2\2\u0282\u0283\b \23\2\u0283"+ + "\u0284\b \7\2\u0284F\3\2\2\2\u0285\u0286\7&\2\2\u0286\u0287\3\2\2\2\u0287"+ + "\u0288\b!\24\2\u0288\u0289\b!\17\2\u0289H\3\2\2\2\u028a\u028b\5\67\31"+ + "\2\u028b\u028c\3\2\2\2\u028c\u028d\b\"\25\2\u028dJ\3\2\2\2\u028e\u028f"+ + "\7$\2\2\u028f\u0290\3\2\2\2\u0290\u0291\b#\7\2\u0291L\3\2\2\2\u0292\u0293"+ + "\7&\2\2\u0293\u0294\3\2\2\2\u0294\u0295\b$\17\2\u0295N\3\2\2\2\u0296\u0297"+ + "\59\32\2\u0297\u0298\3\2\2\2\u0298\u0299\b%\25\2\u0299P\3\2\2\2\u029a"+ + "\u029c\7&\2\2\u029b\u029a\3\2\2\2\u029b\u029c\3\2\2\2\u029c\u029d\3\2"+ + "\2\2\u029d\u029e\7\61\2\2\u029e\u029f\3\2\2\2\u029f\u02a0\b&\23\2\u02a0"+ + "\u02a1\b&\7\2\u02a1R\3\2\2\2\u02a2\u02a3\7&\2\2\u02a3\u02a4\3\2\2\2\u02a4"+ + "\u02a5\b\'\24\2\u02a5\u02a6\b\'\17\2\u02a6T\3\2\2\2\u02a7\u02a8\5/\25"+ + "\2\u02a8\u02a9\3\2\2\2\u02a9\u02aa\b(\25\2\u02aaV\3\2\2\2\u02ab\u02ac"+ + "\5A\36\2\u02ac\u02ad\3\2\2\2\u02ad\u02ae\b)\23\2\u02ae\u02af\b)\7\2\u02af"+ + "X\3\2\2\2\u02b0\u02b1\7&\2\2\u02b1\u02b2\3\2\2\2\u02b2\u02b3\b*\24\2\u02b3"+ + "\u02b4\b*\17\2\u02b4Z\3\2\2\2\u02b5\u02b6\5\61\26\2\u02b6\u02b7\3\2\2"+ + "\2\u02b7\u02b8\b+\25\2\u02b8\\\3\2\2\2\u02b9\u02ba\7}\2\2\u02ba\u02bb"+ + "\b,\26\2\u02bb\u02bc\3\2\2\2\u02bc\u02bd\b,\27\2\u02bd\u02be\b,\7\2\u02be"+ + "\u02bf\b,\5\2\u02bf^\3\2\2\2\u02c0\u02c1\5C\37\2\u02c1\u02c2\3\2\2\2\u02c2"+ + "\u02c3\b-\30\2\u02c3\u02c4\b-\7\2\u02c4\u02c5\b-\31\2\u02c5`\3\2\2\2\u02c6"+ + "\u02c7\7\60\2\2\u02c7\u02c8\5C\37\2\u02c8b\3\2\2\2\u02c9\u02ca\13\2\2"+ + "\2\u02ca\u02cb\3\2\2\2\u02cb\u02cc\b/\7\2\u02cc\u02cd\b/\32\2\u02cdd\3"+ + "\2\2\2\u02ce\u02cf\7^\2\2\u02cf\u02d0\7\61\2\2\u02d0f\3\2\2\2\u02d1\u02d2"+ + "\7^\2\2\u02d2\u02d3\7&\2\2\u02d3h\3\2\2\2\u02d4\u02d6\7^\2\2\u02d5\u02d7"+ + "\7\17\2\2\u02d6\u02d5\3\2\2\2\u02d6\u02d7\3\2\2\2\u02d7\u02d8\3\2\2\2"+ + "\u02d8\u02d9\7\f\2\2\u02d9j\3\2\2\2\u02da\u02db\7^\2\2\u02db\u02df\t\b"+ + "\2\2\u02dc\u02df\5m\64\2\u02dd\u02df\5o\65\2\u02de\u02da\3\2\2\2\u02de"+ + "\u02dc\3\2\2\2\u02de\u02dd\3\2\2\2\u02dfl\3\2\2\2\u02e0\u02e2\7^\2\2\u02e1"+ + "\u02e3\t\t\2\2\u02e2\u02e1\3\2\2\2\u02e2\u02e3\3\2\2\2\u02e3\u02e5\3\2"+ + "\2\2\u02e4\u02e6\5{;\2\u02e5\u02e4\3\2\2\2\u02e5\u02e6\3\2\2\2\u02e6\u02e7"+ + "\3\2\2\2\u02e7\u02e8\5{;\2\u02e8n\3\2\2\2\u02e9\u02ea\7^\2\2\u02ea\u02eb"+ + "\7w\2\2\u02eb\u02ec\5\177=\2\u02ec\u02ed\5\177=\2\u02ed\u02ee\5\177=\2"+ + "\u02ee\u02ef\5\177=\2\u02efp\3\2\2\2\u02f0\u02f7\5u8\2\u02f1\u02f2\7\60"+ + "\2\2\u02f2\u02f4\5u8\2\u02f3\u02f5\5\u0087A\2\u02f4\u02f3\3\2\2\2\u02f4"+ + "\u02f5\3\2\2\2\u02f5\u02f8\3\2\2\2\u02f6\u02f8\5\u0087A\2\u02f7\u02f1"+ + "\3\2\2\2\u02f7\u02f6\3\2\2\2\u02f8\u02fa\3\2\2\2\u02f9\u02fb\5\u008dD"+ + "\2\u02fa\u02f9\3\2\2\2\u02fa\u02fb\3\2\2\2\u02fb\u0300\3\2\2\2\u02fc\u02fd"+ + "\5u8\2\u02fd\u02fe\5\u008fE\2\u02fe\u0300\3\2\2\2\u02ff\u02f0\3\2\2\2"+ + "\u02ff\u02fc\3\2\2\2\u0300r\3\2\2\2\u0301\u0302\7\62\2\2\u0302\u0306\7"+ + "z\2\2\u0303\u0304\7\62\2\2\u0304\u0306\7Z\2\2\u0305\u0301\3\2\2\2\u0305"+ + "\u0303\3\2\2\2\u0306\u0307\3\2\2\2\u0307\u0313\5}<\2\u0308\u0309\7\62"+ + "\2\2\u0309\u030d\7d\2\2\u030a\u030b\7\62\2\2\u030b\u030d\7D\2\2\u030c"+ + "\u0308\3\2\2\2\u030c\u030a\3\2\2\2\u030d\u030e\3\2\2\2\u030e\u0313\5\u0081"+ + ">\2\u030f\u0310\7\62\2\2\u0310\u0313\5y:\2\u0311\u0313\5w9\2\u0312\u0305"+ + "\3\2\2\2\u0312\u030c\3\2\2\2\u0312\u030f\3\2\2\2\u0312\u0311\3\2\2\2\u0313"+ + "\u0315\3\2\2\2\u0314\u0316\5\u008bC\2\u0315\u0314\3\2\2\2\u0315\u0316"+ + "\3\2\2\2\u0316t\3\2\2\2\u0317\u0323\5\u0089B\2\u0318\u031d\5\u0089B\2"+ + "\u0319\u031c\5\u0089B\2\u031a\u031c\7a\2\2\u031b\u0319\3\2\2\2\u031b\u031a"+ + "\3\2\2\2\u031c\u031f\3\2\2\2\u031d\u031b\3\2\2\2\u031d\u031e\3\2\2\2\u031e"+ + "\u0320\3\2\2\2\u031f\u031d\3\2\2\2\u0320\u0321\5\u0089B\2\u0321\u0323"+ + "\3\2\2\2\u0322\u0317\3\2\2\2\u0322\u0318\3\2\2\2\u0323v\3\2\2\2\u0324"+ + "\u032f\5\u0089B\2\u0325\u032a\t\n\2\2\u0326\u0329\5\u0089B\2\u0327\u0329"+ + "\7a\2\2\u0328\u0326\3\2\2\2\u0328\u0327\3\2\2\2\u0329\u032c\3\2\2\2\u032a"+ + "\u0328\3\2\2\2\u032a\u032b\3\2\2\2\u032b\u032d\3\2\2\2\u032c\u032a\3\2"+ + "\2\2\u032d\u032f\5\u0089B\2\u032e\u0324\3\2\2\2\u032e\u0325\3\2\2\2\u032f"+ + "x\3\2\2\2\u0330\u033c\5{;\2\u0331\u0336\5{;\2\u0332\u0335\5{;\2\u0333"+ + "\u0335\7a\2\2\u0334\u0332\3\2\2\2\u0334\u0333\3\2\2\2\u0335\u0338\3\2"+ + "\2\2\u0336\u0334\3\2\2\2\u0336\u0337\3\2\2\2\u0337\u0339\3\2\2\2\u0338"+ + "\u0336\3\2\2\2\u0339\u033a\5{;\2\u033a\u033c\3\2\2\2\u033b\u0330\3\2\2"+ + "\2\u033b\u0331\3\2\2\2\u033cz\3\2\2\2\u033d\u033e\t\13\2\2\u033e|\3\2"+ + "\2\2\u033f\u034b\5\177=\2\u0340\u0345\5\177=\2\u0341\u0344\5\177=\2\u0342"+ + "\u0344\7a\2\2\u0343\u0341\3\2\2\2\u0343\u0342\3\2\2\2\u0344\u0347\3\2"+ + "\2\2\u0345\u0343\3\2\2\2\u0345\u0346\3\2\2\2\u0346\u0348\3\2\2\2\u0347"+ + "\u0345\3\2\2\2\u0348\u0349\5\177=\2\u0349\u034b\3\2\2\2\u034a\u033f\3"+ + "\2\2\2\u034a\u0340\3\2\2\2\u034b~\3\2\2\2\u034c\u034d\t\f\2\2\u034d\u0080"+ + "\3\2\2\2\u034e\u035a\5\u0083?\2\u034f\u0354\5\u0083?\2\u0350\u0353\5\u0083"+ + "?\2\u0351\u0353\7a\2\2\u0352\u0350\3\2\2\2\u0352\u0351\3\2\2\2\u0353\u0356"+ + "\3\2\2\2\u0354\u0352\3\2\2\2\u0354\u0355\3\2\2\2\u0355\u0357\3\2\2\2\u0356"+ + "\u0354\3\2\2\2\u0357\u0358\5\u0083?\2\u0358\u035a\3\2\2\2\u0359\u034e"+ + "\3\2\2\2\u0359\u034f\3\2\2\2\u035a\u0082\3\2\2\2\u035b\u035c\t\r\2\2\u035c"+ + "\u0084\3\2\2\2\u035d\u035e\t\16\2\2\u035e\u0086\3\2\2\2\u035f\u0361\t"+ + "\17\2\2\u0360\u0362\5\u0085@\2\u0361\u0360\3\2\2\2\u0361\u0362\3\2\2\2"+ + "\u0362\u0364\3\2\2\2\u0363\u0365\5\u0089B\2\u0364\u0363\3\2\2\2\u0365"+ + "\u0366\3\2\2\2\u0366\u0364\3\2\2\2\u0366\u0367\3\2\2\2\u0367\u0088\3\2"+ + "\2\2\u0368\u0369\t\20\2\2\u0369\u008a\3\2\2\2\u036a\u036b\t\21\2\2\u036b"+ + "\u008c\3\2\2\2\u036c\u036d\t\22\2\2\u036d\u008e\3\2\2\2\u036e\u036f\t"+ + "\23\2\2\u036f\u0090\3\2\2\2\u0370\u0371\7e\2\2\u0371\u0372\7n\2\2\u0372"+ + "\u0373\7c\2\2\u0373\u0374\7u\2\2\u0374\u0375\7u\2\2\u0375\u0092\3\2\2"+ + "\2\u0376\u0377\7k\2\2\u0377\u0378\7p\2\2\u0378\u0379\7v\2\2\u0379\u037a"+ + "\7g\2\2\u037a\u037b\7t\2\2\u037b\u037c\7h\2\2\u037c\u037d\7c\2\2\u037d"+ + "\u037e\7e\2\2\u037e\u037f\7g\2\2\u037f\u0094\3\2\2\2\u0380\u0381\7v\2"+ + "\2\u0381\u0382\7t\2\2\u0382\u0383\7c\2\2\u0383\u0384\7k\2\2\u0384\u0385"+ + "\7v\2\2\u0385\u0096\3\2\2\2\u0386\u0387\7g\2\2\u0387\u0388\7p\2\2\u0388"+ + "\u0389\7w\2\2\u0389\u038a\7o\2\2\u038a\u0098\3\2\2\2\u038b\u038c\7r\2"+ + "\2\u038c\u038d\7c\2\2\u038d\u038e\7e\2\2\u038e\u038f\7m\2\2\u038f\u0390"+ + "\7c\2\2\u0390\u0391\7i\2\2\u0391\u0392\7g\2\2\u0392\u009a\3\2\2\2\u0393"+ + "\u0394\7k\2\2\u0394\u0395\7o\2\2\u0395\u0396\7r\2\2\u0396\u0397\7q\2\2"+ + "\u0397\u0398\7t\2\2\u0398\u0399\7v\2\2\u0399\u009c\3\2\2\2\u039a\u039b"+ + "\7g\2\2\u039b\u039c\7z\2\2\u039c\u039d\7v\2\2\u039d\u039e\7g\2\2\u039e"+ + "\u039f\7p\2\2\u039f\u03a0\7f\2\2\u03a0\u03a1\7u\2\2\u03a1\u009e\3\2\2"+ + "\2\u03a2\u03a3\7k\2\2\u03a3\u03a4\7o\2\2\u03a4\u03a5\7r\2\2\u03a5\u03a6"+ + "\7n\2\2\u03a6\u03a7\7g\2\2\u03a7\u03a8\7o\2\2\u03a8\u03a9\7g\2\2\u03a9"+ + "\u03aa\7p\2\2\u03aa\u03ab\7v\2\2\u03ab\u03ac\7u\2\2\u03ac\u00a0\3\2\2"+ + "\2\u03ad\u03ae\7f\2\2\u03ae\u03af\7g\2\2\u03af\u03b0\7h\2\2\u03b0\u00a2"+ + "\3\2\2\2\u03b1\u03b2\7p\2\2\u03b2\u03b3\7w\2\2\u03b3\u03b4\7n\2\2\u03b4"+ + "\u03b5\7n\2\2\u03b5\u00a4\3\2\2\2\u03b6\u03b7\7v\2\2\u03b7\u03b8\7t\2"+ + "\2\u03b8\u03b9\7w\2\2\u03b9\u03ba\7g\2\2\u03ba\u00a6\3\2\2\2\u03bb\u03bc"+ + "\7h\2\2\u03bc\u03bd\7c\2\2\u03bd\u03be\7n\2\2\u03be\u03bf\7u\2\2\u03bf"+ + "\u03c0\7g\2\2\u03c0\u00a8\3\2\2\2\u03c1\u03c2\7p\2\2\u03c2\u03c3\7g\2"+ + "\2\u03c3\u03c4\7y\2\2\u03c4\u00aa\3\2\2\2\u03c5\u03c6\7u\2\2\u03c6\u03c7"+ + "\7w\2\2\u03c7\u03c8\7r\2\2\u03c8\u03c9\7g\2\2\u03c9\u03ca\7t\2\2\u03ca"+ + "\u00ac\3\2\2\2\u03cb\u03cc\7v\2\2\u03cc\u03cd\7j\2\2\u03cd\u03ce\7k\2"+ + "\2\u03ce\u03cf\7u\2\2\u03cf\u00ae\3\2\2\2\u03d0\u03d1\7k\2\2\u03d1\u03d2"+ + "\7p\2\2\u03d2\u00b0\3\2\2\2\u03d3\u03d4\7h\2\2\u03d4\u03d5\7q\2\2\u03d5"+ + "\u03d6\7t\2\2\u03d6\u00b2\3\2\2\2\u03d7\u03d8\7k\2\2\u03d8\u03d9\7h\2"+ + "\2\u03d9\u00b4\3\2\2\2\u03da\u03db\7g\2\2\u03db\u03dc\7n\2\2\u03dc\u03dd"+ + "\7u\2\2\u03dd\u03de\7g\2\2\u03de\u00b6\3\2\2\2\u03df\u03e0\7f\2\2\u03e0"+ + "\u03e1\7q\2\2\u03e1\u00b8\3\2\2\2\u03e2\u03e3\7y\2\2\u03e3\u03e4\7j\2"+ + "\2\u03e4\u03e5\7k\2\2\u03e5\u03e6\7n\2\2\u03e6\u03e7\7g\2\2\u03e7\u00ba"+ + "\3\2\2\2\u03e8\u03e9\7u\2\2\u03e9\u03ea\7y\2\2\u03ea\u03eb\7k\2\2\u03eb"+ + "\u03ec\7v\2\2\u03ec\u03ed\7e\2\2\u03ed\u03ee\7j\2\2\u03ee\u00bc\3\2\2"+ + "\2\u03ef\u03f0\7e\2\2\u03f0\u03f1\7c\2\2\u03f1\u03f2\7u\2\2\u03f2\u03f3"+ + "\7g\2\2\u03f3\u00be\3\2\2\2\u03f4\u03f5\7f\2\2\u03f5\u03f6\7g\2\2\u03f6"+ + "\u03f7\7h\2\2\u03f7\u03f8\7c\2\2\u03f8\u03f9\7w\2\2\u03f9\u03fa\7n\2\2"+ + "\u03fa\u03fb\7v\2\2\u03fb\u00c0\3\2\2\2\u03fc\u03fd\7e\2\2\u03fd\u03fe"+ + "\7q\2\2\u03fe\u03ff\7p\2\2\u03ff\u0400\7v\2\2\u0400\u0401\7k\2\2\u0401"+ + "\u0402\7p\2\2\u0402\u0403\7w\2\2\u0403\u0404\7g\2\2\u0404\u00c2\3\2\2"+ + "\2\u0405\u0406\7d\2\2\u0406\u0407\7t\2\2\u0407\u0408\7g\2\2\u0408\u0409"+ + "\7c\2\2\u0409\u040a\7m\2\2\u040a\u00c4\3\2\2\2\u040b\u040c\7t\2\2\u040c"+ + "\u040d\7g\2\2\u040d\u040e\7v\2\2\u040e\u040f\7w\2\2\u040f\u0410\7t\2\2"+ + "\u0410\u0411\7p\2\2\u0411\u00c6\3\2\2\2\u0412\u0413\7v\2\2\u0413\u0414"+ + "\7t\2\2\u0414\u0415\7{\2\2\u0415\u00c8\3\2\2\2\u0416\u0417\7e\2\2\u0417"+ + "\u0418\7c\2\2\u0418\u0419\7v\2\2\u0419\u041a\7e\2\2\u041a\u041b\7j\2\2"+ + "\u041b\u00ca\3\2\2\2\u041c\u041d\7h\2\2\u041d\u041e\7k\2\2\u041e\u041f"+ + "\7p\2\2\u041f\u0420\7c\2\2\u0420\u0421\7n\2\2\u0421\u0422\7n\2\2\u0422"+ + "\u0423\7{\2\2\u0423\u00cc\3\2\2\2\u0424\u0425\7v\2\2\u0425\u0426\7j\2"+ + "\2\u0426\u0427\7t\2\2\u0427\u0428\7q\2\2\u0428\u0429\7y\2\2\u0429\u00ce"+ + "\3\2\2\2\u042a\u042b\7v\2\2\u042b\u042c\7j\2\2\u042c\u042d\7t\2\2\u042d"+ + "\u042e\7q\2\2\u042e\u042f\7y\2\2\u042f\u0430\7u\2\2\u0430\u00d0\3\2\2"+ + "\2\u0431\u0432\7c\2\2\u0432\u0433\7u\2\2\u0433\u0434\7u\2\2\u0434\u0435"+ + "\7g\2\2\u0435\u0436\7t\2\2\u0436\u0437\7v\2\2\u0437\u00d2\3\2\2\2\u0438"+ + "\u0439\7e\2\2\u0439\u043a\7q\2\2\u043a\u043b\7p\2\2\u043b\u043c\7u\2\2"+ + "\u043c\u043d\7v\2\2\u043d\u00d4\3\2\2\2\u043e\u043f\7i\2\2\u043f\u0440"+ + "\7q\2\2\u0440\u0441\7v\2\2\u0441\u0442\7q\2\2\u0442\u00d6\3\2\2\2\u0443"+ + "\u0444\7@\2\2\u0444\u0445\7@\2\2\u0445\u0446\7@\2\2\u0446\u0447\7?\2\2"+ + "\u0447\u00d8\3\2\2\2\u0448\u0449\7@\2\2\u0449\u044a\7@\2\2\u044a\u044b"+ + "\7?\2\2\u044b\u00da\3\2\2\2\u044c\u044d\7>\2\2\u044d\u044e\7>\2\2\u044e"+ + "\u044f\7?\2\2\u044f\u00dc\3\2\2\2\u0450\u0451\7>\2\2\u0451\u0452\7?\2"+ + "\2\u0452\u0453\7@\2\2\u0453\u00de\3\2\2\2\u0454\u0455\7A\2\2\u0455\u0456"+ + "\7<\2\2\u0456\u00e0\3\2\2\2\u0457\u0458\7A\2\2\u0458\u0459\7\60\2\2\u0459"+ + "\u00e2\3\2\2\2\u045a\u045b\7,\2\2\u045b\u045c\7\60\2\2\u045c\u00e4\3\2"+ + "\2\2\u045d\u045e\7\60\2\2\u045e\u045f\7B\2\2\u045f\u00e6\3\2\2\2\u0460"+ + "\u0461\7\60\2\2\u0461\u0462\7(\2\2\u0462\u00e8\3\2\2\2\u0463\u0464\7>"+ + "\2\2\u0464\u0465\7?\2\2\u0465\u00ea\3\2\2\2\u0466\u0467\7@\2\2\u0467\u0468"+ + "\7?\2\2\u0468\u00ec\3\2\2\2\u0469\u046a\7/\2\2\u046a\u046b\7@\2\2\u046b"+ + "\u00ee\3\2\2\2\u046c\u046d\7/\2\2\u046d\u046e\7/\2\2\u046e\u00f0\3\2\2"+ + "\2\u046f\u0470\7-\2\2\u0470\u0471\7-\2\2\u0471\u00f2\3\2\2\2\u0472\u0473"+ + "\7,\2\2\u0473\u0474\7,\2\2\u0474\u00f4\3\2\2\2\u0475\u0476\7>\2\2\u0476"+ + "\u0477\7>\2\2\u0477\u00f6\3\2\2\2\u0478\u0479\7\60\2\2\u0479\u047a\7\60"+ + "\2\2\u047a\u00f8\3\2\2\2\u047b\u047c\7\60\2\2\u047c\u047d\7\60\2\2\u047d"+ + "\u047e\7>\2\2\u047e\u00fa\3\2\2\2\u047f\u0480\7?\2\2\u0480\u0481\7?\2"+ + "\2\u0481\u00fc\3\2\2\2\u0482\u0483\7#\2\2\u0483\u0484\7?\2\2\u0484\u00fe"+ + "\3\2\2\2\u0485\u0486\7?\2\2\u0486\u0487\7?\2\2\u0487\u0488\7\u0080\2\2"+ + "\u0488\u0100\3\2\2\2\u0489\u048a\7?\2\2\u048a\u048b\7\u0080\2\2\u048b"+ + "\u0102\3\2\2\2\u048c\u048d\7(\2\2\u048d\u048e\7(\2\2\u048e\u0104\3\2\2"+ + "\2\u048f\u0490\7~\2\2\u0490\u0491\7~\2\2\u0491\u0106\3\2\2\2\u0492\u0493"+ + "\7-\2\2\u0493\u0494\7?\2\2\u0494\u0108\3\2\2\2\u0495\u0496\7/\2\2\u0496"+ + "\u0497\7?\2\2\u0497\u010a\3\2\2\2\u0498\u0499\7,\2\2\u0499\u049a\7?\2"+ + "\2\u049a\u010c\3\2\2\2\u049b\u049c\7\61\2\2\u049c\u049d\7?\2\2\u049d\u010e"+ + "\3\2\2\2\u049e\u049f\7\'\2\2\u049f\u04a0\7?\2\2\u04a0\u0110\3\2\2\2\u04a1"+ + "\u04a2\7(\2\2\u04a2\u04a3\7?\2\2\u04a3\u0112\3\2\2\2\u04a4\u04a5\7`\2"+ + "\2\u04a5\u04a6\7?\2\2\u04a6\u0114\3\2\2\2\u04a7\u04a8\7~\2\2\u04a8\u04a9"+ + "\7?\2\2\u04a9\u0116\3\2\2\2\u04aa\u04ab\7=\2\2\u04ab\u0118\3\2\2\2\u04ac"+ + "\u04ad\7\60\2\2\u04ad\u011a\3\2\2\2\u04ae\u04af\7.\2\2\u04af\u011c\3\2"+ + "\2\2\u04b0\u04b1\7B\2\2\u04b1\u011e\3\2\2\2\u04b2\u04b3\7?\2\2\u04b3\u0120"+ + "\3\2\2\2\u04b4\u04b5\7>\2\2\u04b5\u0122\3\2\2\2\u04b6\u04b7\7@\2\2\u04b7"+ + "\u0124\3\2\2\2\u04b8\u04b9\7<\2\2\u04b9\u0126\3\2\2\2\u04ba\u04bb\7~\2"+ + "\2\u04bb\u0128\3\2\2\2\u04bc\u04bd\7#\2\2\u04bd\u012a\3\2\2\2\u04be\u04bf"+ + "\7\u0080\2\2\u04bf\u012c\3\2\2\2\u04c0\u04c1\7,\2\2\u04c1\u012e\3\2\2"+ + "\2\u04c2\u04c3\7\61\2\2\u04c3\u0130\3\2\2\2\u04c4\u04c5\7\'\2\2\u04c5"+ + "\u0132\3\2\2\2\u04c6\u04c7\7-\2\2\u04c7\u0134\3\2\2\2\u04c8\u04c9\7/\2"+ + "\2\u04c9\u0136\3\2\2\2\u04ca\u04cb\7(\2\2\u04cb\u0138\3\2\2\2\u04cc\u04cd"+ + "\7`\2\2\u04cd\u013a\3\2\2\2\u04ce\u04cf\7A\2\2\u04cf\u013c\3\2\2\2\u04d0"+ + "\u04d1\7\60\2\2\u04d1\u04d2\7\60\2\2\u04d2\u04d3\7\60\2\2\u04d3\u013e"+ + "\3\2\2\2\u04d4\u04d5\7c\2\2\u04d5\u04d6\7u\2\2\u04d6\u0140\3\2\2\2\u04d7"+ + "\u04d8\7k\2\2\u04d8\u04d9\7p\2\2\u04d9\u04da\7u\2\2\u04da\u04db\7v\2\2"+ + "\u04db\u04dc\7c\2\2\u04dc\u04dd\7p\2\2\u04dd\u04de\7e\2\2\u04de\u04df"+ + "\7g\2\2\u04df\u04e0\7q\2\2\u04e0\u04e1\7h\2\2\u04e1\u0142\3\2\2\2\u04e2"+ + "\u04e3\7x\2\2\u04e3\u04e4\7q\2\2\u04e4\u04e5\7k\2\2\u04e5\u050d\7f\2\2"+ + "\u04e6\u04e7\7d\2\2\u04e7\u04e8\7q\2\2\u04e8\u04e9\7q\2\2\u04e9\u04ea"+ + "\7n\2\2\u04ea\u04eb\7g\2\2\u04eb\u04ec\7c\2\2\u04ec\u050d\7p\2\2\u04ed"+ + "\u04ee\7d\2\2\u04ee\u04ef\7{\2\2\u04ef\u04f0\7v\2\2\u04f0\u050d\7g\2\2"+ + "\u04f1\u04f2\7e\2\2\u04f2\u04f3\7j\2\2\u04f3\u04f4\7c\2\2\u04f4\u050d"+ + "\7t\2\2\u04f5\u04f6\7u\2\2\u04f6\u04f7\7j\2\2\u04f7\u04f8\7q\2\2\u04f8"+ + "\u04f9\7t\2\2\u04f9\u050d\7v\2\2\u04fa\u04fb\7k\2\2\u04fb\u04fc\7p\2\2"+ + "\u04fc\u050d\7v\2\2\u04fd\u04fe\7h\2\2\u04fe\u04ff\7n\2\2\u04ff\u0500"+ + "\7q\2\2\u0500\u0501\7c\2\2\u0501\u050d\7v\2\2\u0502\u0503\7n\2\2\u0503"+ + "\u0504\7q\2\2\u0504\u0505\7p\2\2\u0505\u050d\7i\2\2\u0506\u0507\7f\2\2"+ + "\u0507\u0508\7q\2\2\u0508\u0509\7w\2\2\u0509\u050a\7d\2\2\u050a\u050b"+ + "\7n\2\2\u050b\u050d\7g\2\2\u050c\u04e2\3\2\2\2\u050c\u04e6\3\2\2\2\u050c"+ + "\u04ed\3\2\2\2\u050c\u04f1\3\2\2\2\u050c\u04f5\3\2\2\2\u050c\u04fa\3\2"+ + "\2\2\u050c\u04fd\3\2\2\2\u050c\u0502\3\2\2\2\u050c\u0506\3\2\2\2\u050d"+ + "\u0144\3\2\2\2\u050e\u0512\5\u0147\u00a1\2\u050f\u0512\5\u0149\u00a2\2"+ + "\u0510\u0512\5\u014b\u00a3\2\u0511\u050e\3\2\2\2\u0511\u050f\3\2\2\2\u0511"+ + "\u0510\3\2\2\2\u0512\u0146\3\2\2\2\u0513\u0514\7r\2\2\u0514\u0515\7w\2"+ + "\2\u0515\u0516\7d\2\2\u0516\u0517\7n\2\2\u0517\u0518\7k\2\2\u0518\u0519"+ + "\7e\2\2\u0519\u0148\3\2\2\2\u051a\u051b\7r\2\2\u051b\u051c\7t\2\2\u051c"+ + "\u051d\7q\2\2\u051d\u051e\7v\2\2\u051e\u051f\7g\2\2\u051f\u0520\7e\2\2"+ + "\u0520\u0521\7v\2\2\u0521\u0522\7g\2\2\u0522\u0523\7f\2\2\u0523\u014a"+ + "\3\2\2\2\u0524\u0525\7r\2\2\u0525\u0526\7t\2\2\u0526\u0527\7k\2\2\u0527"+ + "\u0528\7x\2\2\u0528\u0529\7c\2\2\u0529\u052a\7v\2\2\u052a\u052b\7g\2\2"+ + "\u052b\u014c\3\2\2\2\u052c\u052d\7c\2\2\u052d\u052e\7d\2\2\u052e\u052f"+ + "\7u\2\2\u052f\u0530\7v\2\2\u0530\u0531\7t\2\2\u0531\u0532\7c\2\2\u0532"+ + "\u0533\7e\2\2\u0533\u0534\7v\2\2\u0534\u014e\3\2\2\2\u0535\u0536\7u\2"+ + "\2\u0536\u0537\7v\2\2\u0537\u0538\7c\2\2\u0538\u0539\7v\2\2\u0539\u053a"+ + "\7k\2\2\u053a\u053b\7e\2\2\u053b\u0150\3\2\2\2\u053c\u053d\7h\2\2\u053d"+ + "\u053e\7k\2\2\u053e\u053f\7p\2\2\u053f\u0540\7c\2\2\u0540\u0541\7n\2\2"+ + "\u0541\u0152\3\2\2\2\u0542\u0543\7v\2\2\u0543\u0544\7t\2\2\u0544\u0545"+ + "\7c\2\2\u0545\u0546\7p\2\2\u0546\u0547\7u\2\2\u0547\u0548\7k\2\2\u0548"+ + "\u0549\7g\2\2\u0549\u054a\7p\2\2\u054a\u054b\7v\2\2\u054b\u0154\3\2\2"+ + "\2\u054c\u054d\7p\2\2\u054d\u054e\7c\2\2\u054e\u054f\7v\2\2\u054f\u0550"+ + "\7k\2\2\u0550\u0551\7x\2\2\u0551\u0552\7g\2\2\u0552\u0156\3\2\2\2\u0553"+ + "\u0554\7x\2\2\u0554\u0555\7q\2\2\u0555\u0556\7n\2\2\u0556\u0557\7c\2\2"+ + "\u0557\u0558\7v\2\2\u0558\u0559\7k\2\2\u0559\u055a\7n\2\2\u055a\u055b"+ + "\7g\2\2\u055b\u0158\3\2\2\2\u055c\u055d\7u\2\2\u055d\u055e\7{\2\2\u055e"+ + "\u055f\7p\2\2\u055f\u0560\7e\2\2\u0560\u0561\7j\2\2\u0561\u0562\7t\2\2"+ + "\u0562\u0563\7q\2\2\u0563\u0564\7p\2\2\u0564\u0565\7k\2\2\u0565\u0566"+ + "\7|\2\2\u0566\u0567\7g\2\2\u0567\u0568\7f\2\2\u0568\u015a\3\2\2\2\u0569"+ + "\u056a\7u\2\2\u056a\u056b\7v\2\2\u056b\u056c\7t\2\2\u056c\u056d\7k\2\2"+ + "\u056d\u056e\7e\2\2\u056e\u056f\7v\2\2\u056f\u0570\7h\2\2\u0570\u0571"+ + "\7r\2\2\u0571\u015c\3\2\2\2\u0572\u0573\7v\2\2\u0573\u0574\7j\2\2\u0574"+ + "\u0575\7t\2\2\u0575\u0576\7g\2\2\u0576\u0577\7c\2\2\u0577\u0578\7f\2\2"+ + "\u0578\u0579\7u\2\2\u0579\u057a\7c\2\2\u057a\u057b\7h\2\2\u057b\u057c"+ + "\7g\2\2\u057c\u015e\3\2\2\2\u057d\u057f\7\17\2\2\u057e\u057d\3\2\2\2\u057e"+ + "\u057f\3\2\2\2\u057f\u0580\3\2\2\2\u0580\u0581\7\f\2\2\u0581\u0582\6\u00ad"+ + "\f\2\u0582\u0583\3\2\2\2\u0583\u0584\b\u00ad\3\2\u0584\u0160\3\2\2\2\u0585"+ + "\u0587\7\17\2\2\u0586\u0585\3\2\2\2\u0586\u0587\3\2\2\2\u0587\u0588\3"+ + "\2\2\2\u0588\u0589\7\f\2\2\u0589\u0162\3\2\2\2\u058a\u058e\5\u0165\u00b0"+ + "\2\u058b\u058d\5\u0167\u00b1\2\u058c\u058b\3\2\2\2\u058d\u0590\3\2\2\2"+ + "\u058e\u058c\3\2\2\2\u058e\u058f\3\2\2\2\u058f\u0164\3\2\2\2\u0590\u058e"+ + "\3\2\2\2\u0591\u0594\t\24\2\2\u0592\u0594\5\u016d\u00b4\2\u0593\u0591"+ + "\3\2\2\2\u0593\u0592\3\2\2\2\u0594\u0166\3\2\2\2\u0595\u0598\t\25\2\2"+ + "\u0596\u0598\5\u016d\u00b4\2\u0597\u0595\3\2\2\2\u0597\u0596\3\2\2\2\u0598"+ + "\u0168\3\2\2\2\u0599\u059c\t\26\2\2\u059a\u059c\5\u016d\u00b4\2\u059b"+ + "\u0599\3\2\2\2\u059b\u059a\3\2\2\2\u059c\u016a\3\2\2\2\u059d\u05a0\t\27"+ + "\2\2\u059e\u05a0\5\u016d\u00b4\2\u059f\u059d\3\2\2\2\u059f\u059e\3\2\2"+ + "\2\u05a0\u016c\3\2\2\2\u05a1\u05a2\n\30\2\2\u05a2\u05a7\6\u00b4\r\2\u05a3"+ + "\u05a4\t\31\2\2\u05a4\u05a5\t\32\2\2\u05a5\u05a7\6\u00b4\16\2\u05a6\u05a1"+ + "\3\2\2\2\u05a6\u05a3\3\2\2\2\u05a7\u016e\3\2\2\2J\2\3\4\5\6\7\b\u0175"+ + "\u0179\u0184\u0192\u01a1\u01ab\u01d1\u01da\u01df\u01e7\u01f4\u01fc\u0200"+ + "\u0207\u0212\u021d\u022a\u0238\u0245\u024d\u0255\u025b\u0263\u0269\u027d"+ + "\u029b\u02d6\u02de\u02e2\u02e5\u02f4\u02f7\u02fa\u02ff\u0305\u030c\u0312"+ + "\u0315\u031b\u031d\u0322\u0328\u032a\u032e\u0334\u0336\u033b\u0343\u0345"+ + "\u034a\u0352\u0354\u0359\u0361\u0366\u050c\u0511\u057e\u0586\u058e\u0593"+ + "\u0597\u059b\u059f\u05a6\33\tx\2\b\2\2\3\7\2\7\2\2\3\b\3\6\2\2\3\t\4\3"+ + "\n\5\3\13\6\3\f\7\t\13\2\t\f\2\7\3\2\7\7\2\7\4\2\7\5\2\7\6\2\t\r\2\t\16"+ + "\2\5\2\2\3,\b\t\t\2\ty\2\7\b\2\2\3\2"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + static { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/java/IncrementalJavaAnalyzeManager.java b/app/src/main/java/com/tyron/code/language/java/IncrementalJavaAnalyzeManager.java new file mode 100644 index 00000000..6be6bab6 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/IncrementalJavaAnalyzeManager.java @@ -0,0 +1,84 @@ +package com.tyron.code.language.java; + +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.jetbrains.kotlin.com.intellij.lang.java.lexer.JavaLexer; +import org.jetbrains.kotlin.com.intellij.lexer.Lexer; +import org.jetbrains.kotlin.com.intellij.lexer.LexerPosition; +import org.jetbrains.kotlin.com.intellij.pom.java.LanguageLevel; + +import java.util.List; + +import io.github.rosemoe.sora.lang.analysis.IncrementalAnalyzeManager; +import io.github.rosemoe.sora.lang.analysis.StyleReceiver; +import io.github.rosemoe.sora.lang.styling.Span; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.ContentReference; + +public class IncrementalJavaAnalyzeManager + implements IncrementalAnalyzeManager { + + private final Lexer mLexer; + + public IncrementalJavaAnalyzeManager() { + mLexer = new JavaLexer(LanguageLevel.HIGHEST); + } + + @Override + public LexerPosition getInitialState() { + return mLexer.getCurrentPosition(); + } + + @Override + public LineTokenizeResult getState(int line) { + return null; + } + + @Override + public boolean stateEquals(LexerPosition state, LexerPosition another) { + return state.equals(another); + } + + @Override + public LineTokenizeResult tokenizeLine(CharSequence line, LexerPosition state) { + return null; + } + + @Override + public List generateSpansForLine(LineTokenizeResult tokens) { + return null; + } + + @Override + public void setReceiver(@Nullable StyleReceiver receiver) { + + } + + @Override + public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { + mLexer.start(content.getReference().toString()); + } + + @Override + public void insert(CharPosition start, CharPosition end, CharSequence insertedContent) { + + } + + @Override + public void delete(CharPosition start, CharPosition end, CharSequence deletedContent) { + + } + + @Override + public void rerun() { + + } + + @Override + public void destroy() { + + } +} diff --git a/app/src/main/java/com/tyron/code/language/java/Java.java b/app/src/main/java/com/tyron/code/language/java/Java.java new file mode 100644 index 00000000..1b2f5124 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/Java.java @@ -0,0 +1,19 @@ +package com.tyron.code.language.java; + +import com.tyron.code.language.Language; +import com.tyron.editor.Editor; + +import java.io.File; + +public class Java implements Language { + + @Override + public boolean isApplicable(File ext) { + return ext.getName().endsWith(".java"); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return new JavaLanguage(editor); + } +} diff --git a/app/src/main/java/com/tyron/code/language/java/JavaAnalyzer.java b/app/src/main/java/com/tyron/code/language/java/JavaAnalyzer.java new file mode 100644 index 00000000..90829ead --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/JavaAnalyzer.java @@ -0,0 +1,277 @@ +package com.tyron.code.language.java; + +import android.content.SharedPreferences; +import android.content.res.AssetManager; +import android.os.Looper; +import android.util.Log; + +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.builder.model.SourceFileObject; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.JavaModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.BuildConfig; +import com.tyron.code.analyzer.DiagnosticTextmateAnalyzer; +import com.tyron.code.analyzer.SemanticAnalyzeManager; +import com.tyron.code.analyzer.semantic.SemanticToken; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.common.util.Debouncer; +import com.tyron.completion.index.CompilerService; +import com.tyron.completion.java.JavaCompilerProvider; +import com.tyron.completion.java.compiler.CompileTask; +import com.tyron.completion.java.compiler.CompilerContainer; +import com.tyron.completion.java.compiler.JavaCompilerService; +import com.tyron.completion.java.util.ErrorCodes; +import com.tyron.completion.java.util.TreeUtil; +import com.tyron.completion.progress.ProcessCanceledException; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.editor.CharPosition; +import com.tyron.editor.Editor; + +import javax.tools.Diagnostic; +import javax.tools.JavaFileObject; +import com.sun.source.tree.BlockTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.SourcePositions; +import com.sun.source.util.TreePath; +import com.sun.source.util.Trees; +import com.sun.tools.javac.api.ClientCodeWrapper; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.JCDiagnostic; + +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.ref.WeakReference; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.stream.Collectors; + +import io.github.rosemoe.sora.lang.styling.MappedSpans; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora.lang.styling.TextStyle; +import io.github.rosemoe.sora.langs.textmate.theme.TextMateColorScheme; +import io.github.rosemoe.sora.textmate.core.grammar.StackElement; +import io.github.rosemoe.sora.textmate.core.theme.FontStyle; +import io.github.rosemoe.sora.textmate.core.theme.IRawTheme; +import io.github.rosemoe.sora.textmate.core.theme.ThemeTrieElementRule; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; +import kotlin.Unit; +import kotlin.jvm.functions.Function0; + +public class JavaAnalyzer extends SemanticAnalyzeManager { + + private static final String GRAMMAR_NAME = "java.tmLanguage.json"; + private static final String LANGUAGE_PATH = "textmate/java/syntaxes/java.tmLanguage.json"; + private static final String CONFIG_PATH = "textmate/java/language-configuration.json"; + + public static JavaAnalyzer create(Editor editor) { + try { + AssetManager assetManager = ApplicationLoader.applicationContext.getAssets(); + + try (InputStreamReader config = new InputStreamReader(assetManager.open(CONFIG_PATH))) { + return new JavaAnalyzer(editor, GRAMMAR_NAME, assetManager.open(LANGUAGE_PATH), + config, ((TextMateColorScheme) ((CodeEditorView) editor) + .getColorScheme()).getRawTheme()); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static final Debouncer sDebouncer = new Debouncer(Duration.ofMillis(700), Executors + .newScheduledThreadPool(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable runnable) { + ThreadGroup threadGroup = Looper.getMainLooper().getThread().getThreadGroup(); + return new Thread(threadGroup, runnable, TAG); + } + })); + private static final String TAG = JavaAnalyzer.class.getSimpleName(); + + private final WeakReference mEditorReference; + private final SharedPreferences mPreferences; + + public JavaAnalyzer(Editor editor, + String grammarName, + InputStream grammarIns, + Reader languageConfiguration, + IRawTheme theme) throws Exception { + super(editor, grammarName, grammarIns, languageConfiguration, theme); + + mEditorReference = new WeakReference<>(editor); + mPreferences = ApplicationLoader.getDefaultPreferences(); + } + + @Override + public List analyzeSpansAsync(CharSequence contents) { + Editor editor = mEditorReference.get(); + JavaCompilerService compiler = getCompiler(editor); + if (compiler == null) { + return null; + } + + File currentFile = editor.getCurrentFile(); + SourceFileObject object = new SourceFileObject(currentFile.toPath(), contents.toString(), Instant.now()); + CompilerContainer container = compiler.compile(Collections.singletonList(object)); + + return container.get(task -> { + JavaSemanticHighlighter highlighter = new JavaSemanticHighlighter(task.task); + CompilationUnitTree root = task.root(currentFile); + highlighter.scan(root, true); + return highlighter.getTokens(); + }); + } + + @Override + public void analyzeInBackground(CharSequence contents) { + sDebouncer.cancel(); + sDebouncer.schedule(cancel -> { + doAnalyzeInBackground(cancel, contents); + return Unit.INSTANCE; + }); + } + + private JavaCompilerService getCompiler(Editor editor) { + Project project = ProjectManager.getInstance().getCurrentProject(); + if (project == null) { + return null; + } + if (project.isCompiling() || project.isIndexing()) { + return null; + } + Module module = project.getModule(editor.getCurrentFile()); + if (module instanceof JavaModule) { + JavaCompilerProvider provider = + CompilerService.getInstance().getIndex(JavaCompilerProvider.KEY); + if (provider != null) { + return provider.getCompiler(project, (JavaModule) module); + } + } + return null; + } + + private void doAnalyzeInBackground(Function0 cancel, CharSequence contents) { + Log.d(TAG, "doAnalyzeInBackground: called"); + Editor editor = mEditorReference.get(); + if (editor == null) { + return; + } + if (cancel.invoke()) { + return; + } + + // do not compile the file if it not yet closed as it will cause issues when + // compiling multiple files at the same time + if (mPreferences.getBoolean(SharedPreferenceKeys.JAVA_ERROR_HIGHLIGHTING, true)) { + JavaCompilerService service = getCompiler(editor); + if (service != null) { + File currentFile = editor.getCurrentFile(); + if (currentFile == null) { + return; + } + Module module = + ProjectManager.getInstance().getCurrentProject().getModule(currentFile); + if (!module.getFileManager().isOpened(currentFile)) { + return; + } + try { + if (service.getCachedContainer().isWriting()) { + return; + } + ProgressManager.getInstance().runLater(() -> editor.setAnalyzing(true)); + SourceFileObject sourceFileObject = + new SourceFileObject(currentFile.toPath(), contents.toString(), + Instant.now()); + CompilerContainer container = + service.compile(Collections.singletonList(sourceFileObject)); + container.run(task -> { + if (!cancel.invoke()) { + List collect = + task.diagnostics.stream().map(d -> modifyDiagnostic(task, d)) + .peek(it -> ProgressManager.checkCanceled()) + .filter(d -> currentFile.equals(d.getSource())) + .collect(Collectors.toList()); + editor.setDiagnostics(collect); + + ProgressManager.getInstance() + .runLater(() -> editor.setAnalyzing(false), 300); + } + }); + } catch (Throwable e) { + if (e instanceof ProcessCanceledException) { + throw e; + } + if (BuildConfig.DEBUG) { + Log.e(TAG, "Unable to get diagnostics", e); + } + service.destroy(); + ProgressManager.getInstance().runLater(() -> editor.setAnalyzing(false)); + } + } + } + } + + private DiagnosticWrapper modifyDiagnostic(CompileTask task, + Diagnostic diagnostic) { + DiagnosticWrapper wrapped = new DiagnosticWrapper(diagnostic); + + if (diagnostic instanceof ClientCodeWrapper.DiagnosticSourceUnwrapper) { + Trees trees = Trees.instance(task.task); + SourcePositions positions = trees.getSourcePositions(); + + JCDiagnostic jcDiagnostic = + ((ClientCodeWrapper.DiagnosticSourceUnwrapper) diagnostic).d; + JCDiagnostic.DiagnosticPosition diagnosticPosition = + jcDiagnostic.getDiagnosticPosition(); + JCTree tree = diagnosticPosition.getTree(); + + if (tree != null) { + TreePath treePath = trees.getPath(task.root(), tree); + if (treePath == null) { + return wrapped; + } + String code = jcDiagnostic.getCode(); + + long start = diagnostic.getStartPosition(); + long end = diagnostic.getEndPosition(); + switch (code) { + case ErrorCodes.MISSING_RETURN_STATEMENT: + TreePath block = TreeUtil.findParentOfType(treePath, BlockTree.class); + if (block != null) { + // show error span only at the end parenthesis + end = positions.getEndPosition(task.root(), block.getLeaf()) + 1; + start = end - 2; + } + break; + case ErrorCodes.DEPRECATED: + if (treePath.getLeaf().getKind() == Tree.Kind.METHOD) { + MethodTree methodTree = (MethodTree) treePath.getLeaf(); + if (methodTree.getBody() != null) { + start = positions.getStartPosition(task.root(), methodTree); + end = positions.getStartPosition(task.root(), methodTree.getBody()); + } + } + break; + } + + wrapped.setStartPosition(start); + wrapped.setEndPosition(end); + } + } + return wrapped; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/java/JavaAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/language/java/JavaAutoCompleteProvider.java new file mode 100644 index 00000000..f7dc35d7 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/JavaAutoCompleteProvider.java @@ -0,0 +1,64 @@ +package com.tyron.code.language.java; + +import android.content.SharedPreferences; + +import androidx.annotation.Nullable; + +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.JavaModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.language.AbstractAutoCompleteProvider; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.completion.main.CompletionEngine; +import com.tyron.completion.model.CompletionList; +import com.tyron.editor.Editor; + +import java.util.Optional; + +public class JavaAutoCompleteProvider extends AbstractAutoCompleteProvider { + + private final Editor mEditor; + private final SharedPreferences mPreferences; + + public JavaAutoCompleteProvider(Editor editor) { + mEditor = editor; + mPreferences = ApplicationLoader.getDefaultPreferences(); + } + + + @Nullable + @Override + public CompletionList getCompletionList(String prefix, int line, int column) { + if (!mPreferences.getBoolean(SharedPreferenceKeys.JAVA_CODE_COMPLETION, true)) { + return null; + } + + Project project = ProjectManager.getInstance().getCurrentProject(); + + if (project == null) { + return null; + } + + Module currentModule = project.getModule(mEditor.getCurrentFile()); + + if (currentModule instanceof JavaModule) { + Optional content = currentModule.getFileManager() + .getFileContent(mEditor.getCurrentFile()); + if (content.isPresent()) { + return CompletionEngine.getInstance() + .complete(project, + currentModule, + mEditor, + mEditor.getCurrentFile(), + content.get().toString(), + prefix, + line, + column, + mEditor.getCaret().getStart()); + } + } + return null; + } +} diff --git a/app/src/main/java/com/tyron/code/language/java/JavaLanguage.java b/app/src/main/java/com/tyron/code/language/java/JavaLanguage.java new file mode 100644 index 00000000..e13f308d --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/JavaLanguage.java @@ -0,0 +1,248 @@ +package com.tyron.code.language.java; + +import android.os.Bundle; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.google.common.collect.Range; +import com.google.googlejavaformat.java.Formatter; +import com.google.googlejavaformat.java.FormatterException; +import com.google.googlejavaformat.java.JavaFormatterOptions; +import com.tyron.code.language.CompletionItemWrapper; +import com.tyron.code.language.EditorFormatter; +import com.tyron.code.analyzer.BaseTextmateAnalyzer; +import com.tyron.completion.model.CompletionItem; +import com.tyron.completion.model.CompletionList; +import com.tyron.editor.Editor; + +import java.util.ArrayList; +import java.util.Collection; + +import io.github.rosemoe.editor.langs.java.JavaTextTokenizer; +import io.github.rosemoe.editor.langs.java.Tokens; +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; +import io.github.rosemoe.sora.lang.completion.CompletionHelper; +import io.github.rosemoe.sora.lang.completion.CompletionPublisher; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.text.TextUtils; +import io.github.rosemoe.sora.util.MyCharacter; +import io.github.rosemoe.sora.widget.SymbolPairMatch; + +public class JavaLanguage implements Language, EditorFormatter { + + private final Editor mEditor; + + private final BaseTextmateAnalyzer mAnalyzer; + + public JavaLanguage(Editor editor) { + mEditor = editor; + mAnalyzer = JavaAnalyzer.create(editor); + } + + public boolean isAutoCompleteChar(char p1) { + return p1 == '.' || MyCharacter.isJavaIdentifierPart(p1); + } + + public int getIndentAdvance(String p1) { + JavaTextTokenizer tokenizer = new JavaTextTokenizer(p1); + Tokens token; + int advance = 0; + while ((token = tokenizer.directNextToken()) != Tokens.EOF) { + switch (token) { + case LBRACE: + advance++; + break; + } + } + return (advance * getTabWidth()); + } + + public int getFormatIndent(String line) { + JavaTextTokenizer tokenizer = new JavaTextTokenizer(line); + Tokens token; + int advance = 0; + while ((token = tokenizer.directNextToken()) != Tokens.EOF) { + switch (token) { + case LBRACE: + advance++; + break; + case RBRACE: + advance--; + } + } + return (advance * getTabWidth()); + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return mAnalyzer; + } + + @Override + public int getInterruptionLevel() { + return INTERRUPTION_LEVEL_SLIGHT; + } + + @Override + public void requireAutoComplete(@NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) throws CompletionCancelledException { + char c = content.charAt(position.getIndex() - 1); + if (!isAutoCompleteChar(c)) { + return; + } + String prefix = CompletionHelper.computePrefix(content, position, this::isAutoCompleteChar); + JavaAutoCompleteProvider provider = new JavaAutoCompleteProvider(mEditor); + CompletionList list = provider.getCompletionList(prefix, position.getLine(), position.getColumn()); + if (list == null) { + return; + } + for (CompletionItem item : list.getItems()) { + CompletionItemWrapper wrapper = new CompletionItemWrapper(item); + publisher.addItem(wrapper); + } + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + String text = content.getLine(line).substring(0, column); + return getIndentAdvance(text); + } + + @Override + public boolean useTab() { + return true; + } + + public int getTabWidth() { + return 4; + } + + + @Override + public CharSequence format(CharSequence p1) { + return format(p1, 0, p1.length()); + } + + @NonNull + @Override + public CharSequence format(@NonNull CharSequence contents, int start, int end) { + return com.tyron.eclipse.formatter.Formatter.format(contents.toString(), + start, + end - start); + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return new SymbolPairMatch.DefaultSymbolPairs(); + } + + private final NewlineHandler[] newLineHandlers = new NewlineHandler[]{new BraceHandler(), + new TwoIndentHandler(), new JavaDocStartHandler(), new JavaDocHandler()}; + + @Override + public NewlineHandler[] getNewlineHandlers() { + return newLineHandlers; + } + + @Override + public void destroy() { + + } + + class TwoIndentHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + Log.d("BeforeText", beforeText); + if (beforeText.replace("\r", "").trim().startsWith(".")) { + return false; + } + return beforeText.endsWith(")") && !afterText.startsWith(";"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText) + (4 * 2); + String text; + StringBuilder sb = new StringBuilder().append('\n').append(text = + TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); + int shiftLeft = 0; + return new NewlineHandleResult(sb, shiftLeft); + } + + + } + + class BraceHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.endsWith("{") && afterText.startsWith("}"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceBefore = getIndentAdvance(beforeText); + int advanceAfter = getIndentAdvance(afterText); + String text; + StringBuilder sb = + new StringBuilder("\n").append(TextUtils.createIndent(count + advanceBefore, + tabSize, useTab())).append('\n').append(text = + TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); + int shiftLeft = text.length() + 1; + return new NewlineHandleResult(sb, shiftLeft); + } + } + + class JavaDocStartHandler implements NewlineHandler { + + private boolean shouldCreateEnd = true; + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.trim().startsWith("/**"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText); + String text = ""; + StringBuilder sb = + new StringBuilder().append("\n").append(TextUtils.createIndent(count + advanceAfter, tabSize, useTab())).append(" * "); + if (shouldCreateEnd) { + sb.append("\n").append(text = TextUtils.createIndent(count + advanceAfter, + tabSize, useTab())).append(" */"); + } + return new NewlineHandleResult(sb, text.length() + 4); + } + } + + class JavaDocHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.trim().startsWith("*") && !beforeText.trim().startsWith("*/"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceAfter = getIndentAdvance(afterText); + StringBuilder sb = + new StringBuilder().append("\n").append(TextUtils.createIndent(count + advanceAfter, tabSize, useTab())).append("* "); + return new NewlineHandleResult(sb, 0); + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/java/JavaSemanticHighlighter.java b/app/src/main/java/com/tyron/code/language/java/JavaSemanticHighlighter.java new file mode 100644 index 00000000..6618128b --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/JavaSemanticHighlighter.java @@ -0,0 +1,342 @@ +package com.tyron.code.language.java; + +import androidx.annotation.NonNull; + +import com.google.common.collect.Ordering; +import com.tyron.code.analyzer.semantic.SemanticToken; +import com.tyron.code.analyzer.semantic.TokenType; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.ErroneousTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.JavacTask; +import com.sun.source.util.SourcePositions; +import com.sun.source.util.TreePathScanner; +import com.sun.source.util.Trees; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeInfo; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +public class JavaSemanticHighlighter extends TreePathScanner { + + private static final Ordering INCREASING = + Ordering.from(Comparator.comparingInt(SemanticToken::getOffset)); + + + private JCTree.JCCompilationUnit cu; + private SourcePositions pos; + private final Trees trees; + private final Elements elements; + private List tokens; + + public JavaSemanticHighlighter(JavacTask task) { + this.trees = Trees.instance(task); + this.pos = trees.getSourcePositions(); + this.elements = task.getElements(); + this.tokens = new ArrayList<>(); + } + + + private enum TokenModifier { + + ABSTRACT("abstract"), + STATIC("static"), + FINAL("readOnly"), + DECLARATION("declaration"), + + PUBLIC("public"), + PRIVATE("private"), + PROTECTED("protected"), + NATIVE("native"), + GENERIC("generic"), + TYPE_ARGUMENT("typeArgument"), + IMPORT_DECLARATION("importDeclaration"), + CONSTRUCTOR("constructor"); + + private final String genericName; + + /** + * The bitmask for this semantic token modifier. + * Use bitwise OR to combine with other token modifiers. + */ + public final int bitmask = 1 << ordinal(); + + /** + * The inverse bitmask for this semantic token modifier. + * Use bitwise AND to remove from other token modifiers. + */ + public final int inverseBitmask = ~bitmask; + + TokenModifier(String genericName) { + this.genericName = genericName; + } + + @NonNull + @Override + public String toString() { + return genericName; + } + + /** + * Returns the bitwise OR of all the semantic token modifiers that apply + * based on the binding's {@link Modifier}s and wheter or not it is deprecated. + * + * @param binding A binding. + * @return The bitwise OR of the applicable modifiers for the binding. + */ + public static int checkJavaModifiers(Element binding) { + if (binding == null) { + return 0; + } + + int modifiers = 0; + Set bindingModifiers = binding.getModifiers(); + if (bindingModifiers.contains(Modifier.PUBLIC)) { + modifiers |= PUBLIC.bitmask; + } + if (bindingModifiers.contains(Modifier.PRIVATE)) { + modifiers |= PRIVATE.bitmask; + } + if (bindingModifiers.contains(Modifier.PROTECTED)) { + modifiers |= PROTECTED.bitmask; + } + if (bindingModifiers.contains(Modifier.ABSTRACT)) { + modifiers |= ABSTRACT.bitmask; + } + if (bindingModifiers.contains(Modifier.STATIC)) { + modifiers |= STATIC.bitmask; + } + if (bindingModifiers.contains(Modifier.FINAL)) { + modifiers |= FINAL.bitmask; + } + if (bindingModifiers.contains(Modifier.NATIVE)) { + modifiers |= NATIVE.bitmask; + } +// if (binding.isDeprecated()) { +// modifiers |= DEPRECATED.bitmask; +// } + return modifiers; + } + } + + + + public List getTokens() { + return INCREASING.immutableSortedCopy(tokens); + } + + private void addToken(int offset, int length, TokenType tokenType, int modifiers) { + tokens.add(new SemanticToken(offset, length, tokenType, modifiers)); + } + + private void addToken(JCTree node, TokenType tokenType, int modifiers) { + addToken(node.getStartPosition(), node.getStartPosition(), tokenType, modifiers); + } + + + @Override + public Void visitCompilationUnit(CompilationUnitTree t, Boolean b) { + cu = (JCTree.JCCompilationUnit) t; + return super.visitCompilationUnit(t, b); + } + + @Override + public Void visitIdentifier(IdentifierTree t, Boolean b) { + JCTree.JCIdent identifier = ((JCTree.JCIdent) t); + + Tree parent = getCurrentPath().getParentPath().getLeaf(); + Tree.Kind parentKind = parent.getKind(); + + switch (parentKind) { + case ANNOTATION: + addAnnotation(identifier); + break; + case VARIABLE: + addVariable(identifier); + break; + case MEMBER_SELECT: + addMemberSelect(identifier); + break; + default: + addIdentifier(identifier); + } + return super.visitIdentifier(t, b); + } + + private void addMemberSelect(JCTree.JCIdent identifier) { + Element element = trees.getElement(getCurrentPath()); + TokenType applicableType = JavaTokenTypes.getApplicableType(element); + if (applicableType == null) { + return; + } + + int start = (int) pos.getStartPosition(cu, identifier); + int end = (int) pos.getEndPosition(cu, identifier); + addToken(start, end - start, applicableType, 0); + } + + private void addVariable(JCTree.JCIdent identifier) { + int start = identifier.getStartPosition(); + int end = start + identifier.getName().length(); + + Element element = trees.getElement(getCurrentPath()); + addToken(start, end - start, JavaTokenTypes.CLASS, 0); + } + private void addIdentifier(JCTree.JCIdent identifier) { + if (identifier == null) { + return; + } + int start = identifier.getStartPosition(); + int end = start + identifier.getName().length(); + + + } + private void addAnnotation(JCTree.JCIdent identifier) { + TokenType tokenType = TokenType.UNKNOWN; + + Element element = trees.getElement(getCurrentPath()); + if (element != null) { + TypeMirror type = element.asType(); + if (type.getKind() != TypeKind.ERROR) { + tokenType = JavaTokenTypes.ANNOTATION; + } + } + + int start = identifier.getStartPosition(); + int end = (int) pos.getEndPosition(cu, identifier); + addToken(start, end - start, tokenType, 0); + } + + @Override + public Void visitClass(ClassTree t, Boolean b) { + Element element = trees.getElement(getCurrentPath()); + if (element == null) { + return super.visitClass(t, b); + } + + int start = (int) pos.getStartPosition(cu, t); + + String contents = getContents(); + start = contents.indexOf(element.getSimpleName().toString(), start); + if (start == -1) { + return super.visitClass(t, b); + } + int end = start + element.getSimpleName().length(); + + TokenType applicableType = JavaTokenTypes.getApplicableType(element); + if (applicableType != null) { + addToken(start, end - start, applicableType, TokenModifier.checkJavaModifiers(element)); + } + return super.visitClass(t, b); + } + + @Override + public Void visitMethod(MethodTree methodTree, Boolean aBoolean) { + Element element = trees.getElement(getCurrentPath()); + if (element == null) { + return super.visitMethod(methodTree, aBoolean); + } + + boolean isConstructor = TreeInfo.isConstructor((JCTree) methodTree); + String name; + if (isConstructor) { + name = element.getEnclosingElement().getSimpleName().toString(); + } else { + name = element.getSimpleName().toString(); + } + + String contents = getContents(); + int start = (int) pos.getStartPosition(cu, methodTree); + start = contents.indexOf(name, start); + int end = start + name.length(); + + long realEnd = pos.getEndPosition(cu, methodTree); + if (realEnd != -1) { + TokenType type; + if (isConstructor) { + type = JavaTokenTypes.CONSTRUCTOR; + } else { + type = JavaTokenTypes.METHOD_DECLARATION; + } + addToken(start, end - start, type, 0); + } + return super.visitMethod(methodTree, aBoolean); + } + + @Override + public Void visitVariable(VariableTree t, Boolean b) { + JCTree tree = ((JCTree.JCVariableDecl) t); + int start = tree.getPreferredPosition(); + int end = start + t.getName().length(); + Element element = trees.getElement(getCurrentPath()); + TokenType applicableType = JavaTokenTypes.getApplicableType(element); + if (applicableType != null) { + if (element.getModifiers().contains(Modifier.FINAL)) { + addToken(start, end - start, JavaTokenTypes.CONSTANT, TokenModifier.checkJavaModifiers(element)); + } else { + addToken(start, end - start, applicableType, TokenModifier.checkJavaModifiers(element)); + } + } + return super.visitVariable(t, b); + } + + private void addSuper() { + + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree t, Boolean b) { + JCTree.JCMethodInvocation method = ((JCTree.JCMethodInvocation) t); + Element element = trees.getElement(getCurrentPath()); + int end = method.getPreferredPosition(); + + int realEndPosition = (int) pos.getEndPosition(cu, t); + if (end == -1 || element == null || realEndPosition == -1) { + return super.visitMethodInvocation(t, b); + } + int start = end - element.getSimpleName().length(); + TokenType type = JavaTokenTypes.METHOD_CALL; + if (element.getKind() == ElementKind.CONSTRUCTOR) { + start = method.getStartPosition(); + end = start + "super".length(); + type = JavaTokenTypes.CONSTRUCTOR; + } + addToken(start, end - start, type, TokenModifier.checkJavaModifiers(element)); + return super.visitMethodInvocation(t, b); + } + + @Override + public Void visitErroneous(ErroneousTree t, Boolean b) { + List errorTrees = t.getErrorTrees(); + if (errorTrees != null) { + for (Tree errorTree : errorTrees) { + scan(errorTree, b); + } + } + return null; + } + + private String getContents() { + try { + return String.valueOf(cu.getSourceFile().getCharContent(false)); + } catch (IOException e) { + return ""; + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/java/JavaTokenTypes.java b/app/src/main/java/com/tyron/code/language/java/JavaTokenTypes.java new file mode 100644 index 00000000..20daea50 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/java/JavaTokenTypes.java @@ -0,0 +1,58 @@ +package com.tyron.code.language.java; + +import com.tyron.code.analyzer.semantic.TokenType; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.VariableElement; +import com.sun.tools.javac.code.Symbol; + +public class JavaTokenTypes { + + public static final TokenType FIELD = TokenType.create("variable.other.object.property.java"); + public static final TokenType CONSTANT = TokenType.create("variable.other.constant"); + public static final TokenType PARAMETER = TokenType.create("variable.parameter"); + public static final TokenType CLASS = TokenType.create("entity.name.type.class"); + public static final TokenType METHOD_CALL = TokenType.create("meta.method-call"); + public static final TokenType METHOD_DECLARATION = TokenType.create("entity.name.function.member"); + public static final TokenType VARIABLE = TokenType.create("entity.name.variable"); + public static final TokenType CONSTRUCTOR = TokenType.create("class.instance.constructor"); + public static final TokenType ANNOTATION = TokenType.create("storage.type.annotation"); + + public static TokenType getApplicableType(Element element) { + if (element == null) { + return null; + } + + switch (element.getKind()) { + case LOCAL_VARIABLE: + Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) element; + if (varSymbol.getModifiers().contains(Modifier.FINAL)) { + return CONSTANT; + } + return VARIABLE; + case METHOD: + Symbol.MethodSymbol methodSymbol = ((Symbol.MethodSymbol) element); + if (methodSymbol.isConstructor()) { + return getApplicableType(methodSymbol.getEnclosingElement()); + } + return METHOD_DECLARATION; + case FIELD: + VariableElement variableElement = ((VariableElement) element); + if (variableElement.getModifiers().contains(Modifier.FINAL)) { + return CONSTANT; + } + return FIELD; + case CLASS: + return CLASS; + case CONSTRUCTOR: + return CONSTRUCTOR; + case PARAMETER: + return PARAMETER; + case ANNOTATION_TYPE: + return ANNOTATION; + default: + return null; + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/json/JSON.g4 b/app/src/main/java/com/tyron/code/language/json/JSON.g4 new file mode 100644 index 00000000..2f11b8b3 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSON.g4 @@ -0,0 +1,108 @@ +/** Taken from "The Definitive ANTLR 4 Reference" by Terence Parr */ + +// Derived from http://json.org +grammar JSON; + +json + : value + ; + +obj + : LBRACE pair (',' pair)* RBRACE + | LBRACE RBRACE + ; + +pair + : STRING COLON value + ; + +arr + : LBRACKET value (COMMA value)* RBRACKET + | LBRACKET RBRACKET + ; + +value + : STRING + | NUMBER + | obj + | arr + | TRUE + | FALSE + | NULL + ; + +TRUE + : 'true' + ; +FALSE + : 'false' + ; +NULL + : 'null' + ; + + +COLON + : ':' + ; + +STRING + : '"' (ESC | SAFECODEPOINT)* '"' + ; + +LBRACE + : '{' + ; + +RBRACE + : '}' + ; + + +LBRACKET + : '[' + ; + +RBRACKET + : ']' + ; + +COMMA + : ',' + ; + + +fragment ESC + : '\\' (["\\/bfnrt] | UNICODE) + ; +fragment UNICODE + : 'u' HEX HEX HEX HEX + ; +fragment HEX + : [0-9a-fA-F] + ; +fragment SAFECODEPOINT + : ~ ["\\\u0000-\u001F] + ; + + +NUMBER + : '-'? INT ('.' [0-9] +)? EXP? + ; + + +fragment INT + : '0' | [1-9] [0-9]* + ; + +// no leading zeros + +fragment EXP + : [Ee] [+\-]? INT + ; + +// \- since - means "range" inside [...] + +WS + : [ \t\n\r] + -> skip + ; \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JSON.interp b/app/src/main/java/com/tyron/code/language/json/JSON.interp new file mode 100644 index 00000000..2b38429f --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSON.interp @@ -0,0 +1,40 @@ +token literal names: +null +'true' +'false' +'null' +':' +null +'{' +'}' +'[' +']' +',' +null +null + +token symbolic names: +null +TRUE +FALSE +NULL +COLON +STRING +LBRACE +RBRACE +LBRACKET +RBRACKET +COMMA +NUMBER +WS + +rule names: +json +obj +pair +arr +value + + +atn: +[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 14, 58, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 19, 10, 3, 12, 3, 14, 3, 22, 11, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 28, 10, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 7, 5, 38, 10, 5, 12, 5, 14, 5, 41, 11, 5, 3, 5, 3, 5, 3, 5, 3, 5, 5, 5, 47, 10, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 5, 6, 56, 10, 6, 3, 6, 2, 2, 7, 2, 4, 6, 8, 10, 2, 2, 2, 62, 2, 12, 3, 2, 2, 2, 4, 27, 3, 2, 2, 2, 6, 29, 3, 2, 2, 2, 8, 46, 3, 2, 2, 2, 10, 55, 3, 2, 2, 2, 12, 13, 5, 10, 6, 2, 13, 3, 3, 2, 2, 2, 14, 15, 7, 8, 2, 2, 15, 20, 5, 6, 4, 2, 16, 17, 7, 12, 2, 2, 17, 19, 5, 6, 4, 2, 18, 16, 3, 2, 2, 2, 19, 22, 3, 2, 2, 2, 20, 18, 3, 2, 2, 2, 20, 21, 3, 2, 2, 2, 21, 23, 3, 2, 2, 2, 22, 20, 3, 2, 2, 2, 23, 24, 7, 9, 2, 2, 24, 28, 3, 2, 2, 2, 25, 26, 7, 8, 2, 2, 26, 28, 7, 9, 2, 2, 27, 14, 3, 2, 2, 2, 27, 25, 3, 2, 2, 2, 28, 5, 3, 2, 2, 2, 29, 30, 7, 7, 2, 2, 30, 31, 7, 6, 2, 2, 31, 32, 5, 10, 6, 2, 32, 7, 3, 2, 2, 2, 33, 34, 7, 10, 2, 2, 34, 39, 5, 10, 6, 2, 35, 36, 7, 12, 2, 2, 36, 38, 5, 10, 6, 2, 37, 35, 3, 2, 2, 2, 38, 41, 3, 2, 2, 2, 39, 37, 3, 2, 2, 2, 39, 40, 3, 2, 2, 2, 40, 42, 3, 2, 2, 2, 41, 39, 3, 2, 2, 2, 42, 43, 7, 11, 2, 2, 43, 47, 3, 2, 2, 2, 44, 45, 7, 10, 2, 2, 45, 47, 7, 11, 2, 2, 46, 33, 3, 2, 2, 2, 46, 44, 3, 2, 2, 2, 47, 9, 3, 2, 2, 2, 48, 56, 7, 7, 2, 2, 49, 56, 7, 13, 2, 2, 50, 56, 5, 4, 3, 2, 51, 56, 5, 8, 5, 2, 52, 56, 7, 3, 2, 2, 53, 56, 7, 4, 2, 2, 54, 56, 7, 5, 2, 2, 55, 48, 3, 2, 2, 2, 55, 49, 3, 2, 2, 2, 55, 50, 3, 2, 2, 2, 55, 51, 3, 2, 2, 2, 55, 52, 3, 2, 2, 2, 55, 53, 3, 2, 2, 2, 55, 54, 3, 2, 2, 2, 56, 11, 3, 2, 2, 2, 7, 20, 27, 39, 46, 55] \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JSON.tokens b/app/src/main/java/com/tyron/code/language/json/JSON.tokens new file mode 100644 index 00000000..44a91db0 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSON.tokens @@ -0,0 +1,21 @@ +TRUE=1 +FALSE=2 +NULL=3 +COLON=4 +STRING=5 +LBRACE=6 +RBRACE=7 +LBRACKET=8 +RBRACKET=9 +COMMA=10 +NUMBER=11 +WS=12 +'true'=1 +'false'=2 +'null'=3 +':'=4 +'{'=6 +'}'=7 +'['=8 +']'=9 +','=10 diff --git a/app/src/main/java/com/tyron/code/language/json/JSONBaseListener.java b/app/src/main/java/com/tyron/code/language/json/JSONBaseListener.java new file mode 100644 index 00000000..0f2740e3 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSONBaseListener.java @@ -0,0 +1,99 @@ +// Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 +package com.tyron.code.language.json; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.tree.TerminalNode; + +/** + * This class provides an empty implementation of {@link JSONListener}, + * which can be extended to create a listener which only needs to handle a subset + * of the available methods. + */ +public class JSONBaseListener implements JSONListener { + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterJson(JSONParser.JsonContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitJson(JSONParser.JsonContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterObj(JSONParser.ObjContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitObj(JSONParser.ObjContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterPair(JSONParser.PairContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitPair(JSONParser.PairContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterArr(JSONParser.ArrContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitArr(JSONParser.ArrContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterValue(JSONParser.ValueContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitValue(JSONParser.ValueContext ctx) { } + + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void enterEveryRule(ParserRuleContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void exitEveryRule(ParserRuleContext ctx) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void visitTerminal(TerminalNode node) { } + /** + * {@inheritDoc} + * + *

The default implementation does nothing.

+ */ + @Override public void visitErrorNode(ErrorNode node) { } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JSONBaseVisitor.java b/app/src/main/java/com/tyron/code/language/json/JSONBaseVisitor.java new file mode 100644 index 00000000..38842f07 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSONBaseVisitor.java @@ -0,0 +1,49 @@ +// Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 +package com.tyron.code.language.json; +import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; + +/** + * This class provides an empty implementation of {@link JSONVisitor}, + * which can be extended to create a visitor which only needs to handle a subset + * of the available methods. + * + * @param The return type of the visit operation. Use {@link Void} for + * operations with no return type. + */ +public class JSONBaseVisitor extends AbstractParseTreeVisitor implements JSONVisitor { + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitJson(JSONParser.JsonContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitObj(JSONParser.ObjContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitPair(JSONParser.PairContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitArr(JSONParser.ArrContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitValue(JSONParser.ValueContext ctx) { return visitChildren(ctx); } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JSONLexer.interp b/app/src/main/java/com/tyron/code/language/json/JSONLexer.interp new file mode 100644 index 00000000..d8aa9bf9 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSONLexer.interp @@ -0,0 +1,59 @@ +token literal names: +null +'true' +'false' +'null' +':' +null +'{' +'}' +'[' +']' +',' +null +null + +token symbolic names: +null +TRUE +FALSE +NULL +COLON +STRING +LBRACE +RBRACE +LBRACKET +RBRACKET +COMMA +NUMBER +WS + +rule names: +TRUE +FALSE +NULL +COLON +STRING +LBRACE +RBRACE +LBRACKET +RBRACKET +COMMA +ESC +UNICODE +HEX +SAFECODEPOINT +NUMBER +INT +EXP +WS + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 14, 130, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 7, 6, 61, 10, 6, 12, 6, 14, 6, 64, 11, 6, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 5, 12, 81, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 14, 3, 14, 3, 15, 3, 15, 3, 16, 5, 16, 94, 10, 16, 3, 16, 3, 16, 3, 16, 6, 16, 99, 10, 16, 13, 16, 14, 16, 100, 5, 16, 103, 10, 16, 3, 16, 5, 16, 106, 10, 16, 3, 17, 3, 17, 3, 17, 7, 17, 111, 10, 17, 12, 17, 14, 17, 114, 11, 17, 5, 17, 116, 10, 17, 3, 18, 3, 18, 5, 18, 120, 10, 18, 3, 18, 3, 18, 3, 19, 6, 19, 125, 10, 19, 13, 19, 14, 19, 126, 3, 19, 3, 19, 2, 2, 20, 3, 3, 5, 4, 7, 5, 9, 6, 11, 7, 13, 8, 15, 9, 17, 10, 19, 11, 21, 12, 23, 2, 25, 2, 27, 2, 29, 2, 31, 13, 33, 2, 35, 2, 37, 14, 3, 2, 10, 10, 2, 36, 36, 49, 49, 94, 94, 100, 100, 104, 104, 112, 112, 116, 116, 118, 118, 5, 2, 50, 59, 67, 72, 99, 104, 5, 2, 2, 33, 36, 36, 94, 94, 3, 2, 50, 59, 3, 2, 51, 59, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45, 47, 47, 5, 2, 11, 12, 15, 15, 34, 34, 2, 134, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 31, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 3, 39, 3, 2, 2, 2, 5, 44, 3, 2, 2, 2, 7, 50, 3, 2, 2, 2, 9, 55, 3, 2, 2, 2, 11, 57, 3, 2, 2, 2, 13, 67, 3, 2, 2, 2, 15, 69, 3, 2, 2, 2, 17, 71, 3, 2, 2, 2, 19, 73, 3, 2, 2, 2, 21, 75, 3, 2, 2, 2, 23, 77, 3, 2, 2, 2, 25, 82, 3, 2, 2, 2, 27, 88, 3, 2, 2, 2, 29, 90, 3, 2, 2, 2, 31, 93, 3, 2, 2, 2, 33, 115, 3, 2, 2, 2, 35, 117, 3, 2, 2, 2, 37, 124, 3, 2, 2, 2, 39, 40, 7, 118, 2, 2, 40, 41, 7, 116, 2, 2, 41, 42, 7, 119, 2, 2, 42, 43, 7, 103, 2, 2, 43, 4, 3, 2, 2, 2, 44, 45, 7, 104, 2, 2, 45, 46, 7, 99, 2, 2, 46, 47, 7, 110, 2, 2, 47, 48, 7, 117, 2, 2, 48, 49, 7, 103, 2, 2, 49, 6, 3, 2, 2, 2, 50, 51, 7, 112, 2, 2, 51, 52, 7, 119, 2, 2, 52, 53, 7, 110, 2, 2, 53, 54, 7, 110, 2, 2, 54, 8, 3, 2, 2, 2, 55, 56, 7, 60, 2, 2, 56, 10, 3, 2, 2, 2, 57, 62, 7, 36, 2, 2, 58, 61, 5, 23, 12, 2, 59, 61, 5, 29, 15, 2, 60, 58, 3, 2, 2, 2, 60, 59, 3, 2, 2, 2, 61, 64, 3, 2, 2, 2, 62, 60, 3, 2, 2, 2, 62, 63, 3, 2, 2, 2, 63, 65, 3, 2, 2, 2, 64, 62, 3, 2, 2, 2, 65, 66, 7, 36, 2, 2, 66, 12, 3, 2, 2, 2, 67, 68, 7, 125, 2, 2, 68, 14, 3, 2, 2, 2, 69, 70, 7, 127, 2, 2, 70, 16, 3, 2, 2, 2, 71, 72, 7, 93, 2, 2, 72, 18, 3, 2, 2, 2, 73, 74, 7, 95, 2, 2, 74, 20, 3, 2, 2, 2, 75, 76, 7, 46, 2, 2, 76, 22, 3, 2, 2, 2, 77, 80, 7, 94, 2, 2, 78, 81, 9, 2, 2, 2, 79, 81, 5, 25, 13, 2, 80, 78, 3, 2, 2, 2, 80, 79, 3, 2, 2, 2, 81, 24, 3, 2, 2, 2, 82, 83, 7, 119, 2, 2, 83, 84, 5, 27, 14, 2, 84, 85, 5, 27, 14, 2, 85, 86, 5, 27, 14, 2, 86, 87, 5, 27, 14, 2, 87, 26, 3, 2, 2, 2, 88, 89, 9, 3, 2, 2, 89, 28, 3, 2, 2, 2, 90, 91, 10, 4, 2, 2, 91, 30, 3, 2, 2, 2, 92, 94, 7, 47, 2, 2, 93, 92, 3, 2, 2, 2, 93, 94, 3, 2, 2, 2, 94, 95, 3, 2, 2, 2, 95, 102, 5, 33, 17, 2, 96, 98, 7, 48, 2, 2, 97, 99, 9, 5, 2, 2, 98, 97, 3, 2, 2, 2, 99, 100, 3, 2, 2, 2, 100, 98, 3, 2, 2, 2, 100, 101, 3, 2, 2, 2, 101, 103, 3, 2, 2, 2, 102, 96, 3, 2, 2, 2, 102, 103, 3, 2, 2, 2, 103, 105, 3, 2, 2, 2, 104, 106, 5, 35, 18, 2, 105, 104, 3, 2, 2, 2, 105, 106, 3, 2, 2, 2, 106, 32, 3, 2, 2, 2, 107, 116, 7, 50, 2, 2, 108, 112, 9, 6, 2, 2, 109, 111, 9, 5, 2, 2, 110, 109, 3, 2, 2, 2, 111, 114, 3, 2, 2, 2, 112, 110, 3, 2, 2, 2, 112, 113, 3, 2, 2, 2, 113, 116, 3, 2, 2, 2, 114, 112, 3, 2, 2, 2, 115, 107, 3, 2, 2, 2, 115, 108, 3, 2, 2, 2, 116, 34, 3, 2, 2, 2, 117, 119, 9, 7, 2, 2, 118, 120, 9, 8, 2, 2, 119, 118, 3, 2, 2, 2, 119, 120, 3, 2, 2, 2, 120, 121, 3, 2, 2, 2, 121, 122, 5, 33, 17, 2, 122, 36, 3, 2, 2, 2, 123, 125, 9, 9, 2, 2, 124, 123, 3, 2, 2, 2, 125, 126, 3, 2, 2, 2, 126, 124, 3, 2, 2, 2, 126, 127, 3, 2, 2, 2, 127, 128, 3, 2, 2, 2, 128, 129, 8, 19, 2, 2, 129, 38, 3, 2, 2, 2, 14, 2, 60, 62, 80, 93, 100, 102, 105, 112, 115, 119, 126, 3, 8, 2, 2] \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JSONLexer.java b/app/src/main/java/com/tyron/code/language/json/JSONLexer.java new file mode 100644 index 00000000..1bfa9866 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSONLexer.java @@ -0,0 +1,150 @@ +// Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 +package com.tyron.code.language.json; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.dfa.DFA; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) +public class JSONLexer extends Lexer { + static { RuntimeMetaData.checkVersion("4.9.2", RuntimeMetaData.VERSION); } + + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + public static final int + TRUE=1, FALSE=2, NULL=3, COLON=4, STRING=5, LBRACE=6, RBRACE=7, LBRACKET=8, + RBRACKET=9, COMMA=10, NUMBER=11, WS=12; + public static String[] channelNames = { + "DEFAULT_TOKEN_CHANNEL", "HIDDEN" + }; + + public static String[] modeNames = { + "DEFAULT_MODE" + }; + + private static String[] makeRuleNames() { + return new String[] { + "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACE", "RBRACE", "LBRACKET", + "RBRACKET", "COMMA", "ESC", "UNICODE", "HEX", "SAFECODEPOINT", "NUMBER", + "INT", "EXP", "WS" + }; + } + public static final String[] ruleNames = makeRuleNames(); + + private static String[] makeLiteralNames() { + return new String[] { + null, "'true'", "'false'", "'null'", "':'", null, "'{'", "'}'", "'['", + "']'", "','" + }; + } + private static final String[] _LITERAL_NAMES = makeLiteralNames(); + private static String[] makeSymbolicNames() { + return new String[] { + null, "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACE", "RBRACE", + "LBRACKET", "RBRACKET", "COMMA", "NUMBER", "WS" + }; + } + private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + static { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) { + tokenNames[i] = ""; + } + } + } + + @Override + @Deprecated + public String[] getTokenNames() { + return tokenNames; + } + + @Override + + public Vocabulary getVocabulary() { + return VOCABULARY; + } + + + public JSONLexer(CharStream input) { + super(input); + _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + + @Override + public String getGrammarFileName() { return "JSON.g4"; } + + @Override + public String[] getRuleNames() { return ruleNames; } + + @Override + public String getSerializedATN() { return _serializedATN; } + + @Override + public String[] getChannelNames() { return channelNames; } + + @Override + public String[] getModeNames() { return modeNames; } + + @Override + public ATN getATN() { return _ATN; } + + public static final String _serializedATN = + "\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2\16\u0082\b\1\4\2"+ + "\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4"+ + "\13\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22"+ + "\t\22\4\23\t\23\3\2\3\2\3\2\3\2\3\2\3\3\3\3\3\3\3\3\3\3\3\3\3\4\3\4\3"+ + "\4\3\4\3\4\3\5\3\5\3\6\3\6\3\6\7\6=\n\6\f\6\16\6@\13\6\3\6\3\6\3\7\3\7"+ + "\3\b\3\b\3\t\3\t\3\n\3\n\3\13\3\13\3\f\3\f\3\f\5\fQ\n\f\3\r\3\r\3\r\3"+ + "\r\3\r\3\r\3\16\3\16\3\17\3\17\3\20\5\20^\n\20\3\20\3\20\3\20\6\20c\n"+ + "\20\r\20\16\20d\5\20g\n\20\3\20\5\20j\n\20\3\21\3\21\3\21\7\21o\n\21\f"+ + "\21\16\21r\13\21\5\21t\n\21\3\22\3\22\5\22x\n\22\3\22\3\22\3\23\6\23}"+ + "\n\23\r\23\16\23~\3\23\3\23\2\2\24\3\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n"+ + "\23\13\25\f\27\2\31\2\33\2\35\2\37\r!\2#\2%\16\3\2\n\n\2$$\61\61^^ddh"+ + "hppttvv\5\2\62;CHch\5\2\2!$$^^\3\2\62;\3\2\63;\4\2GGgg\4\2--//\5\2\13"+ + "\f\17\17\"\"\2\u0086\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2"+ + "\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3"+ + "\2\2\2\2\37\3\2\2\2\2%\3\2\2\2\3\'\3\2\2\2\5,\3\2\2\2\7\62\3\2\2\2\t\67"+ + "\3\2\2\2\139\3\2\2\2\rC\3\2\2\2\17E\3\2\2\2\21G\3\2\2\2\23I\3\2\2\2\25"+ + "K\3\2\2\2\27M\3\2\2\2\31R\3\2\2\2\33X\3\2\2\2\35Z\3\2\2\2\37]\3\2\2\2"+ + "!s\3\2\2\2#u\3\2\2\2%|\3\2\2\2\'(\7v\2\2()\7t\2\2)*\7w\2\2*+\7g\2\2+\4"+ + "\3\2\2\2,-\7h\2\2-.\7c\2\2./\7n\2\2/\60\7u\2\2\60\61\7g\2\2\61\6\3\2\2"+ + "\2\62\63\7p\2\2\63\64\7w\2\2\64\65\7n\2\2\65\66\7n\2\2\66\b\3\2\2\2\67"+ + "8\7<\2\28\n\3\2\2\29>\7$\2\2:=\5\27\f\2;=\5\35\17\2<:\3\2\2\2<;\3\2\2"+ + "\2=@\3\2\2\2><\3\2\2\2>?\3\2\2\2?A\3\2\2\2@>\3\2\2\2AB\7$\2\2B\f\3\2\2"+ + "\2CD\7}\2\2D\16\3\2\2\2EF\7\177\2\2F\20\3\2\2\2GH\7]\2\2H\22\3\2\2\2I"+ + "J\7_\2\2J\24\3\2\2\2KL\7.\2\2L\26\3\2\2\2MP\7^\2\2NQ\t\2\2\2OQ\5\31\r"+ + "\2PN\3\2\2\2PO\3\2\2\2Q\30\3\2\2\2RS\7w\2\2ST\5\33\16\2TU\5\33\16\2UV"+ + "\5\33\16\2VW\5\33\16\2W\32\3\2\2\2XY\t\3\2\2Y\34\3\2\2\2Z[\n\4\2\2[\36"+ + "\3\2\2\2\\^\7/\2\2]\\\3\2\2\2]^\3\2\2\2^_\3\2\2\2_f\5!\21\2`b\7\60\2\2"+ + "ac\t\5\2\2ba\3\2\2\2cd\3\2\2\2db\3\2\2\2de\3\2\2\2eg\3\2\2\2f`\3\2\2\2"+ + "fg\3\2\2\2gi\3\2\2\2hj\5#\22\2ih\3\2\2\2ij\3\2\2\2j \3\2\2\2kt\7\62\2"+ + "\2lp\t\6\2\2mo\t\5\2\2nm\3\2\2\2or\3\2\2\2pn\3\2\2\2pq\3\2\2\2qt\3\2\2"+ + "\2rp\3\2\2\2sk\3\2\2\2sl\3\2\2\2t\"\3\2\2\2uw\t\7\2\2vx\t\b\2\2wv\3\2"+ + "\2\2wx\3\2\2\2xy\3\2\2\2yz\5!\21\2z$\3\2\2\2{}\t\t\2\2|{\3\2\2\2}~\3\2"+ + "\2\2~|\3\2\2\2~\177\3\2\2\2\177\u0080\3\2\2\2\u0080\u0081\b\23\2\2\u0081"+ + "&\3\2\2\2\16\2<>P]dfipsw~\3\b\2\2"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + static { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JSONLexer.tokens b/app/src/main/java/com/tyron/code/language/json/JSONLexer.tokens new file mode 100644 index 00000000..44a91db0 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSONLexer.tokens @@ -0,0 +1,21 @@ +TRUE=1 +FALSE=2 +NULL=3 +COLON=4 +STRING=5 +LBRACE=6 +RBRACE=7 +LBRACKET=8 +RBRACKET=9 +COMMA=10 +NUMBER=11 +WS=12 +'true'=1 +'false'=2 +'null'=3 +':'=4 +'{'=6 +'}'=7 +'['=8 +']'=9 +','=10 diff --git a/app/src/main/java/com/tyron/code/language/json/JSONListener.java b/app/src/main/java/com/tyron/code/language/json/JSONListener.java new file mode 100644 index 00000000..6336e40b --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSONListener.java @@ -0,0 +1,60 @@ +// Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 +package com.tyron.code.language.json; +import org.antlr.v4.runtime.tree.ParseTreeListener; + +/** + * This interface defines a complete listener for a parse tree produced by + * {@link JSONParser}. + */ +public interface JSONListener extends ParseTreeListener { + /** + * Enter a parse tree produced by {@link JSONParser#json}. + * @param ctx the parse tree + */ + void enterJson(JSONParser.JsonContext ctx); + /** + * Exit a parse tree produced by {@link JSONParser#json}. + * @param ctx the parse tree + */ + void exitJson(JSONParser.JsonContext ctx); + /** + * Enter a parse tree produced by {@link JSONParser#obj}. + * @param ctx the parse tree + */ + void enterObj(JSONParser.ObjContext ctx); + /** + * Exit a parse tree produced by {@link JSONParser#obj}. + * @param ctx the parse tree + */ + void exitObj(JSONParser.ObjContext ctx); + /** + * Enter a parse tree produced by {@link JSONParser#pair}. + * @param ctx the parse tree + */ + void enterPair(JSONParser.PairContext ctx); + /** + * Exit a parse tree produced by {@link JSONParser#pair}. + * @param ctx the parse tree + */ + void exitPair(JSONParser.PairContext ctx); + /** + * Enter a parse tree produced by {@link JSONParser#arr}. + * @param ctx the parse tree + */ + void enterArr(JSONParser.ArrContext ctx); + /** + * Exit a parse tree produced by {@link JSONParser#arr}. + * @param ctx the parse tree + */ + void exitArr(JSONParser.ArrContext ctx); + /** + * Enter a parse tree produced by {@link JSONParser#value}. + * @param ctx the parse tree + */ + void enterValue(JSONParser.ValueContext ctx); + /** + * Exit a parse tree produced by {@link JSONParser#value}. + * @param ctx the parse tree + */ + void exitValue(JSONParser.ValueContext ctx); +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JSONParser.java b/app/src/main/java/com/tyron/code/language/json/JSONParser.java new file mode 100644 index 00000000..425748e3 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSONParser.java @@ -0,0 +1,491 @@ +// Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 +package com.tyron.code.language.json; +import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.tree.*; +import java.util.List; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) +public class JSONParser extends Parser { + static { RuntimeMetaData.checkVersion("4.9.2", RuntimeMetaData.VERSION); } + + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + public static final int + TRUE=1, FALSE=2, NULL=3, COLON=4, STRING=5, LBRACE=6, RBRACE=7, LBRACKET=8, + RBRACKET=9, COMMA=10, NUMBER=11, WS=12; + public static final int + RULE_json = 0, RULE_obj = 1, RULE_pair = 2, RULE_arr = 3, RULE_value = 4; + private static String[] makeRuleNames() { + return new String[] { + "json", "obj", "pair", "arr", "value" + }; + } + public static final String[] ruleNames = makeRuleNames(); + + private static String[] makeLiteralNames() { + return new String[] { + null, "'true'", "'false'", "'null'", "':'", null, "'{'", "'}'", "'['", + "']'", "','" + }; + } + private static final String[] _LITERAL_NAMES = makeLiteralNames(); + private static String[] makeSymbolicNames() { + return new String[] { + null, "TRUE", "FALSE", "NULL", "COLON", "STRING", "LBRACE", "RBRACE", + "LBRACKET", "RBRACKET", "COMMA", "NUMBER", "WS" + }; + } + private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + static { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) { + tokenNames[i] = ""; + } + } + } + + @Override + @Deprecated + public String[] getTokenNames() { + return tokenNames; + } + + @Override + + public Vocabulary getVocabulary() { + return VOCABULARY; + } + + @Override + public String getGrammarFileName() { return "JSON.g4"; } + + @Override + public String[] getRuleNames() { return ruleNames; } + + @Override + public String getSerializedATN() { return _serializedATN; } + + @Override + public ATN getATN() { return _ATN; } + + public JSONParser(TokenStream input) { + super(input); + _interp = new ParserATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + + public static class JsonContext extends ParserRuleContext { + public ValueContext value() { + return getRuleContext(ValueContext.class,0); + } + public JsonContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_json; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof JSONListener ) ((JSONListener)listener).enterJson(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof JSONListener ) ((JSONListener)listener).exitJson(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JSONVisitor ) return ((JSONVisitor)visitor).visitJson(this); + else return visitor.visitChildren(this); + } + } + + public final JsonContext json() throws RecognitionException { + JsonContext _localctx = new JsonContext(_ctx, getState()); + enterRule(_localctx, 0, RULE_json); + try { + enterOuterAlt(_localctx, 1); + { + setState(10); + value(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class ObjContext extends ParserRuleContext { + public TerminalNode LBRACE() { return getToken(JSONParser.LBRACE, 0); } + public List pair() { + return getRuleContexts(PairContext.class); + } + public PairContext pair(int i) { + return getRuleContext(PairContext.class,i); + } + public TerminalNode RBRACE() { return getToken(JSONParser.RBRACE, 0); } + public List COMMA() { return getTokens(JSONParser.COMMA); } + public TerminalNode COMMA(int i) { + return getToken(JSONParser.COMMA, i); + } + public ObjContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_obj; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof JSONListener ) ((JSONListener)listener).enterObj(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof JSONListener ) ((JSONListener)listener).exitObj(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JSONVisitor ) return ((JSONVisitor)visitor).visitObj(this); + else return visitor.visitChildren(this); + } + } + + public final ObjContext obj() throws RecognitionException { + ObjContext _localctx = new ObjContext(_ctx, getState()); + enterRule(_localctx, 2, RULE_obj); + int _la; + try { + setState(25); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,1,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(12); + match(LBRACE); + setState(13); + pair(); + setState(18); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==COMMA) { + { + { + setState(14); + match(COMMA); + setState(15); + pair(); + } + } + setState(20); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(21); + match(RBRACE); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(23); + match(LBRACE); + setState(24); + match(RBRACE); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class PairContext extends ParserRuleContext { + public TerminalNode STRING() { return getToken(JSONParser.STRING, 0); } + public TerminalNode COLON() { return getToken(JSONParser.COLON, 0); } + public ValueContext value() { + return getRuleContext(ValueContext.class,0); + } + public PairContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_pair; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof JSONListener ) ((JSONListener)listener).enterPair(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof JSONListener ) ((JSONListener)listener).exitPair(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JSONVisitor ) return ((JSONVisitor)visitor).visitPair(this); + else return visitor.visitChildren(this); + } + } + + public final PairContext pair() throws RecognitionException { + PairContext _localctx = new PairContext(_ctx, getState()); + enterRule(_localctx, 4, RULE_pair); + try { + enterOuterAlt(_localctx, 1); + { + setState(27); + match(STRING); + setState(28); + match(COLON); + setState(29); + value(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class ArrContext extends ParserRuleContext { + public TerminalNode LBRACKET() { return getToken(JSONParser.LBRACKET, 0); } + public List value() { + return getRuleContexts(ValueContext.class); + } + public ValueContext value(int i) { + return getRuleContext(ValueContext.class,i); + } + public TerminalNode RBRACKET() { return getToken(JSONParser.RBRACKET, 0); } + public List COMMA() { return getTokens(JSONParser.COMMA); } + public TerminalNode COMMA(int i) { + return getToken(JSONParser.COMMA, i); + } + public ArrContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_arr; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof JSONListener ) ((JSONListener)listener).enterArr(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof JSONListener ) ((JSONListener)listener).exitArr(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JSONVisitor ) return ((JSONVisitor)visitor).visitArr(this); + else return visitor.visitChildren(this); + } + } + + public final ArrContext arr() throws RecognitionException { + ArrContext _localctx = new ArrContext(_ctx, getState()); + enterRule(_localctx, 6, RULE_arr); + int _la; + try { + setState(44); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,3,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(31); + match(LBRACKET); + setState(32); + value(); + setState(37); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==COMMA) { + { + { + setState(33); + match(COMMA); + setState(34); + value(); + } + } + setState(39); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(40); + match(RBRACKET); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(42); + match(LBRACKET); + setState(43); + match(RBRACKET); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class ValueContext extends ParserRuleContext { + public TerminalNode STRING() { return getToken(JSONParser.STRING, 0); } + public TerminalNode NUMBER() { return getToken(JSONParser.NUMBER, 0); } + public ObjContext obj() { + return getRuleContext(ObjContext.class,0); + } + public ArrContext arr() { + return getRuleContext(ArrContext.class,0); + } + public TerminalNode TRUE() { return getToken(JSONParser.TRUE, 0); } + public TerminalNode FALSE() { return getToken(JSONParser.FALSE, 0); } + public TerminalNode NULL() { return getToken(JSONParser.NULL, 0); } + public ValueContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_value; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof JSONListener ) ((JSONListener)listener).enterValue(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof JSONListener ) ((JSONListener)listener).exitValue(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JSONVisitor ) return ((JSONVisitor)visitor).visitValue(this); + else return visitor.visitChildren(this); + } + } + + public final ValueContext value() throws RecognitionException { + ValueContext _localctx = new ValueContext(_ctx, getState()); + enterRule(_localctx, 8, RULE_value); + try { + setState(53); + _errHandler.sync(this); + switch (_input.LA(1)) { + case STRING: + enterOuterAlt(_localctx, 1); + { + setState(46); + match(STRING); + } + break; + case NUMBER: + enterOuterAlt(_localctx, 2); + { + setState(47); + match(NUMBER); + } + break; + case LBRACE: + enterOuterAlt(_localctx, 3); + { + setState(48); + obj(); + } + break; + case LBRACKET: + enterOuterAlt(_localctx, 4); + { + setState(49); + arr(); + } + break; + case TRUE: + enterOuterAlt(_localctx, 5); + { + setState(50); + match(TRUE); + } + break; + case FALSE: + enterOuterAlt(_localctx, 6); + { + setState(51); + match(FALSE); + } + break; + case NULL: + enterOuterAlt(_localctx, 7); + { + setState(52); + match(NULL); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static final String _serializedATN = + "\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\3\16:\4\2\t\2\4\3\t"+ + "\3\4\4\t\4\4\5\t\5\4\6\t\6\3\2\3\2\3\3\3\3\3\3\3\3\7\3\23\n\3\f\3\16\3"+ + "\26\13\3\3\3\3\3\3\3\3\3\5\3\34\n\3\3\4\3\4\3\4\3\4\3\5\3\5\3\5\3\5\7"+ + "\5&\n\5\f\5\16\5)\13\5\3\5\3\5\3\5\3\5\5\5/\n\5\3\6\3\6\3\6\3\6\3\6\3"+ + "\6\3\6\5\68\n\6\3\6\2\2\7\2\4\6\b\n\2\2\2>\2\f\3\2\2\2\4\33\3\2\2\2\6"+ + "\35\3\2\2\2\b.\3\2\2\2\n\67\3\2\2\2\f\r\5\n\6\2\r\3\3\2\2\2\16\17\7\b"+ + "\2\2\17\24\5\6\4\2\20\21\7\f\2\2\21\23\5\6\4\2\22\20\3\2\2\2\23\26\3\2"+ + "\2\2\24\22\3\2\2\2\24\25\3\2\2\2\25\27\3\2\2\2\26\24\3\2\2\2\27\30\7\t"+ + "\2\2\30\34\3\2\2\2\31\32\7\b\2\2\32\34\7\t\2\2\33\16\3\2\2\2\33\31\3\2"+ + "\2\2\34\5\3\2\2\2\35\36\7\7\2\2\36\37\7\6\2\2\37 \5\n\6\2 \7\3\2\2\2!"+ + "\"\7\n\2\2\"\'\5\n\6\2#$\7\f\2\2$&\5\n\6\2%#\3\2\2\2&)\3\2\2\2\'%\3\2"+ + "\2\2\'(\3\2\2\2(*\3\2\2\2)\'\3\2\2\2*+\7\13\2\2+/\3\2\2\2,-\7\n\2\2-/"+ + "\7\13\2\2.!\3\2\2\2.,\3\2\2\2/\t\3\2\2\2\608\7\7\2\2\618\7\r\2\2\628\5"+ + "\4\3\2\638\5\b\5\2\648\7\3\2\2\658\7\4\2\2\668\7\5\2\2\67\60\3\2\2\2\67"+ + "\61\3\2\2\2\67\62\3\2\2\2\67\63\3\2\2\2\67\64\3\2\2\2\67\65\3\2\2\2\67"+ + "\66\3\2\2\28\13\3\2\2\2\7\24\33\'.\67"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + static { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JSONVisitor.java b/app/src/main/java/com/tyron/code/language/json/JSONVisitor.java new file mode 100644 index 00000000..6cf9add5 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JSONVisitor.java @@ -0,0 +1,43 @@ +// Generated from /home/tyron/AndroidStudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/json/JSON.g4 by ANTLR 4.9.2 +package com.tyron.code.language.json; +import org.antlr.v4.runtime.tree.ParseTreeVisitor; + +/** + * This interface defines a complete generic visitor for a parse tree produced + * by {@link JSONParser}. + * + * @param The return type of the visit operation. Use {@link Void} for + * operations with no return type. + */ +public interface JSONVisitor extends ParseTreeVisitor { + /** + * Visit a parse tree produced by {@link JSONParser#json}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitJson(JSONParser.JsonContext ctx); + /** + * Visit a parse tree produced by {@link JSONParser#obj}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitObj(JSONParser.ObjContext ctx); + /** + * Visit a parse tree produced by {@link JSONParser#pair}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitPair(JSONParser.PairContext ctx); + /** + * Visit a parse tree produced by {@link JSONParser#arr}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitArr(JSONParser.ArrContext ctx); + /** + * Visit a parse tree produced by {@link JSONParser#value}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitValue(JSONParser.ValueContext ctx); +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/Json.java b/app/src/main/java/com/tyron/code/language/json/Json.java new file mode 100644 index 00000000..e9ba0ef2 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/Json.java @@ -0,0 +1,19 @@ +package com.tyron.code.language.json; + +import com.tyron.code.language.Language; +import com.tyron.editor.Editor; + +import java.io.File; + +public class Json implements Language { + @Override + public boolean isApplicable(File ext) { + return ext.getName().endsWith(".json"); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return new JsonLanguage(editor); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/json/JsonAnalyzer.java b/app/src/main/java/com/tyron/code/language/json/JsonAnalyzer.java new file mode 100644 index 00000000..a0aa49fa --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JsonAnalyzer.java @@ -0,0 +1,102 @@ +package com.tyron.code.language.json; + +import com.tyron.code.language.AbstractCodeAnalyzer; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.Token; + +import java.util.Stack; + +import io.github.rosemoe.sora.lang.styling.CodeBlock; +import io.github.rosemoe.sora.lang.styling.MappedSpans; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; + +public class JsonAnalyzer extends AbstractCodeAnalyzer { + + private final Stack mBlockLines = new Stack<>(); + private int mMaxSwitch; + private int mCurrSwitch; + + @Override + public Lexer getLexer(CharStream input) { + return new JSONLexer(input); + } + + @Override + public void setup() { + putColor(EditorColorScheme.TEXT_NORMAL, JSONLexer.LBRACKET, + JSONLexer.RBRACKET, JSONLexer.LBRACE, JSONLexer.RBRACE); + putColor(EditorColorScheme.KEYWORD, JSONLexer.TRUE, + JSONLexer.FALSE, JSONLexer.NULL, + JSONLexer.COLON, JSONLexer.COMMA); + putColor(EditorColorScheme.OPERATOR, JSONLexer.COLON); + putColor(EditorColorScheme.LITERAL, JSONLexer.NUMBER); + putColor(EditorColorScheme.ATTRIBUTE_NAME, JSONLexer.STRING); + } + + @Override + public void analyzeInBackground(CharSequence contents) { + + } + + @Override + protected void beforeAnalyze() { + mBlockLines.clear(); + mMaxSwitch = 1; + mCurrSwitch = 0; + } + + @Override + public boolean onNextToken(Token currentToken, Styles styles, MappedSpans.Builder colors) { + int line = currentToken.getLine() - 1; + int column = currentToken.getCharPositionInLine(); + + switch (currentToken.getType()) { + case JSONLexer.STRING: + Token previousToken = getPreviousToken(); + if (previousToken != null) { + if (previousToken.getType() == JSONLexer.COLON) { + colors.addIfNeeded(line, column, EditorColorScheme.LITERAL); + return true; + } + } + break; + case JSONLexer.RBRACE: + if (!mBlockLines.isEmpty()) { + CodeBlock b = mBlockLines.pop(); + b.endLine = line; + b.endColumn = column; + if (b.startLine != b.endLine) { + styles.addCodeBlock(b); + } + } + return false; + case JSONLexer.LBRACE: + if (mBlockLines.isEmpty()) { + if (mCurrSwitch > mMaxSwitch) { + mMaxSwitch = mCurrSwitch; + } + mCurrSwitch = 0; + } + mCurrSwitch++; + CodeBlock block = styles.obtainNewBlock(); + block.startLine = line; + block.startColumn = column; + mBlockLines.push(block); + return false; + } + return false; + } + + @Override + protected void afterAnalyze(CharSequence content, Styles styles, MappedSpans.Builder colors) { + if (mBlockLines.isEmpty()) { + if (mMaxSwitch > mCurrSwitch) { + mMaxSwitch = mCurrSwitch; + } + } + styles.setSuppressSwitch(mMaxSwitch + 10); + } +} diff --git a/app/src/main/java/com/tyron/code/language/json/JsonLanguage.java b/app/src/main/java/com/tyron/code/language/json/JsonLanguage.java new file mode 100644 index 00000000..8a70e8ce --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/json/JsonLanguage.java @@ -0,0 +1,172 @@ +package com.tyron.code.language.json; + +import android.annotation.SuppressLint; +import android.content.res.AssetManager; +import android.os.Bundle; + +import androidx.annotation.NonNull; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonWriter; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; +import com.tyron.code.analyzer.BaseTextmateAnalyzer; +import com.tyron.editor.Editor; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Token; + +import java.io.InputStreamReader; +import java.io.StringWriter; + +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; +import io.github.rosemoe.sora.lang.completion.CompletionPublisher; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; +import io.github.rosemoe.sora.langs.textmate.theme.TextMateColorScheme; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.text.TextUtils; +import io.github.rosemoe.sora.widget.SymbolPairMatch; + +public class JsonLanguage implements Language { + + private final Editor mEditor; + + private final BaseTextmateAnalyzer mAnalyzer; + + public JsonLanguage(Editor editor) { + mEditor = editor; + + try { + AssetManager assetManager = ApplicationLoader.applicationContext.getAssets(); + mAnalyzer = new BaseTextmateAnalyzer(editor, "json.tmLanguage.json", + assetManager.open( + "textmate/json" + + "/syntaxes/json" + + ".tmLanguage.json"), + new InputStreamReader( + assetManager.open( + "textmate/json/language-configuration.json")), + ((TextMateColorScheme) ((CodeEditorView) editor).getColorScheme()).getRawTheme()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return mAnalyzer; + } + + @Override + public int getInterruptionLevel() { + return INTERRUPTION_LEVEL_STRONG; + } + + @Override + public void requireAutoComplete(@NonNull ContentReference content, @NonNull CharPosition position, @NonNull CompletionPublisher publisher, @NonNull Bundle extraArguments) throws CompletionCancelledException { + + } + + public int getTabWidth() { + return 2; + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + String text = content.getLine(line).substring(0, column); + return getIndentAdvance(text); + } + + private int getIndentAdvance(String content) { + JSONLexer lexer = new JSONLexer(CharStreams.fromString(content)); + Token token; + int advance = 0; + while ((token = lexer.nextToken()).getType() != Token.EOF) { + int type = token.getType(); + if (type == JSONLexer.LBRACKET || type == JSONLexer.LBRACE) { + advance++; + } + } + advance = Math.max(0, advance); + return advance * getTabWidth(); + } + + @Override + public boolean useTab() { + return true; + } + + @SuppressLint("WrongThread") + @Override + public CharSequence format(CharSequence text) { + try { + Gson gson = new GsonBuilder() + .setPrettyPrinting() + .create(); + try (StringWriter writer = new StringWriter()) { + JsonWriter jsonWriter = gson.newJsonWriter(writer); + jsonWriter.setIndent(useTab() ? "\t" : " "); + gson.toJson(JsonParser.parseString(text.toString()), jsonWriter); + return writer.toString(); + } + } catch (Throwable e) { + // format error, return the original string + return text; + } + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return new SymbolPairMatch.DefaultSymbolPairs(); + } + + @Override + public NewlineHandler[] getNewlineHandlers() { + return new NewlineHandler[] { + new IndentHandler("{", "}"), + new IndentHandler("[", "]") + }; + } + + @Override + public void destroy() { + + } + + class IndentHandler implements NewlineHandler { + + private final String start; + private final String end; + + public IndentHandler(String start, String end) { + this.start = start; + this.end = end; + } + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.endsWith(start) && afterText.startsWith(end); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceBefore = getIndentAdvance(beforeText); + int advanceAfter = getIndentAdvance(afterText); + String text; + StringBuilder sb = new StringBuilder("\n") + .append(TextUtils.createIndent(count + advanceBefore, tabSize, useTab())) + .append('\n') + .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); + int shiftLeft = text.length() + 1; + return new NewlineHandleResult(sb, shiftLeft); + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/kotlin/Kotlin.java b/app/src/main/java/com/tyron/code/language/kotlin/Kotlin.java new file mode 100644 index 00000000..1ab7d973 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/kotlin/Kotlin.java @@ -0,0 +1,18 @@ +package com.tyron.code.language.kotlin; + +import com.tyron.code.language.Language; +import com.tyron.editor.Editor; + +import java.io.File; + +public class Kotlin implements Language { + @Override + public boolean isApplicable(File ext) { + return ext.getName().endsWith(".kt"); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return new KotlinLanguage(editor); + } +} diff --git a/app/src/main/java/com/tyron/code/language/kotlin/KotlinAnalyzer.java b/app/src/main/java/com/tyron/code/language/kotlin/KotlinAnalyzer.java new file mode 100644 index 00000000..13cf7f00 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/kotlin/KotlinAnalyzer.java @@ -0,0 +1,96 @@ +package com.tyron.code.language.kotlin; + +import android.content.res.AssetManager; + +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.analyzer.DiagnosticTextmateAnalyzer; +import com.tyron.code.language.AbstractCodeAnalyzer; +import com.tyron.code.language.HighlightUtil; +import com.tyron.code.language.java.JavaAnalyzer; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.editor.Editor; +import com.tyron.kotlin_completion.CompletionEngine; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenSource; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.ref.WeakReference; + +import io.github.rosemoe.sora.lang.styling.MappedSpans; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora.langs.textmate.theme.TextMateColorScheme; +import io.github.rosemoe.sora.textmate.core.theme.IRawTheme; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; + +public class KotlinAnalyzer extends DiagnosticTextmateAnalyzer { + + private static final String GRAMMAR_NAME = "kotlin.tmLanguage"; + private static final String LANGUAGE_PATH = "textmate/kotlin/syntaxes/kotlin.tmLanguage"; + private static final String CONFIG_PATH = "textmate/kotlin/language-configuration.json"; + + public static KotlinAnalyzer create(Editor editor) { + try { + AssetManager assetManager = ApplicationLoader.applicationContext.getAssets(); + + try (InputStreamReader config = new InputStreamReader(assetManager.open(CONFIG_PATH))) { + return new KotlinAnalyzer(editor, GRAMMAR_NAME, assetManager.open(LANGUAGE_PATH), + config, ((TextMateColorScheme) ((CodeEditorView) editor) + .getColorScheme()).getRawTheme()); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + public KotlinAnalyzer(Editor editor, + String grammarName, + InputStream grammarIns, + Reader languageConfiguration, + IRawTheme theme) throws Exception { + super(editor, grammarName, grammarIns, languageConfiguration, theme); + } + + @Override + public void analyzeInBackground(CharSequence content) { + + // remove this when kotlin analysis is stable + if (true) { + return; + } + + if (mEditor == null) { + return; + } + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + if (currentProject != null) { + Module module = currentProject.getModule(mEditor.getCurrentFile()); + if (module instanceof AndroidModule) { + if (ApplicationLoader.getDefaultPreferences() + .getBoolean(SharedPreferenceKeys.KOTLIN_HIGHLIGHTING, true)) { + ProgressManager.getInstance().runLater(() -> { + + mEditor.setAnalyzing(true); + + CompletionEngine.getInstance((AndroidModule) module) + .doLint(mEditor.getCurrentFile(), content.toString(), diagnostics -> { + mEditor.setDiagnostics(diagnostics); + + ProgressManager.getInstance().runLater(() -> mEditor.setAnalyzing(false), 300); + }); + }, 900); + } + } + } + } + +} diff --git a/app/src/main/java/com/tyron/code/language/kotlin/KotlinAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/language/kotlin/KotlinAutoCompleteProvider.java new file mode 100644 index 00000000..199df232 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/kotlin/KotlinAutoCompleteProvider.java @@ -0,0 +1,94 @@ +package com.tyron.code.language.kotlin; + +import android.content.SharedPreferences; + +import androidx.annotation.Nullable; + +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.KotlinModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.language.AbstractAutoCompleteProvider; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.completion.model.CompletionList; +import com.tyron.editor.Editor; +import com.tyron.kotlin.completion.core.model.KotlinEnvironment; +import com.tyron.kotlin.completion.core.resolve.AnalysisResultWithProvider; +import com.tyron.kotlin.completion.core.resolve.KotlinAnalyzer; +import com.tyron.kotlin_completion.CompletionEngine; + +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; +import org.jetbrains.kotlin.com.intellij.openapi.components.ServiceManager; +import org.jetbrains.kotlin.com.intellij.psi.PsiFile; +import org.jetbrains.kotlin.com.intellij.psi.PsiManager; +import org.jetbrains.kotlin.psi.KtFile; +import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager; + +import java.util.Objects; + +public class KotlinAutoCompleteProvider extends AbstractAutoCompleteProvider { + + private static final String TAG = KotlinAutoCompleteProvider.class.getSimpleName(); + + private final Editor mEditor; + private final SharedPreferences mPreferences; + + private KotlinCoreEnvironment environment; + + public KotlinAutoCompleteProvider(Editor editor) { + mEditor = editor; + mPreferences = ApplicationLoader.getDefaultPreferences(); + } + + @Nullable + @Override + public CompletionList getCompletionList(String prefix, int line, int column) { + if (!mPreferences.getBoolean(SharedPreferenceKeys.KOTLIN_COMPLETIONS, false)) { + return null; + } + + if (com.tyron.completion.java.provider.CompletionEngine.isIndexing()) { + return null; + } + + if (!mPreferences.getBoolean(SharedPreferenceKeys.KOTLIN_COMPLETIONS, false)) { + return null; + } + + Project project = ProjectManager.getInstance() + .getCurrentProject(); + if (project == null) { + return null; + } + + Module currentModule = project.getModule(mEditor.getCurrentFile()); + + if (!(currentModule instanceof AndroidModule)) { + return null; + } + + if (environment == null) { + environment = KotlinEnvironment.getEnvironment((KotlinModule) currentModule); + } + + if (mEditor.getCurrentFile() == null) { + return null; + } + + CompletionEngine engine = CompletionEngine.getInstance((AndroidModule) currentModule); + + if (engine.isIndexing()) { + return null; + } + + // waiting for code editor to support async code completions + return engine.complete(mEditor.getCurrentFile(), + String.valueOf(mEditor.getContent()), + prefix, + line, + column, + mEditor.getCaret().getStart()); + } +} diff --git a/app/src/main/java/com/tyron/code/language/kotlin/KotlinLanguage.java b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLanguage.java new file mode 100644 index 00000000..572b0cf7 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLanguage.java @@ -0,0 +1,162 @@ +package com.tyron.code.language.kotlin; + +import android.content.res.AssetManager; +import android.os.Bundle; + +import androidx.annotation.NonNull; + +import com.tyron.code.ApplicationLoader; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; +import com.tyron.code.language.CompletionItemWrapper; +import com.tyron.code.analyzer.BaseTextmateAnalyzer; +import com.tyron.completion.model.CompletionItem; +import com.tyron.completion.model.CompletionList; +import com.tyron.editor.Editor; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Token; + +import java.io.InputStreamReader; + +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; +import io.github.rosemoe.sora.lang.completion.CompletionHelper; +import io.github.rosemoe.sora.lang.completion.CompletionPublisher; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; +import io.github.rosemoe.sora.langs.textmate.theme.TextMateColorScheme; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.text.TextUtils; +import io.github.rosemoe.sora.util.MyCharacter; +import io.github.rosemoe.sora.widget.SymbolPairMatch; + +public class KotlinLanguage implements Language { + + private final Editor mEditor; + private final KotlinAnalyzer mAnalyzer; + + public KotlinLanguage(Editor editor) { + mEditor = editor; + AssetManager assetManager = ApplicationLoader.applicationContext.getAssets(); + mAnalyzer = KotlinAnalyzer.create(editor); + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return mAnalyzer; + } + + @Override + public int getInterruptionLevel() { + return INTERRUPTION_LEVEL_SLIGHT; + } + + @Override + public void requireAutoComplete(@NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) throws CompletionCancelledException { + + // remove this when kotlin analysis is stable + if (true) { + return; + } + + char c = content.charAt(position.getIndex() - 1); + if (!isAutoCompleteChar(c)) { + return; + } + String prefix = CompletionHelper.computePrefix(content, position, this::isAutoCompleteChar); + KotlinAutoCompleteProvider provider = new KotlinAutoCompleteProvider(mEditor); + CompletionList list = + provider.getCompletionList(prefix, position.getLine(), position.getColumn()); + if (list != null) { + for (CompletionItem item : list.items) { + CompletionItemWrapper wrapper = new CompletionItemWrapper(item); + publisher.addItem(wrapper); + } + } + } + + public boolean isAutoCompleteChar(char p1) { + return p1 == '.' || MyCharacter.isJavaIdentifierPart(p1); + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + String text = content.getLine(line) + .substring(0, column); + return getIndentAdvance(text); + } + + public int getIndentAdvance(String p1) { + KotlinLexer lexer = new KotlinLexer(CharStreams.fromString(p1)); + Token token; + int advance = 0; + while ((token = lexer.nextToken()) != null) { + if (token.getType() == KotlinLexer.EOF) { + break; + } + if (token.getType() == KotlinLexer.LCURL) { + advance++; + /*case RBRACE: + advance--; + break;*/ + } + } + advance = Math.max(0, advance); + return advance * 4; + } + + @Override + public boolean useTab() { + return true; + } + + @Override + public CharSequence format(CharSequence text) { + return text; + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return new SymbolPairMatch.DefaultSymbolPairs(); + } + + @Override + public NewlineHandler[] getNewlineHandlers() { + return handlers; + } + + @Override + public void destroy() { + + } + + private final NewlineHandler[] handlers = new NewlineHandler[]{new BraceHandler()}; + + class BraceHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.endsWith("{") && afterText.startsWith("}"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceBefore = getIndentAdvance(beforeText); + int advanceAfter = getIndentAdvance(afterText); + String text; + StringBuilder sb = new StringBuilder("\n").append( + TextUtils.createIndent(count + advanceBefore, tabSize, useTab())) + .append('\n') + .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); + int shiftLeft = text.length() + 1; + return new NewlineHandleResult(sb, shiftLeft); + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.g4 b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.g4 new file mode 100644 index 00000000..cc1ad40d --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.g4 @@ -0,0 +1,587 @@ +/** + * Kotlin Grammar for ANTLR v4 + * + * Based on: + * http://jetbrains.github.io/kotlin-spec/#_grammars_and_parsing + * and + * http://kotlinlang.org/docs/reference/grammar.html + * + * Tested on + * https://github.com/JetBrains/kotlin/tree/master/compiler/testData/psi + */ + +lexer grammar KotlinLexer; + +import UnicodeClasses; + +ShebangLine + : '#!' ~[\u000A\u000D]* + -> channel(HIDDEN) + ; + +DelimitedComment + : '/*' ( DelimitedComment | . )*? '*/' + -> channel(HIDDEN) + ; + +LineComment + : '//' ~[\u000A\u000D]* + -> channel(HIDDEN) + ; + +WS + : [\u0020\u0009\u000C] + -> skip + ; + +NL: '\u000A' | '\u000D' '\u000A' ; + +//SEPARATORS & OPERATIONS + +RESERVED: '...' ; +DOT: '.' ; +COMMA: ',' ; +LPAREN: '(' -> pushMode(Inside) ; +RPAREN: ')' ; +LSQUARE: '[' -> pushMode(Inside) ; +RSQUARE: ']' ; +LCURL: '{' ; +RCURL: '}' ; +MULT: '*' ; +MOD: '%' ; +DIV: '/' ; +ADD: '+' ; +SUB: '-' ; +INCR: '++' ; +DECR: '--' ; +CONJ: '&&' ; +DISJ: '||' ; +EXCL: '!' ; +COLON: ':' ; +SEMICOLON: ';' ; +ASSIGNMENT: '=' ; +ADD_ASSIGNMENT: '+=' ; +SUB_ASSIGNMENT: '-=' ; +MULT_ASSIGNMENT: '*=' ; +DIV_ASSIGNMENT: '/=' ; +MOD_ASSIGNMENT: '%=' ; +ARROW: '->' ; +DOUBLE_ARROW: '=>' ; +RANGE: '..' ; +COLONCOLON: '::' ; +Q_COLONCOLON: '?::' ; +DOUBLE_SEMICOLON: ';;' ; +HASH: '#' ; +AT: '@' ; +QUEST: '?' ; +ELVIS: '?:' ; +LANGLE: '<' ; +RANGLE: '>' ; +LE: '<=' ; +GE: '>=' ; +EXCL_EQ: '!=' ; +EXCL_EQEQ: '!==' ; +AS_SAFE: 'as?' ; +EQEQ: '==' ; +EQEQEQ: '===' ; +SINGLE_QUOTE: '\'' ; + +//KEYWORDS + +RETURN_AT: 'return@' Identifier ; +CONTINUE_AT: 'continue@' Identifier ; +BREAK_AT: 'break@' Identifier ; + +FILE: '@file' ; +PACKAGE: 'package' ; +IMPORT: 'import' ; +CLASS: 'class' ; +INTERFACE: 'interface' ; +FUN: 'fun' ; +OBJECT: 'object' ; +VAL: 'val' ; +VAR: 'var' ; +TYPE_ALIAS: 'typealias' ; +CONSTRUCTOR: 'constructor' ; +BY: 'by' ; +COMPANION: 'companion' ; +INIT: 'init' ; +THIS: 'this' ; +SUPER: 'super' ; +TYPEOF: 'typeof' ; +WHERE: 'where' ; +IF: 'if' ; +ELSE: 'else' ; +WHEN: 'when' ; +TRY: 'try' ; +CATCH: 'catch' ; +FINALLY: 'finally' ; +FOR: 'for' ; +DO: 'do' ; +WHILE: 'while' ; +THROW: 'throw' ; +RETURN: 'return' ; +CONTINUE: 'continue' ; +BREAK: 'break' ; +AS: 'as' ; +IS: 'is' ; +IN: 'in' ; +NOT_IS: '!is' (WS | NL)+ ; +NOT_IN: '!in' (WS | NL)+ ; +OUT: 'out' ; +FIELD: '@field' ; +PROPERTY: '@property' ; +GET: '@get' ; +SET: '@set' ; +GETTER: 'get' ; +SETTER: 'set' ; +RECEIVER: '@receiver' ; +PARAM: '@param' ; +SETPARAM: '@setparam' ; +DELEGATE: '@delegate' ; +DYNAMIC: 'dynamic' ; + +//MODIFIERS + +PUBLIC: 'public' ; +PRIVATE: 'private' ; +PROTECTED: 'protected' ; +INTERNAL: 'internal' ; +ENUM: 'enum' ; +SEALED: 'sealed' ; +ANNOTATION: 'annotation' ; +DATA: 'data' ; +INNER: 'inner' ; +TAILREC: 'tailrec' ; +OPERATOR: 'operator' ; +INLINE: 'inline' ; +INFIX: 'infix' ; +EXTERNAL: 'external' ; +SUSPEND: 'suspend' ; +OVERRIDE: 'override' ; +ABSTRACT: 'abstract' ; +FINAL: 'final' ; +OPEN: 'open' ; +CONST: 'const' ; +LATEINIT: 'lateinit' ; +VARARG: 'vararg' ; +NOINLINE: 'noinline' ; +CROSSINLINE: 'crossinline' ; +REIFIED: 'reified' ; + +// + +QUOTE_OPEN: '"' -> pushMode(LineString) ; +TRIPLE_QUOTE_OPEN: '"""' -> pushMode(MultiLineString) ; + +RealLiteral + : FloatLiteral + | DoubleLiteral + ; + +FloatLiteral + : (DoubleLiteral | IntegerLiteral) [fF] + ; + +DoubleLiteral + : ( (DecDigitNoZero DecDigit* | '0')? '.' + | (DecDigitNoZero (DecDigit | '_')* DecDigit)? '.') + ( DecDigit+ + | DecDigit (DecDigit | '_')+ DecDigit + | DecDigit+ [eE] ('+' | '-')? DecDigit+ + | DecDigit+ [eE] ('+' | '-')? DecDigit (DecDigit | '_')+ DecDigit + | DecDigit (DecDigit | '_')+ DecDigit [eE] ('+' | '-')? DecDigit+ + | DecDigit (DecDigit | '_')+ DecDigit [eE] ('+' | '-')? DecDigit (DecDigit | '_')+ DecDigit + ) + ; + +LongLiteral + : (IntegerLiteral | HexLiteral | BinLiteral) 'L' + ; + +IntegerLiteral + : ('0' + | DecDigitNoZero DecDigit* + | DecDigitNoZero (DecDigit | '_')+ DecDigit + | DecDigitNoZero DecDigit* [eE] ('+' | '-')? DecDigit+ + | DecDigitNoZero DecDigit* [eE] ('+' | '-')? DecDigit (DecDigit | '_')+ DecDigit + | DecDigitNoZero (DecDigit | '_')+ DecDigit [eE] ('+' | '-')? DecDigit+ + | DecDigitNoZero (DecDigit | '_')+ DecDigit [eE] ('+' | '-')? DecDigit (DecDigit | '_')+ DecDigit + ) + ; + +fragment DecDigit + : UNICODE_CLASS_ND + ; + +fragment DecDigitNoZero + : UNICODE_CLASS_ND_NoZeros + ; + +fragment UNICODE_CLASS_ND_NoZeros + : '\u0031'..'\u0039' + | '\u0661'..'\u0669' + | '\u06f1'..'\u06f9' + | '\u07c1'..'\u07c9' + | '\u0967'..'\u096f' + | '\u09e7'..'\u09ef' + | '\u0a67'..'\u0a6f' + | '\u0ae7'..'\u0aef' + | '\u0b67'..'\u0b6f' + | '\u0be7'..'\u0bef' + | '\u0c67'..'\u0c6f' + | '\u0ce7'..'\u0cef' + | '\u0d67'..'\u0d6f' + | '\u0de7'..'\u0def' + | '\u0e51'..'\u0e59' + | '\u0ed1'..'\u0ed9' + | '\u0f21'..'\u0f29' + | '\u1041'..'\u1049' + | '\u1091'..'\u1099' + | '\u17e1'..'\u17e9' + | '\u1811'..'\u1819' + | '\u1947'..'\u194f' + | '\u19d1'..'\u19d9' + | '\u1a81'..'\u1a89' + | '\u1a91'..'\u1a99' + | '\u1b51'..'\u1b59' + | '\u1bb1'..'\u1bb9' + | '\u1c41'..'\u1c49' + | '\u1c51'..'\u1c59' + | '\ua621'..'\ua629' + | '\ua8d1'..'\ua8d9' + | '\ua901'..'\ua909' + | '\ua9d1'..'\ua9d9' + | '\ua9f1'..'\ua9f9' + | '\uaa51'..'\uaa59' + | '\uabf1'..'\uabf9' + | '\uff11'..'\uff19' + ; + +HexLiteral + : '0' [xX] HexDigit (HexDigit | '_')* + ; + +fragment HexDigit + : [0-9a-fA-F] + ; + +BinLiteral + : '0' [bB] BinDigit (BinDigit | '_')* + ; + +fragment BinDigit + : [01] + ; + +BooleanLiteral + : 'true' + | 'false' + ; + +NullLiteral + : 'null' + ; + +Identifier + : (Letter | '_') (Letter | '_' | DecDigit)* + | '`' ~('`')+ '`' + ; + +LabelReference + : '@' Identifier + ; + +LabelDefinition + : Identifier '@' + ; + +FieldIdentifier + : '$' Identifier + ; + +CharacterLiteral + : '\'' (EscapeSeq | .) '\'' + ; + +fragment EscapeSeq + : UniCharacterLiteral + | EscapedIdentifier + ; + +fragment UniCharacterLiteral + : '\\' 'u' HexDigit HexDigit HexDigit HexDigit + ; + +fragment EscapedIdentifier + : '\\' ('t' | 'b' | 'r' | 'n' | '\'' | '"' | '\\' | '$') + ; + +fragment Letter + : UNICODE_CLASS_LL + | UNICODE_CLASS_LM + | UNICODE_CLASS_LO + | UNICODE_CLASS_LT + | UNICODE_CLASS_LU + | UNICODE_CLASS_NL + ; + + +mode Inside ; + +Inside_RPAREN: ')' -> popMode, type(RPAREN) ; +Inside_RSQUARE: ']' -> popMode, type(RSQUARE); + +Inside_LPAREN: LPAREN -> pushMode(Inside), type(LPAREN) ; +Inside_LSQUARE: LSQUARE -> pushMode(Inside), type(LSQUARE) ; + +Inside_LCURL: LCURL -> type(LCURL) ; +Inside_RCURL: RCURL -> type(RCURL) ; +Inside_DOT: DOT -> type(DOT) ; +Inside_COMMA: COMMA -> type(COMMA) ; +Inside_MULT: MULT -> type(MULT) ; +Inside_MOD: MOD -> type(MOD) ; +Inside_DIV: DIV -> type(DIV) ; +Inside_ADD: ADD -> type(ADD) ; +Inside_SUB: SUB -> type(SUB) ; +Inside_INCR: INCR -> type(INCR) ; +Inside_DECR: DECR -> type(DECR) ; +Inside_CONJ: CONJ -> type(CONJ) ; +Inside_DISJ: DISJ -> type(DISJ) ; +Inside_EXCL: EXCL -> type(EXCL) ; +Inside_COLON: COLON -> type(COLON) ; +Inside_SEMICOLON: SEMICOLON -> type(SEMICOLON) ; +Inside_ASSIGNMENT: ASSIGNMENT -> type(ASSIGNMENT) ; +Inside_ADD_ASSIGNMENT: ADD_ASSIGNMENT -> type(ADD_ASSIGNMENT) ; +Inside_SUB_ASSIGNMENT: SUB_ASSIGNMENT -> type(SUB_ASSIGNMENT) ; +Inside_MULT_ASSIGNMENT: MULT_ASSIGNMENT -> type(MULT_ASSIGNMENT) ; +Inside_DIV_ASSIGNMENT: DIV_ASSIGNMENT -> type(DIV_ASSIGNMENT) ; +Inside_MOD_ASSIGNMENT: MOD_ASSIGNMENT -> type(MOD_ASSIGNMENT) ; +Inside_ARROW: ARROW -> type(ARROW) ; +Inside_DOUBLE_ARROW: DOUBLE_ARROW -> type(DOUBLE_ARROW) ; +Inside_RANGE: RANGE -> type(RANGE) ; +Inside_RESERVED: RESERVED -> type(RESERVED) ; +Inside_COLONCOLON: COLONCOLON -> type(COLONCOLON) ; +Inside_Q_COLONCOLON: Q_COLONCOLON -> type(Q_COLONCOLON) ; +Inside_DOUBLE_SEMICOLON: DOUBLE_SEMICOLON -> type(DOUBLE_SEMICOLON) ; +Inside_HASH: HASH -> type(HASH) ; +Inside_AT: AT -> type(AT) ; +Inside_QUEST: QUEST -> type(QUEST) ; +Inside_ELVIS: ELVIS -> type(ELVIS) ; +Inside_LANGLE: LANGLE -> type(LANGLE) ; +Inside_RANGLE: RANGLE -> type(RANGLE) ; +Inside_LE: LE -> type(LE) ; +Inside_GE: GE -> type(GE) ; +Inside_EXCL_EQ: EXCL_EQ -> type(EXCL_EQ) ; +Inside_EXCL_EQEQ: EXCL_EQEQ -> type(EXCL_EQEQ) ; +Inside_NOT_IS: NOT_IS -> type(NOT_IS) ; +Inside_NOT_IN: NOT_IN -> type(NOT_IN) ; +Inside_AS_SAFE: AS_SAFE -> type(AS_SAFE) ; +Inside_EQEQ: EQEQ -> type(EQEQ) ; +Inside_EQEQEQ: EQEQEQ -> type(EQEQEQ) ; +Inside_SINGLE_QUOTE: SINGLE_QUOTE -> type(SINGLE_QUOTE) ; +Inside_QUOTE_OPEN: QUOTE_OPEN -> pushMode(LineString), type(QUOTE_OPEN) ; +Inside_TRIPLE_QUOTE_OPEN: TRIPLE_QUOTE_OPEN -> pushMode(MultiLineString), type(TRIPLE_QUOTE_OPEN) ; + +Inside_VAL: VAL -> type(VAL) ; +Inside_VAR: VAR -> type(VAR) ; +Inside_OBJECT: OBJECT -> type(OBJECT) ; +Inside_SUPER: SUPER -> type(SUPER) ; +Inside_IN: IN -> type(IN) ; +Inside_OUT: OUT -> type(OUT) ; +Inside_FIELD: FIELD -> type(FIELD) ; +Inside_FILE: FILE -> type(FILE) ; +Inside_PROPERTY: PROPERTY -> type(PROPERTY) ; +Inside_GET: GET -> type(GET) ; +Inside_SET: SET -> type(SET) ; +Inside_RECEIVER: RECEIVER -> type(RECEIVER) ; +Inside_PARAM: PARAM -> type(PARAM) ; +Inside_SETPARAM: SETPARAM -> type(SETPARAM) ; +Inside_DELEGATE: DELEGATE -> type(DELEGATE) ; +Inside_THROW: THROW -> type(THROW) ; +Inside_RETURN: RETURN -> type(RETURN) ; +Inside_CONTINUE: CONTINUE -> type(CONTINUE) ; +Inside_BREAK: BREAK -> type(BREAK) ; +Inside_RETURN_AT: RETURN_AT -> type(RETURN_AT) ; +Inside_CONTINUE_AT: CONTINUE_AT -> type(CONTINUE_AT) ; +Inside_BREAK_AT: BREAK_AT -> type(BREAK_AT) ; +Inside_IF: IF -> type(IF) ; +Inside_ELSE: ELSE -> type(ELSE) ; +Inside_WHEN: WHEN -> type(WHEN) ; +Inside_TRY: TRY -> type(TRY) ; +Inside_CATCH: CATCH -> type(CATCH) ; +Inside_FINALLY: FINALLY -> type(FINALLY) ; +Inside_FOR: FOR -> type(FOR) ; +Inside_DO: DO -> type(DO) ; +Inside_WHILE: WHILE -> type(WHILE) ; + +Inside_PUBLIC: PUBLIC -> type(PUBLIC) ; +Inside_PRIVATE: PRIVATE -> type(PRIVATE) ; +Inside_PROTECTED: PROTECTED -> type(PROTECTED) ; +Inside_INTERNAL: INTERNAL -> type(INTERNAL) ; +Inside_ENUM: ENUM -> type(ENUM) ; +Inside_SEALED: SEALED -> type(SEALED) ; +Inside_ANNOTATION: ANNOTATION -> type(ANNOTATION) ; +Inside_DATA: DATA -> type(DATA) ; +Inside_INNER: INNER -> type(INNER) ; +Inside_TAILREC: TAILREC -> type(TAILREC) ; +Inside_OPERATOR: OPERATOR -> type(OPERATOR) ; +Inside_INLINE: INLINE -> type(INLINE) ; +Inside_INFIX: INFIX -> type(INFIX) ; +Inside_EXTERNAL: EXTERNAL -> type(EXTERNAL) ; +Inside_SUSPEND: SUSPEND -> type(SUSPEND) ; +Inside_OVERRIDE: OVERRIDE -> type(OVERRIDE) ; +Inside_ABSTRACT: ABSTRACT -> type(ABSTRACT) ; +Inside_FINAL: FINAL -> type(FINAL) ; +Inside_OPEN: OPEN -> type(OPEN) ; +Inside_CONST: CONST -> type(CONST) ; +Inside_LATEINIT: LATEINIT -> type(LATEINIT) ; +Inside_VARARG: VARARG -> type(VARARG) ; +Inside_NOINLINE: NOINLINE -> type(NOINLINE) ; +Inside_CROSSINLINE: CROSSINLINE -> type(CROSSINLINE) ; +Inside_REIFIED: REIFIED -> type(REIFIED) ; + +Inside_BooleanLiteral: BooleanLiteral -> type(BooleanLiteral) ; +Inside_IntegerLiteral: IntegerLiteral -> type(IntegerLiteral) ; +Inside_HexLiteral: HexLiteral -> type(HexLiteral) ; +Inside_BinLiteral: BinLiteral -> type(BinLiteral) ; +Inside_CharacterLiteral: CharacterLiteral -> type(CharacterLiteral) ; +Inside_RealLiteral: RealLiteral -> type(RealLiteral) ; +Inside_NullLiteral: NullLiteral -> type(NullLiteral) ; + +Inside_LongLiteral: LongLiteral -> type(LongLiteral) ; + +Inside_Identifier: Identifier -> type(Identifier) ; +Inside_LabelReference: LabelReference -> type(LabelReference) ; +Inside_LabelDefinition: LabelDefinition -> type(LabelDefinition) ; +Inside_Comment: (LineComment | DelimitedComment) -> channel(HIDDEN) ; +Inside_WS: WS -> skip ; +Inside_NL: NL -> skip ; + + +mode LineString ; + +QUOTE_CLOSE + : '"' -> popMode + ; + +LineStrRef + : FieldIdentifier + ; + +LineStrText + : ~('\\' | '"' | '$')+ | '$' + ; + +LineStrEscapedChar + : '\\' . + | UniCharacterLiteral + ; + +LineStrExprStart + : '${' -> pushMode(StringExpression) + ; + + +mode MultiLineString ; + +TRIPLE_QUOTE_CLOSE + : MultiLineStringQuote? '"""' -> popMode + ; + +MultiLineStringQuote + : '"'+ + ; + +MultiLineStrRef + : FieldIdentifier + ; + +MultiLineStrText + : ~('\\' | '"' | '$')+ | '$' + ; + +MultiLineStrEscapedChar + : '\\' . + ; + +MultiLineStrExprStart + : '${' -> pushMode(StringExpression) + ; + +MultiLineNL: NL -> skip ; + + +mode StringExpression ; + +StrExpr_RCURL: RCURL -> popMode, type(RCURL) ; + +StrExpr_LPAREN: LPAREN -> pushMode(Inside), type(LPAREN) ; +StrExpr_LSQUARE: LSQUARE -> pushMode(Inside), type(LSQUARE) ; + +StrExpr_RPAREN: ')' -> type(RPAREN) ; +StrExpr_RSQUARE: ']' -> type(RSQUARE); +StrExpr_LCURL: LCURL -> pushMode(StringExpression), type(LCURL) ; +StrExpr_DOT: DOT -> type(DOT) ; +StrExpr_COMMA: COMMA -> type(COMMA) ; +StrExpr_MULT: MULT -> type(MULT) ; +StrExpr_MOD: MOD -> type(MOD) ; +StrExpr_DIV: DIV -> type(DIV) ; +StrExpr_ADD: ADD -> type(ADD) ; +StrExpr_SUB: SUB -> type(SUB) ; +StrExpr_INCR: INCR -> type(INCR) ; +StrExpr_DECR: DECR -> type(DECR) ; +StrExpr_CONJ: CONJ -> type(CONJ) ; +StrExpr_DISJ: DISJ -> type(DISJ) ; +StrExpr_EXCL: EXCL -> type(EXCL) ; +StrExpr_COLON: COLON -> type(COLON) ; +StrExpr_SEMICOLON: SEMICOLON -> type(SEMICOLON) ; +StrExpr_ASSIGNMENT: ASSIGNMENT -> type(ASSIGNMENT) ; +StrExpr_ADD_ASSIGNMENT: ADD_ASSIGNMENT -> type(ADD_ASSIGNMENT) ; +StrExpr_SUB_ASSIGNMENT: SUB_ASSIGNMENT -> type(SUB_ASSIGNMENT) ; +StrExpr_MULT_ASSIGNMENT: MULT_ASSIGNMENT -> type(MULT_ASSIGNMENT) ; +StrExpr_DIV_ASSIGNMENT: DIV_ASSIGNMENT -> type(DIV_ASSIGNMENT) ; +StrExpr_MOD_ASSIGNMENT: MOD_ASSIGNMENT -> type(MOD_ASSIGNMENT) ; +StrExpr_ARROW: ARROW -> type(ARROW) ; +StrExpr_DOUBLE_ARROW: DOUBLE_ARROW -> type(DOUBLE_ARROW) ; +StrExpr_RANGE: RANGE -> type(RANGE) ; +StrExpr_COLONCOLON: COLONCOLON -> type(COLONCOLON) ; +StrExpr_Q_COLONCOLON: Q_COLONCOLON -> type(Q_COLONCOLON) ; +StrExpr_DOUBLE_SEMICOLON: DOUBLE_SEMICOLON -> type(DOUBLE_SEMICOLON) ; +StrExpr_HASH: HASH -> type(HASH) ; +StrExpr_AT: AT -> type(AT) ; +StrExpr_QUEST: QUEST -> type(QUEST) ; +StrExpr_ELVIS: ELVIS -> type(ELVIS) ; +StrExpr_LANGLE: LANGLE -> type(LANGLE) ; +StrExpr_RANGLE: RANGLE -> type(RANGLE) ; +StrExpr_LE: LE -> type(LE) ; +StrExpr_GE: GE -> type(GE) ; +StrExpr_EXCL_EQ: EXCL_EQ -> type(EXCL_EQ) ; +StrExpr_EXCL_EQEQ: EXCL_EQEQ -> type(EXCL_EQEQ) ; +StrExpr_AS: AS -> type(IS) ; +StrExpr_IS: IS -> type(IN) ; +StrExpr_IN: IN ; +StrExpr_NOT_IS: NOT_IS -> type(NOT_IS) ; +StrExpr_NOT_IN: NOT_IN -> type(NOT_IN) ; +StrExpr_AS_SAFE: AS_SAFE -> type(AS_SAFE) ; +StrExpr_EQEQ: EQEQ -> type(EQEQ) ; +StrExpr_EQEQEQ: EQEQEQ -> type(EQEQEQ) ; +StrExpr_SINGLE_QUOTE: SINGLE_QUOTE -> type(SINGLE_QUOTE) ; +StrExpr_QUOTE_OPEN: QUOTE_OPEN -> pushMode(LineString), type(QUOTE_OPEN) ; +StrExpr_TRIPLE_QUOTE_OPEN: TRIPLE_QUOTE_OPEN -> pushMode(MultiLineString), type(TRIPLE_QUOTE_OPEN) ; + +StrExpr_BooleanLiteral: BooleanLiteral -> type(BooleanLiteral) ; +StrExpr_IntegerLiteral: IntegerLiteral -> type(IntegerLiteral) ; +StrExpr_HexLiteral: HexLiteral -> type(HexLiteral) ; +StrExpr_BinLiteral: BinLiteral -> type(BinLiteral) ; +StrExpr_CharacterLiteral: CharacterLiteral -> type(CharacterLiteral) ; +StrExpr_RealLiteral: RealLiteral -> type(RealLiteral) ; +StrExpr_NullLiteral: NullLiteral -> type(NullLiteral) ; +StrExpr_LongLiteral: LongLiteral -> type(LongLiteral) ; + +StrExpr_Identifier: Identifier -> type(Identifier) ; +StrExpr_LabelReference: LabelReference -> type(LabelReference) ; +StrExpr_LabelDefinition: LabelDefinition -> type(LabelDefinition) ; +StrExpr_Comment: (LineComment | DelimitedComment) -> channel(HIDDEN) ; +StrExpr_WS: WS -> skip ; +StrExpr_NL: NL -> skip ; \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.java b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.java new file mode 100644 index 00000000..b5dfbab9 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/kotlin/KotlinLexer.java @@ -0,0 +1,1581 @@ +// Generated from C:/Users/admin/StudioProjects/CodeAssist/app/src/main/java/com/tyron/code/ui/editor/language/kotlin\KotlinLexer.g4 by ANTLR 4.9.1 +package com.tyron.code.language.kotlin; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.misc.*; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) +public class KotlinLexer extends Lexer { + static { RuntimeMetaData.checkVersion("4.9.1", RuntimeMetaData.VERSION); } + + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + public static final int + ShebangLine=1, DelimitedComment=2, LineComment=3, WS=4, NL=5, RESERVED=6, + DOT=7, COMMA=8, LPAREN=9, RPAREN=10, LSQUARE=11, RSQUARE=12, LCURL=13, + RCURL=14, MULT=15, MOD=16, DIV=17, ADD=18, SUB=19, INCR=20, DECR=21, CONJ=22, + DISJ=23, EXCL=24, COLON=25, SEMICOLON=26, ASSIGNMENT=27, ADD_ASSIGNMENT=28, + SUB_ASSIGNMENT=29, MULT_ASSIGNMENT=30, DIV_ASSIGNMENT=31, MOD_ASSIGNMENT=32, + ARROW=33, DOUBLE_ARROW=34, RANGE=35, COLONCOLON=36, Q_COLONCOLON=37, DOUBLE_SEMICOLON=38, + HASH=39, AT=40, QUEST=41, ELVIS=42, LANGLE=43, RANGLE=44, LE=45, GE=46, + EXCL_EQ=47, EXCL_EQEQ=48, AS_SAFE=49, EQEQ=50, EQEQEQ=51, SINGLE_QUOTE=52, + RETURN_AT=53, CONTINUE_AT=54, BREAK_AT=55, FILE=56, PACKAGE=57, IMPORT=58, + CLASS=59, INTERFACE=60, FUN=61, OBJECT=62, VAL=63, VAR=64, TYPE_ALIAS=65, + CONSTRUCTOR=66, BY=67, COMPANION=68, INIT=69, THIS=70, SUPER=71, TYPEOF=72, + WHERE=73, IF=74, ELSE=75, WHEN=76, TRY=77, CATCH=78, FINALLY=79, FOR=80, + DO=81, WHILE=82, THROW=83, RETURN=84, CONTINUE=85, BREAK=86, AS=87, IS=88, + IN=89, NOT_IS=90, NOT_IN=91, OUT=92, FIELD=93, PROPERTY=94, GET=95, SET=96, + GETTER=97, SETTER=98, RECEIVER=99, PARAM=100, SETPARAM=101, DELEGATE=102, + DYNAMIC=103, PUBLIC=104, PRIVATE=105, PROTECTED=106, INTERNAL=107, ENUM=108, + SEALED=109, ANNOTATION=110, DATA=111, INNER=112, TAILREC=113, OPERATOR=114, + INLINE=115, INFIX=116, EXTERNAL=117, SUSPEND=118, OVERRIDE=119, ABSTRACT=120, + FINAL=121, OPEN=122, CONST=123, LATEINIT=124, VARARG=125, NOINLINE=126, + CROSSINLINE=127, REIFIED=128, QUOTE_OPEN=129, TRIPLE_QUOTE_OPEN=130, RealLiteral=131, + FloatLiteral=132, DoubleLiteral=133, LongLiteral=134, IntegerLiteral=135, + HexLiteral=136, BinLiteral=137, BooleanLiteral=138, NullLiteral=139, Identifier=140, + LabelReference=141, LabelDefinition=142, FieldIdentifier=143, CharacterLiteral=144, + UNICODE_CLASS_LL=145, UNICODE_CLASS_LM=146, UNICODE_CLASS_LO=147, UNICODE_CLASS_LT=148, + UNICODE_CLASS_LU=149, UNICODE_CLASS_ND=150, UNICODE_CLASS_NL=151, Inside_Comment=152, + Inside_WS=153, Inside_NL=154, QUOTE_CLOSE=155, LineStrRef=156, LineStrText=157, + LineStrEscapedChar=158, LineStrExprStart=159, TRIPLE_QUOTE_CLOSE=160, + MultiLineStringQuote=161, MultiLineStrRef=162, MultiLineStrText=163, MultiLineStrEscapedChar=164, + MultiLineStrExprStart=165, MultiLineNL=166, StrExpr_IN=167, StrExpr_Comment=168, + StrExpr_WS=169, StrExpr_NL=170; + public static final int + Inside=1, LineString=2, MultiLineString=3, StringExpression=4; + public static String[] channelNames = { + "DEFAULT_TOKEN_CHANNEL", "HIDDEN" + }; + + public static String[] modeNames = { + "DEFAULT_MODE", "Inside", "LineString", "MultiLineString", "StringExpression" + }; + + private static String[] makeRuleNames() { + return new String[] { + "ShebangLine", "DelimitedComment", "LineComment", "WS", "NL", "RESERVED", + "DOT", "COMMA", "LPAREN", "RPAREN", "LSQUARE", "RSQUARE", "LCURL", "RCURL", + "MULT", "MOD", "DIV", "ADD", "SUB", "INCR", "DECR", "CONJ", "DISJ", "EXCL", + "COLON", "SEMICOLON", "ASSIGNMENT", "ADD_ASSIGNMENT", "SUB_ASSIGNMENT", + "MULT_ASSIGNMENT", "DIV_ASSIGNMENT", "MOD_ASSIGNMENT", "ARROW", "DOUBLE_ARROW", + "RANGE", "COLONCOLON", "Q_COLONCOLON", "DOUBLE_SEMICOLON", "HASH", "AT", + "QUEST", "ELVIS", "LANGLE", "RANGLE", "LE", "GE", "EXCL_EQ", "EXCL_EQEQ", + "AS_SAFE", "EQEQ", "EQEQEQ", "SINGLE_QUOTE", "RETURN_AT", "CONTINUE_AT", + "BREAK_AT", "FILE", "PACKAGE", "IMPORT", "CLASS", "INTERFACE", "FUN", + "OBJECT", "VAL", "VAR", "TYPE_ALIAS", "CONSTRUCTOR", "BY", "COMPANION", + "INIT", "THIS", "SUPER", "TYPEOF", "WHERE", "IF", "ELSE", "WHEN", "TRY", + "CATCH", "FINALLY", "FOR", "DO", "WHILE", "THROW", "RETURN", "CONTINUE", + "BREAK", "AS", "IS", "IN", "NOT_IS", "NOT_IN", "OUT", "FIELD", "PROPERTY", + "GET", "SET", "GETTER", "SETTER", "RECEIVER", "PARAM", "SETPARAM", "DELEGATE", + "DYNAMIC", "PUBLIC", "PRIVATE", "PROTECTED", "INTERNAL", "ENUM", "SEALED", + "ANNOTATION", "DATA", "INNER", "TAILREC", "OPERATOR", "INLINE", "INFIX", + "EXTERNAL", "SUSPEND", "OVERRIDE", "ABSTRACT", "FINAL", "OPEN", "CONST", + "LATEINIT", "VARARG", "NOINLINE", "CROSSINLINE", "REIFIED", "QUOTE_OPEN", + "TRIPLE_QUOTE_OPEN", "RealLiteral", "FloatLiteral", "DoubleLiteral", + "LongLiteral", "IntegerLiteral", "DecDigit", "DecDigitNoZero", "UNICODE_CLASS_ND_NoZeros", + "HexLiteral", "HexDigit", "BinLiteral", "BinDigit", "BooleanLiteral", + "NullLiteral", "Identifier", "LabelReference", "LabelDefinition", "FieldIdentifier", + "CharacterLiteral", "EscapeSeq", "UniCharacterLiteral", "EscapedIdentifier", + "Letter", "UNICODE_CLASS_LL", "UNICODE_CLASS_LM", "UNICODE_CLASS_LO", + "UNICODE_CLASS_LT", "UNICODE_CLASS_LU", "UNICODE_CLASS_ND", "UNICODE_CLASS_NL", + "Inside_RPAREN", "Inside_RSQUARE", "Inside_LPAREN", "Inside_LSQUARE", + "Inside_LCURL", "Inside_RCURL", "Inside_DOT", "Inside_COMMA", "Inside_MULT", + "Inside_MOD", "Inside_DIV", "Inside_ADD", "Inside_SUB", "Inside_INCR", + "Inside_DECR", "Inside_CONJ", "Inside_DISJ", "Inside_EXCL", "Inside_COLON", + "Inside_SEMICOLON", "Inside_ASSIGNMENT", "Inside_ADD_ASSIGNMENT", "Inside_SUB_ASSIGNMENT", + "Inside_MULT_ASSIGNMENT", "Inside_DIV_ASSIGNMENT", "Inside_MOD_ASSIGNMENT", + "Inside_ARROW", "Inside_DOUBLE_ARROW", "Inside_RANGE", "Inside_RESERVED", + "Inside_COLONCOLON", "Inside_Q_COLONCOLON", "Inside_DOUBLE_SEMICOLON", + "Inside_HASH", "Inside_AT", "Inside_QUEST", "Inside_ELVIS", "Inside_LANGLE", + "Inside_RANGLE", "Inside_LE", "Inside_GE", "Inside_EXCL_EQ", "Inside_EXCL_EQEQ", + "Inside_NOT_IS", "Inside_NOT_IN", "Inside_AS_SAFE", "Inside_EQEQ", "Inside_EQEQEQ", + "Inside_SINGLE_QUOTE", "Inside_QUOTE_OPEN", "Inside_TRIPLE_QUOTE_OPEN", + "Inside_VAL", "Inside_VAR", "Inside_OBJECT", "Inside_SUPER", "Inside_IN", + "Inside_OUT", "Inside_FIELD", "Inside_FILE", "Inside_PROPERTY", "Inside_GET", + "Inside_SET", "Inside_RECEIVER", "Inside_PARAM", "Inside_SETPARAM", "Inside_DELEGATE", + "Inside_THROW", "Inside_RETURN", "Inside_CONTINUE", "Inside_BREAK", "Inside_RETURN_AT", + "Inside_CONTINUE_AT", "Inside_BREAK_AT", "Inside_IF", "Inside_ELSE", + "Inside_WHEN", "Inside_TRY", "Inside_CATCH", "Inside_FINALLY", "Inside_FOR", + "Inside_DO", "Inside_WHILE", "Inside_PUBLIC", "Inside_PRIVATE", "Inside_PROTECTED", + "Inside_INTERNAL", "Inside_ENUM", "Inside_SEALED", "Inside_ANNOTATION", + "Inside_DATA", "Inside_INNER", "Inside_TAILREC", "Inside_OPERATOR", "Inside_INLINE", + "Inside_INFIX", "Inside_EXTERNAL", "Inside_SUSPEND", "Inside_OVERRIDE", + "Inside_ABSTRACT", "Inside_FINAL", "Inside_OPEN", "Inside_CONST", "Inside_LATEINIT", + "Inside_VARARG", "Inside_NOINLINE", "Inside_CROSSINLINE", "Inside_REIFIED", + "Inside_BooleanLiteral", "Inside_IntegerLiteral", "Inside_HexLiteral", + "Inside_BinLiteral", "Inside_CharacterLiteral", "Inside_RealLiteral", + "Inside_NullLiteral", "Inside_LongLiteral", "Inside_Identifier", "Inside_LabelReference", + "Inside_LabelDefinition", "Inside_Comment", "Inside_WS", "Inside_NL", + "QUOTE_CLOSE", "LineStrRef", "LineStrText", "LineStrEscapedChar", "LineStrExprStart", + "TRIPLE_QUOTE_CLOSE", "MultiLineStringQuote", "MultiLineStrRef", "MultiLineStrText", + "MultiLineStrEscapedChar", "MultiLineStrExprStart", "MultiLineNL", "StrExpr_RCURL", + "StrExpr_LPAREN", "StrExpr_LSQUARE", "StrExpr_RPAREN", "StrExpr_RSQUARE", + "StrExpr_LCURL", "StrExpr_DOT", "StrExpr_COMMA", "StrExpr_MULT", "StrExpr_MOD", + "StrExpr_DIV", "StrExpr_ADD", "StrExpr_SUB", "StrExpr_INCR", "StrExpr_DECR", + "StrExpr_CONJ", "StrExpr_DISJ", "StrExpr_EXCL", "StrExpr_COLON", "StrExpr_SEMICOLON", + "StrExpr_ASSIGNMENT", "StrExpr_ADD_ASSIGNMENT", "StrExpr_SUB_ASSIGNMENT", + "StrExpr_MULT_ASSIGNMENT", "StrExpr_DIV_ASSIGNMENT", "StrExpr_MOD_ASSIGNMENT", + "StrExpr_ARROW", "StrExpr_DOUBLE_ARROW", "StrExpr_RANGE", "StrExpr_COLONCOLON", + "StrExpr_Q_COLONCOLON", "StrExpr_DOUBLE_SEMICOLON", "StrExpr_HASH", "StrExpr_AT", + "StrExpr_QUEST", "StrExpr_ELVIS", "StrExpr_LANGLE", "StrExpr_RANGLE", + "StrExpr_LE", "StrExpr_GE", "StrExpr_EXCL_EQ", "StrExpr_EXCL_EQEQ", "StrExpr_AS", + "StrExpr_IS", "StrExpr_IN", "StrExpr_NOT_IS", "StrExpr_NOT_IN", "StrExpr_AS_SAFE", + "StrExpr_EQEQ", "StrExpr_EQEQEQ", "StrExpr_SINGLE_QUOTE", "StrExpr_QUOTE_OPEN", + "StrExpr_TRIPLE_QUOTE_OPEN", "StrExpr_BooleanLiteral", "StrExpr_IntegerLiteral", + "StrExpr_HexLiteral", "StrExpr_BinLiteral", "StrExpr_CharacterLiteral", + "StrExpr_RealLiteral", "StrExpr_NullLiteral", "StrExpr_LongLiteral", + "StrExpr_Identifier", "StrExpr_LabelReference", "StrExpr_LabelDefinition", + "StrExpr_Comment", "StrExpr_WS", "StrExpr_NL" + }; + } + public static final String[] ruleNames = makeRuleNames(); + + private static String[] makeLiteralNames() { + return new String[] { + null, null, null, null, null, null, "'...'", "'.'", "','", "'('", null, + "'['", null, "'{'", "'}'", "'*'", "'%'", "'/'", "'+'", "'-'", "'++'", + "'--'", "'&&'", "'||'", "'!'", "':'", "';'", "'='", "'+='", "'-='", "'*='", + "'/='", "'%='", "'->'", "'=>'", "'..'", "'::'", "'?::'", "';;'", "'#'", + "'@'", "'?'", "'?:'", "'<'", "'>'", "'<='", "'>='", "'!='", "'!=='", + "'as?'", "'=='", "'==='", "'''", null, null, null, "'@file'", "'package'", + "'import'", "'class'", "'interface'", "'fun'", "'object'", "'val'", "'var'", + "'typealias'", "'constructor'", "'by'", "'companion'", "'init'", "'this'", + "'super'", "'typeof'", "'where'", "'if'", "'else'", "'when'", "'try'", + "'catch'", "'finally'", "'for'", "'do'", "'while'", "'throw'", "'return'", + "'continue'", "'break'", "'as'", "'is'", "'in'", null, null, "'out'", + "'@field'", "'@property'", "'@get'", "'@set'", "'get'", "'set'", "'@receiver'", + "'@param'", "'@setparam'", "'@delegate'", "'dynamic'", "'public'", "'private'", + "'protected'", "'internal'", "'enum'", "'sealed'", "'annotation'", "'data'", + "'inner'", "'tailrec'", "'operator'", "'inline'", "'infix'", "'external'", + "'suspend'", "'override'", "'abstract'", "'final'", "'open'", "'const'", + "'lateinit'", "'vararg'", "'noinline'", "'crossinline'", "'reified'", + null, "'\"\"\"'", null, null, null, null, null, null, null, null, "'null'" + }; + } + private static final String[] _LITERAL_NAMES = makeLiteralNames(); + private static String[] makeSymbolicNames() { + return new String[] { + null, "ShebangLine", "DelimitedComment", "LineComment", "WS", "NL", "RESERVED", + "DOT", "COMMA", "LPAREN", "RPAREN", "LSQUARE", "RSQUARE", "LCURL", "RCURL", + "MULT", "MOD", "DIV", "ADD", "SUB", "INCR", "DECR", "CONJ", "DISJ", "EXCL", + "COLON", "SEMICOLON", "ASSIGNMENT", "ADD_ASSIGNMENT", "SUB_ASSIGNMENT", + "MULT_ASSIGNMENT", "DIV_ASSIGNMENT", "MOD_ASSIGNMENT", "ARROW", "DOUBLE_ARROW", + "RANGE", "COLONCOLON", "Q_COLONCOLON", "DOUBLE_SEMICOLON", "HASH", "AT", + "QUEST", "ELVIS", "LANGLE", "RANGLE", "LE", "GE", "EXCL_EQ", "EXCL_EQEQ", + "AS_SAFE", "EQEQ", "EQEQEQ", "SINGLE_QUOTE", "RETURN_AT", "CONTINUE_AT", + "BREAK_AT", "FILE", "PACKAGE", "IMPORT", "CLASS", "INTERFACE", "FUN", + "OBJECT", "VAL", "VAR", "TYPE_ALIAS", "CONSTRUCTOR", "BY", "COMPANION", + "INIT", "THIS", "SUPER", "TYPEOF", "WHERE", "IF", "ELSE", "WHEN", "TRY", + "CATCH", "FINALLY", "FOR", "DO", "WHILE", "THROW", "RETURN", "CONTINUE", + "BREAK", "AS", "IS", "IN", "NOT_IS", "NOT_IN", "OUT", "FIELD", "PROPERTY", + "GET", "SET", "GETTER", "SETTER", "RECEIVER", "PARAM", "SETPARAM", "DELEGATE", + "DYNAMIC", "PUBLIC", "PRIVATE", "PROTECTED", "INTERNAL", "ENUM", "SEALED", + "ANNOTATION", "DATA", "INNER", "TAILREC", "OPERATOR", "INLINE", "INFIX", + "EXTERNAL", "SUSPEND", "OVERRIDE", "ABSTRACT", "FINAL", "OPEN", "CONST", + "LATEINIT", "VARARG", "NOINLINE", "CROSSINLINE", "REIFIED", "QUOTE_OPEN", + "TRIPLE_QUOTE_OPEN", "RealLiteral", "FloatLiteral", "DoubleLiteral", + "LongLiteral", "IntegerLiteral", "HexLiteral", "BinLiteral", "BooleanLiteral", + "NullLiteral", "Identifier", "LabelReference", "LabelDefinition", "FieldIdentifier", + "CharacterLiteral", "UNICODE_CLASS_LL", "UNICODE_CLASS_LM", "UNICODE_CLASS_LO", + "UNICODE_CLASS_LT", "UNICODE_CLASS_LU", "UNICODE_CLASS_ND", "UNICODE_CLASS_NL", + "Inside_Comment", "Inside_WS", "Inside_NL", "QUOTE_CLOSE", "LineStrRef", + "LineStrText", "LineStrEscapedChar", "LineStrExprStart", "TRIPLE_QUOTE_CLOSE", + "MultiLineStringQuote", "MultiLineStrRef", "MultiLineStrText", "MultiLineStrEscapedChar", + "MultiLineStrExprStart", "MultiLineNL", "StrExpr_IN", "StrExpr_Comment", + "StrExpr_WS", "StrExpr_NL" + }; + } + private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + static { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) { + tokenNames[i] = ""; + } + } + } + + @Override + @Deprecated + public String[] getTokenNames() { + return tokenNames; + } + + @Override + + public Vocabulary getVocabulary() { + return VOCABULARY; + } + + + public KotlinLexer(CharStream input) { + super(input); + _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + + @Override + public String getGrammarFileName() { return "KotlinLexer.g4"; } + + @Override + public String[] getRuleNames() { return ruleNames; } + + @Override + public String getSerializedATN() { return _serializedATN; } + + @Override + public String[] getChannelNames() { return channelNames; } + + @Override + public String[] getModeNames() { return modeNames; } + + @Override + public ATN getATN() { return _ATN; } + + private static final int _serializedATNSegments = 2; + private static final String _serializedATNSegment0 = + "\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2\u00ac\u0a30\b\1\b"+ + "\1\b\1\b\1\b\1\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b"+ + "\4\t\t\t\4\n\t\n\4\13\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t"+ + "\20\4\21\t\21\4\22\t\22\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t"+ + "\27\4\30\t\30\4\31\t\31\4\32\t\32\4\33\t\33\4\34\t\34\4\35\t\35\4\36\t"+ + "\36\4\37\t\37\4 \t \4!\t!\4\"\t\"\4#\t#\4$\t$\4%\t%\4&\t&\4\'\t\'\4(\t"+ + "(\4)\t)\4*\t*\4+\t+\4,\t,\4-\t-\4.\t.\4/\t/\4\60\t\60\4\61\t\61\4\62\t"+ + "\62\4\63\t\63\4\64\t\64\4\65\t\65\4\66\t\66\4\67\t\67\48\t8\49\t9\4:\t"+ + ":\4;\t;\4<\t<\4=\t=\4>\t>\4?\t?\4@\t@\4A\tA\4B\tB\4C\tC\4D\tD\4E\tE\4"+ + "F\tF\4G\tG\4H\tH\4I\tI\4J\tJ\4K\tK\4L\tL\4M\tM\4N\tN\4O\tO\4P\tP\4Q\t"+ + "Q\4R\tR\4S\tS\4T\tT\4U\tU\4V\tV\4W\tW\4X\tX\4Y\tY\4Z\tZ\4[\t[\4\\\t\\"+ + "\4]\t]\4^\t^\4_\t_\4`\t`\4a\ta\4b\tb\4c\tc\4d\td\4e\te\4f\tf\4g\tg\4h"+ + "\th\4i\ti\4j\tj\4k\tk\4l\tl\4m\tm\4n\tn\4o\to\4p\tp\4q\tq\4r\tr\4s\ts"+ + "\4t\tt\4u\tu\4v\tv\4w\tw\4x\tx\4y\ty\4z\tz\4{\t{\4|\t|\4}\t}\4~\t~\4\177"+ + "\t\177\4\u0080\t\u0080\4\u0081\t\u0081\4\u0082\t\u0082\4\u0083\t\u0083"+ + "\4\u0084\t\u0084\4\u0085\t\u0085\4\u0086\t\u0086\4\u0087\t\u0087\4\u0088"+ + "\t\u0088\4\u0089\t\u0089\4\u008a\t\u008a\4\u008b\t\u008b\4\u008c\t\u008c"+ + "\4\u008d\t\u008d\4\u008e\t\u008e\4\u008f\t\u008f\4\u0090\t\u0090\4\u0091"+ + "\t\u0091\4\u0092\t\u0092\4\u0093\t\u0093\4\u0094\t\u0094\4\u0095\t\u0095"+ + "\4\u0096\t\u0096\4\u0097\t\u0097\4\u0098\t\u0098\4\u0099\t\u0099\4\u009a"+ + "\t\u009a\4\u009b\t\u009b\4\u009c\t\u009c\4\u009d\t\u009d\4\u009e\t\u009e"+ + "\4\u009f\t\u009f\4\u00a0\t\u00a0\4\u00a1\t\u00a1\4\u00a2\t\u00a2\4\u00a3"+ + "\t\u00a3\4\u00a4\t\u00a4\4\u00a5\t\u00a5\4\u00a6\t\u00a6\4\u00a7\t\u00a7"+ + "\4\u00a8\t\u00a8\4\u00a9\t\u00a9\4\u00aa\t\u00aa\4\u00ab\t\u00ab\4\u00ac"+ + "\t\u00ac\4\u00ad\t\u00ad\4\u00ae\t\u00ae\4\u00af\t\u00af\4\u00b0\t\u00b0"+ + "\4\u00b1\t\u00b1\4\u00b2\t\u00b2\4\u00b3\t\u00b3\4\u00b4\t\u00b4\4\u00b5"+ + "\t\u00b5\4\u00b6\t\u00b6\4\u00b7\t\u00b7\4\u00b8\t\u00b8\4\u00b9\t\u00b9"+ + "\4\u00ba\t\u00ba\4\u00bb\t\u00bb\4\u00bc\t\u00bc\4\u00bd\t\u00bd\4\u00be"+ + "\t\u00be\4\u00bf\t\u00bf\4\u00c0\t\u00c0\4\u00c1\t\u00c1\4\u00c2\t\u00c2"+ + "\4\u00c3\t\u00c3\4\u00c4\t\u00c4\4\u00c5\t\u00c5\4\u00c6\t\u00c6\4\u00c7"+ + "\t\u00c7\4\u00c8\t\u00c8\4\u00c9\t\u00c9\4\u00ca\t\u00ca\4\u00cb\t\u00cb"+ + "\4\u00cc\t\u00cc\4\u00cd\t\u00cd\4\u00ce\t\u00ce\4\u00cf\t\u00cf\4\u00d0"+ + "\t\u00d0\4\u00d1\t\u00d1\4\u00d2\t\u00d2\4\u00d3\t\u00d3\4\u00d4\t\u00d4"+ + "\4\u00d5\t\u00d5\4\u00d6\t\u00d6\4\u00d7\t\u00d7\4\u00d8\t\u00d8\4\u00d9"+ + "\t\u00d9\4\u00da\t\u00da\4\u00db\t\u00db\4\u00dc\t\u00dc\4\u00dd\t\u00dd"+ + "\4\u00de\t\u00de\4\u00df\t\u00df\4\u00e0\t\u00e0\4\u00e1\t\u00e1\4\u00e2"+ + "\t\u00e2\4\u00e3\t\u00e3\4\u00e4\t\u00e4\4\u00e5\t\u00e5\4\u00e6\t\u00e6"+ + "\4\u00e7\t\u00e7\4\u00e8\t\u00e8\4\u00e9\t\u00e9\4\u00ea\t\u00ea\4\u00eb"+ + "\t\u00eb\4\u00ec\t\u00ec\4\u00ed\t\u00ed\4\u00ee\t\u00ee\4\u00ef\t\u00ef"+ + "\4\u00f0\t\u00f0\4\u00f1\t\u00f1\4\u00f2\t\u00f2\4\u00f3\t\u00f3\4\u00f4"+ + "\t\u00f4\4\u00f5\t\u00f5\4\u00f6\t\u00f6\4\u00f7\t\u00f7\4\u00f8\t\u00f8"+ + "\4\u00f9\t\u00f9\4\u00fa\t\u00fa\4\u00fb\t\u00fb\4\u00fc\t\u00fc\4\u00fd"+ + "\t\u00fd\4\u00fe\t\u00fe\4\u00ff\t\u00ff\4\u0100\t\u0100\4\u0101\t\u0101"+ + "\4\u0102\t\u0102\4\u0103\t\u0103\4\u0104\t\u0104\4\u0105\t\u0105\4\u0106"+ + "\t\u0106\4\u0107\t\u0107\4\u0108\t\u0108\4\u0109\t\u0109\4\u010a\t\u010a"+ + "\4\u010b\t\u010b\4\u010c\t\u010c\4\u010d\t\u010d\4\u010e\t\u010e\4\u010f"+ + "\t\u010f\4\u0110\t\u0110\4\u0111\t\u0111\4\u0112\t\u0112\4\u0113\t\u0113"+ + "\4\u0114\t\u0114\4\u0115\t\u0115\4\u0116\t\u0116\4\u0117\t\u0117\4\u0118"+ + "\t\u0118\4\u0119\t\u0119\4\u011a\t\u011a\4\u011b\t\u011b\4\u011c\t\u011c"+ + "\4\u011d\t\u011d\4\u011e\t\u011e\4\u011f\t\u011f\4\u0120\t\u0120\4\u0121"+ + "\t\u0121\4\u0122\t\u0122\4\u0123\t\u0123\4\u0124\t\u0124\4\u0125\t\u0125"+ + "\4\u0126\t\u0126\4\u0127\t\u0127\4\u0128\t\u0128\4\u0129\t\u0129\4\u012a"+ + "\t\u012a\4\u012b\t\u012b\4\u012c\t\u012c\4\u012d\t\u012d\4\u012e\t\u012e"+ + "\4\u012f\t\u012f\4\u0130\t\u0130\4\u0131\t\u0131\4\u0132\t\u0132\4\u0133"+ + "\t\u0133\4\u0134\t\u0134\4\u0135\t\u0135\4\u0136\t\u0136\4\u0137\t\u0137"+ + "\4\u0138\t\u0138\4\u0139\t\u0139\4\u013a\t\u013a\4\u013b\t\u013b\4\u013c"+ + "\t\u013c\4\u013d\t\u013d\4\u013e\t\u013e\4\u013f\t\u013f\4\u0140\t\u0140"+ + "\4\u0141\t\u0141\4\u0142\t\u0142\4\u0143\t\u0143\4\u0144\t\u0144\4\u0145"+ + "\t\u0145\4\u0146\t\u0146\4\u0147\t\u0147\4\u0148\t\u0148\4\u0149\t\u0149"+ + "\4\u014a\t\u014a\4\u014b\t\u014b\4\u014c\t\u014c\4\u014d\t\u014d\4\u014e"+ + "\t\u014e\4\u014f\t\u014f\4\u0150\t\u0150\4\u0151\t\u0151\4\u0152\t\u0152"+ + "\4\u0153\t\u0153\4\u0154\t\u0154\4\u0155\t\u0155\4\u0156\t\u0156\4\u0157"+ + "\t\u0157\4\u0158\t\u0158\4\u0159\t\u0159\4\u015a\t\u015a\4\u015b\t\u015b"+ + "\4\u015c\t\u015c\4\u015d\t\u015d\4\u015e\t\u015e\4\u015f\t\u015f\4\u0160"+ + "\t\u0160\4\u0161\t\u0161\4\u0162\t\u0162\4\u0163\t\u0163\4\u0164\t\u0164"+ + "\4\u0165\t\u0165\4\u0166\t\u0166\4\u0167\t\u0167\4\u0168\t\u0168\4\u0169"+ + "\t\u0169\3\2\3\2\3\2\3\2\7\2\u02dc\n\2\f\2\16\2\u02df\13\2\3\2\3\2\3\3"+ + "\3\3\3\3\3\3\3\3\7\3\u02e8\n\3\f\3\16\3\u02eb\13\3\3\3\3\3\3\3\3\3\3\3"+ + "\3\4\3\4\3\4\3\4\7\4\u02f6\n\4\f\4\16\4\u02f9\13\4\3\4\3\4\3\5\3\5\3\5"+ + "\3\5\3\6\3\6\3\6\5\6\u0304\n\6\3\7\3\7\3\7\3\7\3\b\3\b\3\t\3\t\3\n\3\n"+ + "\3\n\3\n\3\13\3\13\3\f\3\f\3\f\3\f\3\r\3\r\3\16\3\16\3\17\3\17\3\20\3"+ + "\20\3\21\3\21\3\22\3\22\3\23\3\23\3\24\3\24\3\25\3\25\3\25\3\26\3\26\3"+ + "\26\3\27\3\27\3\27\3\30\3\30\3\30\3\31\3\31\3\32\3\32\3\33\3\33\3\34\3"+ + "\34\3\35\3\35\3\35\3\36\3\36\3\36\3\37\3\37\3\37\3 \3 \3 \3!\3!\3!\3\""+ + "\3\"\3\"\3#\3#\3#\3$\3$\3$\3%\3%\3%\3&\3&\3&\3&\3\'\3\'\3\'\3(\3(\3)\3"+ + ")\3*\3*\3+\3+\3+\3,\3,\3-\3-\3.\3.\3.\3/\3/\3/\3\60\3\60\3\60\3\61\3\61"+ + "\3\61\3\61\3\62\3\62\3\62\3\62\3\63\3\63\3\63\3\64\3\64\3\64\3\64\3\65"+ + "\3\65\3\66\3\66\3\66\3\66\3\66\3\66\3\66\3\66\3\66\3\66\3\67\3\67\3\67"+ + "\3\67\3\67\3\67\3\67\3\67\3\67\3\67\3\67\3\67\38\38\38\38\38\38\38\38"+ + "\38\39\39\39\39\39\39\3:\3:\3:\3:\3:\3:\3:\3:\3;\3;\3;\3;\3;\3;\3;\3<"+ + "\3<\3<\3<\3<\3<\3=\3=\3=\3=\3=\3=\3=\3=\3=\3=\3>\3>\3>\3>\3?\3?\3?\3?"+ + "\3?\3?\3?\3@\3@\3@\3@\3A\3A\3A\3A\3B\3B\3B\3B\3B\3B\3B\3B\3B\3B\3C\3C"+ + "\3C\3C\3C\3C\3C\3C\3C\3C\3C\3C\3D\3D\3D\3E\3E\3E\3E\3E\3E\3E\3E\3E\3E"+ + "\3F\3F\3F\3F\3F\3G\3G\3G\3G\3G\3H\3H\3H\3H\3H\3H\3I\3I\3I\3I\3I\3I\3I"+ + "\3J\3J\3J\3J\3J\3J\3K\3K\3K\3L\3L\3L\3L\3L\3M\3M\3M\3M\3M\3N\3N\3N\3N"+ + "\3O\3O\3O\3O\3O\3O\3P\3P\3P\3P\3P\3P\3P\3P\3Q\3Q\3Q\3Q\3R\3R\3R\3S\3S"+ + "\3S\3S\3S\3S\3T\3T\3T\3T\3T\3T\3U\3U\3U\3U\3U\3U\3U\3V\3V\3V\3V\3V\3V"+ + "\3V\3V\3V\3W\3W\3W\3W\3W\3W\3X\3X\3X\3Y\3Y\3Y\3Z\3Z\3Z\3[\3[\3[\3[\3["+ + "\3[\6[\u0473\n[\r[\16[\u0474\3\\\3\\\3\\\3\\\3\\\3\\\6\\\u047d\n\\\r\\"+ + "\16\\\u047e\3]\3]\3]\3]\3^\3^\3^\3^\3^\3^\3^\3_\3_\3_\3_\3_\3_\3_\3_\3"+ + "_\3_\3`\3`\3`\3`\3`\3a\3a\3a\3a\3a\3b\3b\3b\3b\3c\3c\3c\3c\3d\3d\3d\3"+ + "d\3d\3d\3d\3d\3d\3d\3e\3e\3e\3e\3e\3e\3e\3f\3f\3f\3f\3f\3f\3f\3f\3f\3"+ + "f\3g\3g\3g\3g\3g\3g\3g\3g\3g\3g\3h\3h\3h\3h\3h\3h\3h\3h\3i\3i\3i\3i\3"+ + "i\3i\3i\3j\3j\3j\3j\3j\3j\3j\3j\3k\3k\3k\3k\3k\3k\3k\3k\3k\3k\3l\3l\3"+ + "l\3l\3l\3l\3l\3l\3l\3m\3m\3m\3m\3m\3n\3n\3n\3n\3n\3n\3n\3o\3o\3o\3o\3"+ + "o\3o\3o\3o\3o\3o\3o\3p\3p\3p\3p\3p\3q\3q\3q\3q\3q\3q\3r\3r\3r\3r\3r\3"+ + "r\3r\3r\3s\3s\3s\3s\3s\3s\3s\3s\3s\3t\3t\3t\3t\3t\3t\3t\3u\3u\3u\3u\3"+ + "u\3u\3v\3v\3v\3v\3v\3v\3v\3v\3v\3w\3w\3w\3w\3w\3w\3w\3w\3x\3x\3x\3x\3"+ + "x\3x\3x\3x\3x\3y\3y\3y\3y\3y\3y\3y\3y\3y\3z\3z\3z\3z\3z\3z\3{\3{\3{\3"+ + "{\3{\3|\3|\3|\3|\3|\3|\3}\3}\3}\3}\3}\3}\3}\3}\3}\3~\3~\3~\3~\3~\3~\3"+ + "~\3\177\3\177\3\177\3\177\3\177\3\177\3\177\3\177\3\177\3\u0080\3\u0080"+ + "\3\u0080\3\u0080\3\u0080\3\u0080\3\u0080\3\u0080\3\u0080\3\u0080\3\u0080"+ + "\3\u0080\3\u0081\3\u0081\3\u0081\3\u0081\3\u0081\3\u0081\3\u0081\3\u0081"+ + "\3\u0082\3\u0082\3\u0082\3\u0082\3\u0083\3\u0083\3\u0083\3\u0083\3\u0083"+ + "\3\u0083\3\u0084\3\u0084\5\u0084\u05a4\n\u0084\3\u0085\3\u0085\5\u0085"+ + "\u05a8\n\u0085\3\u0085\3\u0085\3\u0086\3\u0086\7\u0086\u05ae\n\u0086\f"+ + "\u0086\16\u0086\u05b1\13\u0086\3\u0086\5\u0086\u05b4\n\u0086\3\u0086\3"+ + "\u0086\3\u0086\3\u0086\7\u0086\u05ba\n\u0086\f\u0086\16\u0086\u05bd\13"+ + "\u0086\3\u0086\3\u0086\5\u0086\u05c1\n\u0086\3\u0086\5\u0086\u05c4\n\u0086"+ + "\3\u0086\6\u0086\u05c7\n\u0086\r\u0086\16\u0086\u05c8\3\u0086\3\u0086"+ + "\3\u0086\6\u0086\u05ce\n\u0086\r\u0086\16\u0086\u05cf\3\u0086\3\u0086"+ + "\3\u0086\6\u0086\u05d5\n\u0086\r\u0086\16\u0086\u05d6\3\u0086\3\u0086"+ + "\5\u0086\u05db\n\u0086\3\u0086\6\u0086\u05de\n\u0086\r\u0086\16\u0086"+ + "\u05df\3\u0086\6\u0086\u05e3\n\u0086\r\u0086\16\u0086\u05e4\3\u0086\3"+ + "\u0086\5\u0086\u05e9\n\u0086\3\u0086\3\u0086\3\u0086\6\u0086\u05ee\n\u0086"+ + "\r\u0086\16\u0086\u05ef\3\u0086\3\u0086\3\u0086\3\u0086\3\u0086\6\u0086"+ + "\u05f7\n\u0086\r\u0086\16\u0086\u05f8\3\u0086\3\u0086\3\u0086\5\u0086"+ + "\u05fe\n\u0086\3\u0086\6\u0086\u0601\n\u0086\r\u0086\16\u0086\u0602\3"+ + "\u0086\3\u0086\3\u0086\6\u0086\u0608\n\u0086\r\u0086\16\u0086\u0609\3"+ + "\u0086\3\u0086\3\u0086\5\u0086\u060f\n\u0086\3\u0086\3\u0086\3\u0086\6"+ + "\u0086\u0614\n\u0086\r\u0086\16\u0086\u0615\3\u0086\3\u0086\5\u0086\u061a"+ + "\n\u0086\3\u0087\3\u0087\3\u0087\5\u0087\u061f\n\u0087\3\u0087\3\u0087"+ + "\3\u0088\3\u0088\3\u0088\7\u0088\u0626\n\u0088\f\u0088\16\u0088\u0629"+ + "\13\u0088\3\u0088\3\u0088\3\u0088\6\u0088\u062e\n\u0088\r\u0088\16\u0088"+ + "\u062f\3\u0088\3\u0088\3\u0088\3\u0088\7\u0088\u0636\n\u0088\f\u0088\16"+ + "\u0088\u0639\13\u0088\3\u0088\3\u0088\5\u0088\u063d\n\u0088\3\u0088\6"+ + "\u0088\u0640\n\u0088\r\u0088\16\u0088\u0641\3\u0088\3\u0088\7\u0088\u0646"+ + "\n\u0088\f\u0088\16\u0088\u0649\13\u0088\3\u0088\3\u0088\5\u0088\u064d"+ + "\n\u0088\3\u0088\3\u0088\3\u0088\6\u0088\u0652\n\u0088\r\u0088\16\u0088"+ + "\u0653\3\u0088\3\u0088\3\u0088\3\u0088\3\u0088\6\u0088\u065b\n\u0088\r"+ + "\u0088\16\u0088\u065c\3\u0088\3\u0088\3\u0088\5\u0088\u0662\n\u0088\3"+ + "\u0088\6\u0088\u0665\n\u0088\r\u0088\16\u0088\u0666\3\u0088\3\u0088\3"+ + "\u0088\6\u0088\u066c\n\u0088\r\u0088\16\u0088\u066d\3\u0088\3\u0088\3"+ + "\u0088\5\u0088\u0673\n\u0088\3\u0088\3\u0088\3\u0088\6\u0088\u0678\n\u0088"+ + "\r\u0088\16\u0088\u0679\3\u0088\3\u0088\5\u0088\u067e\n\u0088\3\u0089"+ + "\3\u0089\3\u008a\3\u008a\3\u008b\3\u008b\3\u008c\3\u008c\3\u008c\3\u008c"+ + "\3\u008c\7\u008c\u068b\n\u008c\f\u008c\16\u008c\u068e\13\u008c\3\u008d"+ + "\3\u008d\3\u008e\3\u008e\3\u008e\3\u008e\3\u008e\7\u008e\u0697\n\u008e"+ + "\f\u008e\16\u008e\u069a\13\u008e\3\u008f\3\u008f\3\u0090\3\u0090\3\u0090"+ + "\3\u0090\3\u0090\3\u0090\3\u0090\3\u0090\3\u0090\5\u0090\u06a7\n\u0090"+ + "\3\u0091\3\u0091\3\u0091\3\u0091\3\u0091\3\u0092\3\u0092\5\u0092\u06b0"+ + "\n\u0092\3\u0092\3\u0092\3\u0092\7\u0092\u06b5\n\u0092\f\u0092\16\u0092"+ + "\u06b8\13\u0092\3\u0092\3\u0092\6\u0092\u06bc\n\u0092\r\u0092\16\u0092"+ + "\u06bd\3\u0092\5\u0092\u06c1\n\u0092\3\u0093\3\u0093\3\u0093\3\u0094\3"+ + "\u0094\3\u0094\3\u0095\3\u0095\3\u0095\3\u0096\3\u0096\3\u0096\5\u0096"+ + "\u06cf\n\u0096\3\u0096\3\u0096\3\u0097\3\u0097\5\u0097\u06d5\n\u0097\3"+ + "\u0098\3\u0098\3\u0098\3\u0098\3\u0098\3\u0098\3\u0098\3\u0099\3\u0099"+ + "\3\u0099\3\u009a\3\u009a\3\u009a\3\u009a\3\u009a\3\u009a\5\u009a\u06e7"+ + "\n\u009a\3\u009b\3\u009b\3\u009c\3\u009c\3\u009d\3\u009d\3\u009e\3\u009e"+ + "\3\u009f\3\u009f\3\u00a0\3\u00a0\3\u00a1\3\u00a1\3\u00a2\3\u00a2\3\u00a2"+ + "\3\u00a2\3\u00a2\3\u00a3\3\u00a3\3\u00a3\3\u00a3\3\u00a3\3\u00a4\3\u00a4"+ + "\3\u00a4\3\u00a4\3\u00a4\3\u00a5\3\u00a5\3\u00a5\3\u00a5\3\u00a5\3\u00a6"+ + "\3\u00a6\3\u00a6\3\u00a6\3\u00a7\3\u00a7\3\u00a7\3\u00a7\3\u00a8\3\u00a8"+ + "\3\u00a8\3\u00a8\3\u00a9\3\u00a9\3\u00a9\3\u00a9\3\u00aa\3\u00aa\3\u00aa"+ + "\3\u00aa\3\u00ab\3\u00ab\3\u00ab\3\u00ab\3\u00ac\3\u00ac\3\u00ac\3\u00ac"+ + "\3\u00ad\3\u00ad\3\u00ad\3\u00ad\3\u00ae\3\u00ae\3\u00ae\3\u00ae\3\u00af"+ + "\3\u00af\3\u00af\3\u00af\3\u00b0\3\u00b0\3\u00b0\3\u00b0\3\u00b1\3\u00b1"+ + "\3\u00b1\3\u00b1\3\u00b2\3\u00b2\3\u00b2\3\u00b2\3\u00b3\3\u00b3\3\u00b3"+ + "\3\u00b3\3\u00b4\3\u00b4\3\u00b4\3\u00b4\3\u00b5\3\u00b5\3\u00b5\3\u00b5"+ + "\3\u00b6\3\u00b6\3\u00b6\3\u00b6\3\u00b7\3\u00b7\3\u00b7\3\u00b7\3\u00b8"+ + "\3\u00b8\3\u00b8\3\u00b8\3\u00b9\3\u00b9\3\u00b9\3\u00b9\3\u00ba\3\u00ba"+ + "\3\u00ba\3\u00ba\3\u00bb\3\u00bb\3\u00bb\3\u00bb\3\u00bc\3\u00bc\3\u00bc"+ + "\3\u00bc\3\u00bd\3\u00bd\3\u00bd\3\u00bd\3\u00be\3\u00be\3\u00be\3\u00be"+ + "\3\u00bf\3\u00bf\3\u00bf\3\u00bf\3\u00c0\3\u00c0\3\u00c0\3\u00c0\3\u00c1"+ + "\3\u00c1\3\u00c1\3\u00c1\3\u00c2\3\u00c2\3\u00c2\3\u00c2\3\u00c3\3\u00c3"+ + "\3\u00c3\3\u00c3\3\u00c4\3\u00c4\3\u00c4\3\u00c4\3\u00c5\3\u00c5\3\u00c5"+ + "\3\u00c5\3\u00c6\3\u00c6\3\u00c6\3\u00c6\3\u00c7\3\u00c7\3\u00c7\3\u00c7"+ + "\3\u00c8\3\u00c8\3\u00c8\3\u00c8\3\u00c9\3\u00c9\3\u00c9\3\u00c9\3\u00ca"+ + "\3\u00ca\3\u00ca\3\u00ca\3\u00cb\3\u00cb\3\u00cb\3\u00cb\3\u00cc\3\u00cc"+ + "\3\u00cc\3\u00cc\3\u00cd\3\u00cd\3\u00cd\3\u00cd\3\u00ce\3\u00ce\3\u00ce"+ + "\3\u00ce\3\u00cf\3\u00cf\3\u00cf\3\u00cf\3\u00d0\3\u00d0\3\u00d0\3\u00d0"+ + "\3\u00d1\3\u00d1\3\u00d1\3\u00d1\3\u00d2\3\u00d2\3\u00d2\3\u00d2\3\u00d3"+ + "\3\u00d3\3\u00d3\3\u00d3\3\u00d3\3\u00d4\3\u00d4\3\u00d4\3\u00d4\3\u00d4"+ + "\3\u00d5\3\u00d5\3\u00d5\3\u00d5\3\u00d6\3\u00d6\3\u00d6\3\u00d6\3\u00d7"+ + "\3\u00d7\3\u00d7\3\u00d7\3\u00d8\3\u00d8\3\u00d8\3\u00d8\3\u00d9\3\u00d9"+ + "\3\u00d9\3\u00d9\3\u00da\3\u00da\3\u00da\3\u00da\3\u00db\3\u00db\3\u00db"+ + "\3\u00db\3\u00dc\3\u00dc\3\u00dc\3\u00dc\3\u00dd\3\u00dd\3\u00dd\3\u00dd"+ + "\3\u00de\3\u00de\3\u00de\3\u00de\3\u00df\3\u00df\3\u00df\3\u00df\3\u00e0"+ + "\3\u00e0\3\u00e0\3\u00e0\3\u00e1\3\u00e1\3\u00e1\3\u00e1\3\u00e2\3\u00e2"+ + "\3\u00e2\3\u00e2\3\u00e3\3\u00e3\3\u00e3\3\u00e3\3\u00e4\3\u00e4\3\u00e4"+ + "\3\u00e4\3\u00e5\3\u00e5\3\u00e5\3\u00e5\3\u00e6\3\u00e6\3\u00e6\3\u00e6"+ + "\3\u00e7\3\u00e7\3\u00e7\3\u00e7\3\u00e8\3\u00e8\3\u00e8\3\u00e8\3\u00e9"+ + "\3\u00e9\3\u00e9\3\u00e9\3\u00ea\3\u00ea\3\u00ea\3\u00ea\3\u00eb\3\u00eb"+ + "\3\u00eb\3\u00eb\3\u00ec\3\u00ec\3\u00ec\3\u00ec\3\u00ed\3\u00ed\3\u00ed"+ + "\3\u00ed\3\u00ee\3\u00ee\3\u00ee\3\u00ee\3\u00ef\3\u00ef\3\u00ef\3\u00ef"+ + "\3\u00f0\3\u00f0\3\u00f0\3\u00f0\3\u00f1\3\u00f1\3\u00f1\3\u00f1\3\u00f2"+ + "\3\u00f2\3\u00f2\3\u00f2\3\u00f3\3\u00f3\3\u00f3\3\u00f3\3\u00f4\3\u00f4"+ + "\3\u00f4\3\u00f4\3\u00f5\3\u00f5\3\u00f5\3\u00f5\3\u00f6\3\u00f6\3\u00f6"+ + "\3\u00f6\3\u00f7\3\u00f7\3\u00f7\3\u00f7\3\u00f8\3\u00f8\3\u00f8\3\u00f8"+ + "\3\u00f9\3\u00f9\3\u00f9\3\u00f9\3\u00fa\3\u00fa\3\u00fa\3\u00fa\3\u00fb"+ + "\3\u00fb\3\u00fb\3\u00fb\3\u00fc\3\u00fc\3\u00fc\3\u00fc\3\u00fd\3\u00fd"+ + "\3\u00fd\3\u00fd\3\u00fe\3\u00fe\3\u00fe\3\u00fe\3\u00ff\3\u00ff\3\u00ff"+ + "\3\u00ff\3\u0100\3\u0100\3\u0100\3\u0100\3\u0101\3\u0101\3\u0101\3\u0101"+ + "\3\u0102\3\u0102\3\u0102\3\u0102\3\u0103\3\u0103\3\u0103\3\u0103\3\u0104"+ + "\3\u0104\3\u0104\3\u0104\3\u0105\3\u0105\3\u0105\3\u0105\3\u0106\3\u0106"+ + "\3\u0106\3\u0106\3\u0107\3\u0107\3\u0107\3\u0107\3\u0108\3\u0108\3\u0108"+ + "\3\u0108\3\u0109\3\u0109\3\u0109\3\u0109\3\u010a\3\u010a\3\u010a\3\u010a"+ + "\3\u010b\3\u010b\3\u010b\3\u010b\3\u010c\3\u010c\3\u010c\3\u010c\3\u010d"+ + "\3\u010d\3\u010d\3\u010d\3\u010e\3\u010e\3\u010e\3\u010e\3\u010f\3\u010f"+ + "\3\u010f\3\u010f\3\u0110\3\u0110\3\u0110\3\u0110\3\u0111\3\u0111\3\u0111"+ + "\3\u0111\3\u0112\3\u0112\3\u0112\3\u0112\3\u0113\3\u0113\3\u0113\3\u0113"+ + "\3\u0114\3\u0114\3\u0114\3\u0114\3\u0115\3\u0115\3\u0115\3\u0115\3\u0116"+ + "\3\u0116\3\u0116\3\u0116\3\u0117\3\u0117\3\u0117\3\u0117\3\u0118\3\u0118"+ + "\5\u0118\u08d7\n\u0118\3\u0118\3\u0118\3\u0119\3\u0119\3\u0119\3\u0119"+ + "\3\u011a\3\u011a\3\u011a\3\u011a\3\u011b\3\u011b\3\u011b\3\u011b\3\u011c"+ + "\3\u011c\3\u011d\6\u011d\u08ea\n\u011d\r\u011d\16\u011d\u08eb\3\u011d"+ + "\5\u011d\u08ef\n\u011d\3\u011e\3\u011e\3\u011e\5\u011e\u08f4\n\u011e\3"+ + "\u011f\3\u011f\3\u011f\3\u011f\3\u011f\3\u0120\5\u0120\u08fc\n\u0120\3"+ + "\u0120\3\u0120\3\u0120\3\u0120\3\u0120\3\u0120\3\u0121\6\u0121\u0905\n"+ + "\u0121\r\u0121\16\u0121\u0906\3\u0122\3\u0122\3\u0123\6\u0123\u090c\n"+ + "\u0123\r\u0123\16\u0123\u090d\3\u0123\5\u0123\u0911\n\u0123\3\u0124\3"+ + "\u0124\3\u0124\3\u0125\3\u0125\3\u0125\3\u0125\3\u0125\3\u0126\3\u0126"+ + "\3\u0126\3\u0126\3\u0127\3\u0127\3\u0127\3\u0127\3\u0127\3\u0128\3\u0128"+ + "\3\u0128\3\u0128\3\u0128\3\u0129\3\u0129\3\u0129\3\u0129\3\u0129\3\u012a"+ + "\3\u012a\3\u012a\3\u012a\3\u012b\3\u012b\3\u012b\3\u012b\3\u012c\3\u012c"+ + "\3\u012c\3\u012c\3\u012c\3\u012d\3\u012d\3\u012d\3\u012d\3\u012e\3\u012e"+ + "\3\u012e\3\u012e\3\u012f\3\u012f\3\u012f\3\u012f\3\u0130\3\u0130\3\u0130"+ + "\3\u0130\3\u0131\3\u0131\3\u0131\3\u0131\3\u0132\3\u0132\3\u0132\3\u0132"+ + "\3\u0133\3\u0133\3\u0133\3\u0133\3\u0134\3\u0134\3\u0134\3\u0134\3\u0135"+ + "\3\u0135\3\u0135\3\u0135\3\u0136\3\u0136\3\u0136\3\u0136\3\u0137\3\u0137"+ + "\3\u0137\3\u0137\3\u0138\3\u0138\3\u0138\3\u0138\3\u0139\3\u0139\3\u0139"+ + "\3\u0139\3\u013a\3\u013a\3\u013a\3\u013a\3\u013b\3\u013b\3\u013b\3\u013b"+ + "\3\u013c\3\u013c\3\u013c\3\u013c\3\u013d\3\u013d\3\u013d\3\u013d\3\u013e"+ + "\3\u013e\3\u013e\3\u013e\3\u013f\3\u013f\3\u013f\3\u013f\3\u0140\3\u0140"+ + "\3\u0140\3\u0140\3\u0141\3\u0141\3\u0141\3\u0141\3\u0142\3\u0142\3\u0142"+ + "\3\u0142\3\u0143\3\u0143\3\u0143\3\u0143\3\u0144\3\u0144\3\u0144\3\u0144"+ + "\3\u0145\3\u0145\3\u0145\3\u0145\3\u0146\3\u0146\3\u0146\3\u0146\3\u0147"+ + "\3\u0147\3\u0147\3\u0147\3\u0148\3\u0148\3\u0148\3\u0148\3\u0149\3\u0149"+ + "\3\u0149\3\u0149\3\u014a\3\u014a\3\u014a\3\u014a\3\u014b\3\u014b\3\u014b"+ + "\3\u014b\3\u014c\3\u014c\3\u014c\3\u014c\3\u014d\3\u014d\3\u014d\3\u014d"+ + "\3\u014e\3\u014e\3\u014e\3\u014e\3\u014f\3\u014f\3\u014f\3\u014f\3\u0150"+ + "\3\u0150\3\u0150\3\u0150\3\u0151\3\u0151\3\u0151\3\u0151\3\u0152\3\u0152"+ + "\3\u0152\3\u0152\3\u0153\3\u0153\3\u0154\3\u0154\3\u0154\3\u0154\3\u0155"+ + "\3\u0155\3\u0155\3\u0155\3\u0156\3\u0156\3\u0156\3\u0156\3\u0157\3\u0157"+ + "\3\u0157\3\u0157\3\u0158\3\u0158\3\u0158\3\u0158\3\u0159\3\u0159\3\u0159"+ + "\3\u0159\3\u015a\3\u015a\3\u015a\3\u015a\3\u015a\3\u015b\3\u015b\3\u015b"+ + "\3\u015b\3\u015b\3\u015c\3\u015c\3\u015c\3\u015c\3\u015d\3\u015d\3\u015d"+ + "\3\u015d\3\u015e\3\u015e\3\u015e\3\u015e\3\u015f\3\u015f\3\u015f\3\u015f"+ + "\3\u0160\3\u0160\3\u0160\3\u0160\3\u0161\3\u0161\3\u0161\3\u0161\3\u0162"+ + "\3\u0162\3\u0162\3\u0162\3\u0163\3\u0163\3\u0163\3\u0163\3\u0164\3\u0164"+ + "\3\u0164\3\u0164\3\u0165\3\u0165\3\u0165\3\u0165\3\u0166\3\u0166\3\u0166"+ + "\3\u0166\3\u0167\3\u0167\5\u0167\u0a25\n\u0167\3\u0167\3\u0167\3\u0168"+ + "\3\u0168\3\u0168\3\u0168\3\u0169\3\u0169\3\u0169\3\u0169\3\u02e9\2\u016a"+ + "\7\3\t\4\13\5\r\6\17\7\21\b\23\t\25\n\27\13\31\f\33\r\35\16\37\17!\20"+ + "#\21%\22\'\23)\24+\25-\26/\27\61\30\63\31\65\32\67\339\34;\35=\36?\37"+ + "A C!E\"G#I$K%M&O\'Q(S)U*W+Y,[-]._/a\60c\61e\62g\63i\64k\65m\66o\67q8s"+ + "9u:w;y<{=}>\177?\u0081@\u0083A\u0085B\u0087C\u0089D\u008bE\u008dF\u008f"+ + "G\u0091H\u0093I\u0095J\u0097K\u0099L\u009bM\u009dN\u009fO\u00a1P\u00a3"+ + "Q\u00a5R\u00a7S\u00a9T\u00abU\u00adV\u00afW\u00b1X\u00b3Y\u00b5Z\u00b7"+ + "[\u00b9\\\u00bb]\u00bd^\u00bf_\u00c1`\u00c3a\u00c5b\u00c7c\u00c9d\u00cb"+ + "e\u00cdf\u00cfg\u00d1h\u00d3i\u00d5j\u00d7k\u00d9l\u00dbm\u00ddn\u00df"+ + "o\u00e1p\u00e3q\u00e5r\u00e7s\u00e9t\u00ebu\u00edv\u00efw\u00f1x\u00f3"+ + "y\u00f5z\u00f7{\u00f9|\u00fb}\u00fd~\u00ff\177\u0101\u0080\u0103\u0081"+ + "\u0105\u0082\u0107\u0083\u0109\u0084\u010b\u0085\u010d\u0086\u010f\u0087"+ + "\u0111\u0088\u0113\u0089\u0115\2\u0117\2\u0119\2\u011b\u008a\u011d\2\u011f"+ + "\u008b\u0121\2\u0123\u008c\u0125\u008d\u0127\u008e\u0129\u008f\u012b\u0090"+ + "\u012d\u0091\u012f\u0092\u0131\2\u0133\2\u0135\2\u0137\2\u0139\u0093\u013b"+ + "\u0094\u013d\u0095\u013f\u0096\u0141\u0097\u0143\u0098\u0145\u0099\u0147"+ + "\2\u0149\2\u014b\2\u014d\2\u014f\2\u0151\2\u0153\2\u0155\2\u0157\2\u0159"+ + "\2\u015b\2\u015d\2\u015f\2\u0161\2\u0163\2\u0165\2\u0167\2\u0169\2\u016b"+ + "\2\u016d\2\u016f\2\u0171\2\u0173\2\u0175\2\u0177\2\u0179\2\u017b\2\u017d"+ + "\2\u017f\2\u0181\2\u0183\2\u0185\2\u0187\2\u0189\2\u018b\2\u018d\2\u018f"+ + "\2\u0191\2\u0193\2\u0195\2\u0197\2\u0199\2\u019b\2\u019d\2\u019f\2\u01a1"+ + "\2\u01a3\2\u01a5\2\u01a7\2\u01a9\2\u01ab\2\u01ad\2\u01af\2\u01b1\2\u01b3"+ + "\2\u01b5\2\u01b7\2\u01b9\2\u01bb\2\u01bd\2\u01bf\2\u01c1\2\u01c3\2\u01c5"+ + "\2\u01c7\2\u01c9\2\u01cb\2\u01cd\2\u01cf\2\u01d1\2\u01d3\2\u01d5\2\u01d7"+ + "\2\u01d9\2\u01db\2\u01dd\2\u01df\2\u01e1\2\u01e3\2\u01e5\2\u01e7\2\u01e9"+ + "\2\u01eb\2\u01ed\2\u01ef\2\u01f1\2\u01f3\2\u01f5\2\u01f7\2\u01f9\2\u01fb"+ + "\2\u01fd\2\u01ff\2\u0201\2\u0203\2\u0205\2\u0207\2\u0209\2\u020b\2\u020d"+ + "\2\u020f\2\u0211\2\u0213\2\u0215\2\u0217\2\u0219\2\u021b\2\u021d\2\u021f"+ + "\2\u0221\2\u0223\2\u0225\2\u0227\2\u0229\2\u022b\2\u022d\2\u022f\2\u0231"+ + "\2\u0233\u009a\u0235\u009b\u0237\u009c\u0239\u009d\u023b\u009e\u023d\u009f"+ + "\u023f\u00a0\u0241\u00a1\u0243\u00a2\u0245\u00a3\u0247\u00a4\u0249\u00a5"+ + "\u024b\u00a6\u024d\u00a7\u024f\u00a8\u0251\2\u0253\2\u0255\2\u0257\2\u0259"+ + "\2\u025b\2\u025d\2\u025f\2\u0261\2\u0263\2\u0265\2\u0267\2\u0269\2\u026b"+ + "\2\u026d\2\u026f\2\u0271\2\u0273\2\u0275\2\u0277\2\u0279\2\u027b\2\u027d"+ + "\2\u027f\2\u0281\2\u0283\2\u0285\2\u0287\2\u0289\2\u028b\2\u028d\2\u028f"+ + "\2\u0291\2\u0293\2\u0295\2\u0297\2\u0299\2\u029b\2\u029d\2\u029f\2\u02a1"+ + "\2\u02a3\2\u02a5\2\u02a7\2\u02a9\u00a9\u02ab\2\u02ad\2\u02af\2\u02b1\2"+ + "\u02b3\2\u02b5\2\u02b7\2\u02b9\2\u02bb\2\u02bd\2\u02bf\2\u02c1\2\u02c3"+ + "\2\u02c5\2\u02c7\2\u02c9\2\u02cb\2\u02cd\2\u02cf\2\u02d1\u00aa\u02d3\u00ab"+ + "\u02d5\u00ac\7\2\3\4\5\6\26\4\2\f\f\17\17\5\2\13\13\16\16\"\"\4\2HHhh"+ + "\4\2GGgg\4\2--//\'\2\63;\u0663\u066b\u06f3\u06fb\u07c3\u07cb\u0969\u0971"+ + "\u09e9\u09f1\u0a69\u0a71\u0ae9\u0af1\u0b69\u0b71\u0be9\u0bf1\u0c69\u0c71"+ + "\u0ce9\u0cf1\u0d69\u0d71\u0de9\u0df1\u0e53\u0e5b\u0ed3\u0edb\u0f23\u0f2b"+ + "\u1043\u104b\u1093\u109b\u17e3\u17eb\u1813\u181b\u1949\u1951\u19d3\u19db"+ + "\u1a83\u1a8b\u1a93\u1a9b\u1b53\u1b5b\u1bb3\u1bbb\u1c43\u1c4b\u1c53\u1c5b"+ + "\ua623\ua62b\ua8d3\ua8db\ua903\ua90b\ua9d3\ua9db\ua9f3\ua9fb\uaa53\uaa5b"+ + "\uabf3\uabfb\uff13\uff1b\4\2ZZzz\5\2\62;CHch\4\2DDdd\3\2\62\63\3\2bb\n"+ + "\2$$&&))^^ddppttvv\u0248\2c|\u00b7\u00b7\u00e1\u00f8\u00fa\u0101\u0103"+ + "\u0103\u0105\u0105\u0107\u0107\u0109\u0109\u010b\u010b\u010d\u010d\u010f"+ + "\u010f\u0111\u0111\u0113\u0113\u0115\u0115\u0117\u0117\u0119\u0119\u011b"+ + "\u011b\u011d\u011d\u011f\u011f\u0121\u0121\u0123\u0123\u0125\u0125\u0127"+ + "\u0127\u0129\u0129\u012b\u012b\u012d\u012d\u012f\u012f\u0131\u0131\u0133"+ + "\u0133\u0135\u0135\u0137\u0137\u0139\u013a\u013c\u013c\u013e\u013e\u0140"+ + "\u0140\u0142\u0142\u0144\u0144\u0146\u0146\u0148\u0148\u014a\u014b\u014d"+ + "\u014d\u014f\u014f\u0151\u0151\u0153\u0153\u0155\u0155\u0157\u0157\u0159"+ + "\u0159\u015b\u015b\u015d\u015d\u015f\u015f\u0161\u0161\u0163\u0163\u0165"+ + "\u0165\u0167\u0167\u0169\u0169\u016b\u016b\u016d\u016d\u016f\u016f\u0171"+ + "\u0171\u0173\u0173\u0175\u0175\u0177\u0177\u0179\u0179\u017c\u017c\u017e"+ + "\u017e\u0180\u0182\u0185\u0185\u0187\u0187\u018a\u018a\u018e\u018f\u0194"+ + "\u0194\u0197\u0197\u019b\u019d\u01a0\u01a0\u01a3\u01a3\u01a5\u01a5\u01a7"+ + "\u01a7\u01aa\u01aa\u01ac\u01ad\u01af\u01af\u01b2\u01b2\u01b6\u01b6\u01b8"+ + "\u01b8\u01bb\u01bc\u01bf\u01c1\u01c8\u01c8\u01cb\u01cb\u01ce\u01ce\u01d0"+ + "\u01d0\u01d2\u01d2\u01d4\u01d4\u01d6\u01d6\u01d8\u01d8\u01da\u01da\u01dc"+ + "\u01dc\u01de\u01df\u01e1\u01e1\u01e3\u01e3\u01e5\u01e5\u01e7\u01e7\u01e9"+ + "\u01e9\u01eb\u01eb\u01ed\u01ed\u01ef\u01ef\u01f1\u01f2\u01f5\u01f5\u01f7"+ + "\u01f7\u01fb\u01fb\u01fd\u01fd\u01ff\u01ff\u0201\u0201\u0203\u0203\u0205"+ + "\u0205\u0207\u0207\u0209\u0209\u020b\u020b\u020d\u020d\u020f\u020f\u0211"+ + "\u0211\u0213\u0213\u0215\u0215\u0217\u0217\u0219\u0219\u021b\u021b\u021d"+ + "\u021d\u021f\u021f\u0221\u0221\u0223\u0223\u0225\u0225\u0227\u0227\u0229"+ + "\u0229\u022b\u022b\u022d\u022d\u022f\u022f\u0231\u0231\u0233\u0233\u0235"+ + "\u023b\u023e\u023e\u0241\u0242\u0244\u0244\u0249\u0249\u024b\u024b\u024d"+ + "\u024d\u024f\u024f\u0251\u0295\u0297\u02b1\u0373\u0373\u0375\u0375\u0379"+ + "\u0379\u037d\u037f\u0392\u0392\u03ae\u03d0\u03d2\u03d3\u03d7\u03d9\u03db"+ + "\u03db\u03dd\u03dd\u03df\u03df\u03e1\u03e1\u03e3\u03e3\u03e5\u03e5\u03e7"+ + "\u03e7\u03e9\u03e9\u03eb\u03eb\u03ed\u03ed\u03ef\u03ef\u03f1\u03f5\u03f7"+ + "\u03f7\u03fa\u03fa\u03fd\u03fe\u0432\u0461\u0463\u0463\u0465\u0465\u0467"+ + "\u0467\u0469\u0469\u046b\u046b\u046d\u046d\u046f\u046f\u0471\u0471\u0473"+ + "\u0473\u0475\u0475\u0477\u0477\u0479\u0479\u047b\u047b\u047d\u047d\u047f"+ + "\u047f\u0481\u0481\u0483\u0483\u048d\u048d\u048f\u048f\u0491\u0491\u0493"+ + "\u0493\u0495\u0495\u0497\u0497\u0499\u0499\u049b\u049b\u049d\u049d\u049f"+ + "\u049f\u04a1\u04a1\u04a3\u04a3\u04a5\u04a5\u04a7\u04a7\u04a9\u04a9\u04ab"+ + "\u04ab\u04ad\u04ad\u04af\u04af\u04b1\u04b1\u04b3\u04b3\u04b5\u04b5\u04b7"+ + "\u04b7\u04b9\u04b9\u04bb\u04bb\u04bd\u04bd\u04bf\u04bf\u04c1\u04c1\u04c4"+ + "\u04c4\u04c6\u04c6\u04c8\u04c8\u04ca\u04ca\u04cc\u04cc\u04ce\u04ce\u04d0"+ + "\u04d1\u04d3\u04d3\u04d5\u04d5\u04d7\u04d7\u04d9\u04d9\u04db\u04db\u04dd"+ + "\u04dd\u04df\u04df\u04e1\u04e1\u04e3\u04e3\u04e5\u04e5\u04e7\u04e7\u04e9"+ + "\u04e9\u04eb\u04eb\u04ed\u04ed\u04ef\u04ef\u04f1\u04f1\u04f3\u04f3\u04f5"+ + "\u04f5\u04f7\u04f7\u04f9\u04f9\u04fb\u04fb\u04fd\u04fd\u04ff\u04ff\u0501"+ + "\u0501\u0503\u0503\u0505\u0505\u0507\u0507\u0509\u0509\u050b\u050b\u050d"+ + "\u050d\u050f\u050f\u0511\u0511\u0513\u0513\u0515\u0515\u0517\u0517\u0519"+ + "\u0519\u051b\u051b\u051d\u051d\u051f\u051f\u0521\u0521\u0523\u0523\u0525"+ + "\u0525\u0527\u0527\u0529\u0529\u0563\u0589\u1d02\u1d2d\u1d6d\u1d79\u1d7b"+ + "\u1d9c\u1e03\u1e03\u1e05\u1e05\u1e07\u1e07\u1e09\u1e09\u1e0b\u1e0b\u1e0d"+ + "\u1e0d\u1e0f\u1e0f\u1e11\u1e11\u1e13\u1e13\u1e15\u1e15\u1e17\u1e17\u1e19"+ + "\u1e19\u1e1b\u1e1b\u1e1d\u1e1d\u1e1f\u1e1f\u1e21\u1e21\u1e23\u1e23\u1e25"+ + "\u1e25\u1e27\u1e27\u1e29\u1e29\u1e2b\u1e2b\u1e2d\u1e2d\u1e2f\u1e2f\u1e31"+ + "\u1e31\u1e33\u1e33\u1e35\u1e35\u1e37\u1e37\u1e39\u1e39\u1e3b\u1e3b\u1e3d"+ + "\u1e3d\u1e3f\u1e3f\u1e41\u1e41\u1e43\u1e43\u1e45\u1e45\u1e47\u1e47\u1e49"+ + "\u1e49\u1e4b\u1e4b\u1e4d\u1e4d\u1e4f\u1e4f\u1e51\u1e51\u1e53\u1e53\u1e55"+ + "\u1e55\u1e57\u1e57\u1e59\u1e59\u1e5b\u1e5b\u1e5d\u1e5d\u1e5f\u1e5f\u1e61"+ + "\u1e61\u1e63\u1e63\u1e65\u1e65\u1e67\u1e67\u1e69\u1e69\u1e6b\u1e6b\u1e6d"+ + "\u1e6d\u1e6f\u1e6f\u1e71\u1e71\u1e73\u1e73\u1e75\u1e75\u1e77\u1e77\u1e79"+ + "\u1e79\u1e7b\u1e7b\u1e7d\u1e7d\u1e7f\u1e7f\u1e81\u1e81\u1e83\u1e83\u1e85"+ + "\u1e85\u1e87\u1e87\u1e89\u1e89\u1e8b\u1e8b\u1e8d\u1e8d\u1e8f\u1e8f\u1e91"+ + "\u1e91\u1e93\u1e93\u1e95\u1e95\u1e97\u1e9f\u1ea1\u1ea1\u1ea3\u1ea3\u1ea5"+ + "\u1ea5\u1ea7\u1ea7\u1ea9\u1ea9\u1eab\u1eab\u1ead\u1ead\u1eaf\u1eaf\u1eb1"+ + "\u1eb1\u1eb3\u1eb3\u1eb5\u1eb5\u1eb7\u1eb7\u1eb9\u1eb9\u1ebb\u1ebb\u1ebd"+ + "\u1ebd\u1ebf\u1ebf\u1ec1\u1ec1\u1ec3\u1ec3\u1ec5\u1ec5\u1ec7\u1ec7\u1ec9"+ + "\u1ec9\u1ecb\u1ecb\u1ecd\u1ecd\u1ecf\u1ecf\u1ed1\u1ed1\u1ed3\u1ed3\u1ed5"+ + "\u1ed5\u1ed7\u1ed7\u1ed9\u1ed9\u1edb\u1edb\u1edd\u1edd\u1edf\u1edf\u1ee1"+ + "\u1ee1\u1ee3\u1ee3\u1ee5\u1ee5\u1ee7\u1ee7\u1ee9\u1ee9\u1eeb\u1eeb\u1eed"+ + "\u1eed\u1eef\u1eef\u1ef1\u1ef1\u1ef3\u1ef3\u1ef5\u1ef5\u1ef7\u1ef7\u1ef9"+ + "\u1ef9\u1efb\u1efb\u1efd\u1efd\u1eff\u1eff\u1f01\u1f09\u1f12\u1f17\u1f22"+ + "\u1f29\u1f32\u1f39\u1f42\u1f47\u1f52\u1f59\u1f62\u1f69\u1f72\u1f7f\u1f82"+ + "\u1f89\u1f92\u1f99\u1fa2\u1fa9\u1fb2\u1fb6\u1fb8\u1fb9\u1fc0\u1fc0\u1fc4"+ + "\u1fc6\u1fc8\u1fc9\u1fd2\u1fd5\u1fd8\u1fd9\u1fe2\u1fe9\u1ff4\u1ff6\u1ff8"+ + "\u1ff9\u210c\u210c\u2110\u2111\u2115\u2115\u2131\u2131\u2136\u2136\u213b"+ + "\u213b\u213e\u213f\u2148\u214b\u2150\u2150\u2186\u2186\u2c32\u2c60\u2c63"+ + "\u2c63\u2c67\u2c68\u2c6a\u2c6a\u2c6c\u2c6c\u2c6e\u2c6e\u2c73\u2c73\u2c75"+ + "\u2c76\u2c78\u2c7d\u2c83\u2c83\u2c85\u2c85\u2c87\u2c87\u2c89\u2c89\u2c8b"+ + "\u2c8b\u2c8d\u2c8d\u2c8f\u2c8f\u2c91\u2c91\u2c93\u2c93\u2c95\u2c95\u2c97"+ + "\u2c97\u2c99\u2c99\u2c9b\u2c9b\u2c9d\u2c9d\u2c9f\u2c9f\u2ca1\u2ca1\u2ca3"+ + "\u2ca3\u2ca5\u2ca5\u2ca7\u2ca7\u2ca9\u2ca9\u2cab\u2cab\u2cad\u2cad\u2caf"+ + "\u2caf\u2cb1\u2cb1\u2cb3\u2cb3\u2cb5\u2cb5\u2cb7\u2cb7\u2cb9\u2cb9\u2cbb"+ + "\u2cbb\u2cbd\u2cbd\u2cbf\u2cbf\u2cc1\u2cc1\u2cc3\u2cc3\u2cc5\u2cc5\u2cc7"+ + "\u2cc7\u2cc9\u2cc9\u2ccb\u2ccb\u2ccd\u2ccd\u2ccf\u2ccf\u2cd1\u2cd1\u2cd3"+ + "\u2cd3\u2cd5\u2cd5\u2cd7\u2cd7\u2cd9\u2cd9\u2cdb\u2cdb\u2cdd\u2cdd\u2cdf"+ + "\u2cdf\u2ce1\u2ce1\u2ce3\u2ce3\u2ce5\u2ce6\u2cee\u2cee\u2cf0\u2cf0\u2cf5"+ + "\u2cf5\u2d02\u2d27\u2d29\u2d29\u2d2f\u2d2f\ua643\ua643\ua645\ua645\ua647"+ + "\ua647\ua649\ua649\ua64b\ua64b\ua64d\ua64d\ua64f\ua64f\ua651\ua651\ua653"+ + "\ua653\ua655\ua655\ua657\ua657\ua659\ua659\ua65b\ua65b\ua65d\ua65d\ua65f"+ + "\ua65f\ua661\ua661\ua663\ua663\ua665\ua665\ua667\ua667\ua669\ua669\ua66b"+ + "\ua66b\ua66d\ua66d\ua66f\ua66f\ua683\ua683\ua685\ua685\ua687\ua687\ua689"+ + "\ua689\ua68b\ua68b\ua68d\ua68d\ua68f\ua68f\ua691\ua691\ua693\ua693\ua695"+ + "\ua695\ua697\ua697\ua699\ua699\ua725\ua725\ua727\ua727\ua729\ua729\ua72b"+ + "\ua72b\ua72d\ua72d\ua72f\ua72f\ua731\ua733\ua735\ua735\ua737\ua737\ua739"+ + "\ua739\ua73b\ua73b\ua73d\ua73d\ua73f\ua73f\ua741\ua741\ua743\ua743\ua745"+ + "\ua745\ua747\ua747\ua749\ua749\ua74b\ua74b\ua74d\ua74d\ua74f\ua74f\ua751"+ + "\ua751\ua753\ua753\ua755\ua755\ua757\ua757\ua759\ua759\ua75b\ua75b\ua75d"+ + "\ua75d\ua75f\ua75f\ua761\ua761\ua763\ua763\ua765\ua765\ua767\ua767\ua769"+ + "\ua769\ua76b\ua76b\ua76d\ua76d\ua76f\ua76f\ua771\ua771\ua773\ua77a\ua77c"+ + "\ua77c\ua77e\ua77e\ua781\ua781\ua783\ua783\ua785\ua785\ua787\ua787\ua789"+ + "\ua789\ua78e\ua78e\ua790\ua790\ua793\ua793\ua795\ua795\ua7a3\ua7a3\ua7a5"+ + "\ua7a5\ua7a7\ua7a7\ua7a9\ua7a9\ua7ab\ua7ab\ua7fc\ua7fc\ufb02\ufb08\ufb15"+ + "\ufb19\uff43\uff5c\65\2\u02b2\u02c3\u02c8\u02d3\u02e2\u02e6\u02ee\u02ee"+ + "\u02f0\u02f0\u0376\u0376\u037c\u037c\u055b\u055b\u0642\u0642\u06e7\u06e8"+ + "\u07f6\u07f7\u07fc\u07fc\u081c\u081c\u0826\u0826\u082a\u082a\u0973\u0973"+ + "\u0e48\u0e48\u0ec8\u0ec8\u10fe\u10fe\u17d9\u17d9\u1845\u1845\u1aa9\u1aa9"+ + "\u1c7a\u1c7f\u1d2e\u1d6c\u1d7a\u1d7a\u1d9d\u1dc1\u2073\u2073\u2081\u2081"+ + "\u2092\u209e\u2c7e\u2c7f\u2d71\u2d71\u2e31\u2e31\u3007\u3007\u3033\u3037"+ + "\u303d\u303d\u309f\u30a0\u30fe\u3100\ua017\ua017\ua4fa\ua4ff\ua60e\ua60e"+ + "\ua681\ua681\ua719\ua721\ua772\ua772\ua78a\ua78a\ua7fa\ua7fb\ua9d1\ua9d1"+ + "\uaa72\uaa72\uaadf\uaadf\uaaf5\uaaf6\uff72\uff72\uffa0\uffa1\u0123\2\u00ac"+ + "\u00ac\u00bc\u00bc\u01bd\u01bd\u01c2\u01c5\u0296\u0296\u05d2\u05ec\u05f2"+ + "\u05f4\u0622\u0641\u0643\u064c\u0670\u0671\u0673\u06d5\u06d7\u06d7\u06f0"+ + "\u06f1\u06fc\u06fe\u0701\u0701\u0712\u0712\u0714\u0731\u074f\u07a7\u07b3"+ + "\u07b3\u07cc\u07ec\u0802\u0817\u0842\u085a\u08a2\u08a2\u08a4\u08ae\u0906"+ + "\u093b\u093f\u093f\u0952\u0952\u095a\u0963\u0974\u0979\u097b\u0981\u0987"+ + "\u098e\u0991\u0992\u0995\u09aa\u09ac\u09b2\u09b4\u09b4\u09b8\u09bb\u09bf"+ + "\u09bf\u09d0\u09d0\u09de\u09df\u09e1\u09e3\u09f2\u09f3\u0a07\u0a0c\u0a11"+ + "\u0a12\u0a15\u0a2a\u0a2c\u0a32\u0a34\u0a35\u0a37\u0a38\u0a3a\u0a3b\u0a5b"+ + "\u0a5e\u0a60\u0a60\u0a74\u0a76\u0a87\u0a8f\u0a91\u0a93\u0a95\u0aaa\u0aac"+ + "\u0ab2\u0ab4\u0ab5\u0ab7\u0abb\u0abf\u0abf\u0ad2\u0ad2\u0ae2\u0ae3\u0b07"+ + "\u0b0e\u0b11\u0b12\u0b15\u0b2a\u0b2c\u0b32\u0b34\u0b35\u0b37\u0b3b\u0b3f"+ + "\u0b3f\u0b5e\u0b5f\u0b61\u0b63\u0b73\u0b73\u0b85\u0b85\u0b87\u0b8c\u0b90"+ + "\u0b92\u0b94\u0b97\u0b9b\u0b9c\u0b9e\u0b9e\u0ba0\u0ba1\u0ba5\u0ba6\u0baa"+ + "\u0bac\u0bb0\u0bbb\u0bd2\u0bd2\u0c07\u0c0e\u0c10\u0c12\u0c14\u0c2a\u0c2c"+ + "\u0c35\u0c37\u0c3b\u0c3f\u0c3f\u0c5a\u0c5b\u0c62\u0c63\u0c87\u0c8e\u0c90"+ + "\u0c92\u0c94\u0caa\u0cac\u0cb5\u0cb7\u0cbb\u0cbf\u0cbf\u0ce0\u0ce0\u0ce2"+ + "\u0ce3\u0cf3\u0cf4\u0d07\u0d0e\u0d10\u0d12\u0d14\u0d3c\u0d3f\u0d3f\u0d50"+ + "\u0d50\u0d62\u0d63\u0d7c\u0d81\u0d87\u0d98\u0d9c\u0db3\u0db5\u0dbd\u0dbf"+ + "\u0dbf\u0dc2\u0dc8\u0e03\u0e32\u0e34\u0e35\u0e42\u0e47\u0e83\u0e84\u0e86"+ + "\u0e86\u0e89\u0e8a\u0e8c\u0e8c\u0e8f\u0e8f\u0e96\u0e99\u0e9b\u0ea1\u0ea3"+ + "\u0ea5\u0ea7\u0ea7\u0ea9\u0ea9\u0eac\u0ead\u0eaf\u0eb2\u0eb4\u0eb5\u0ebf"+ + "\u0ebf\u0ec2\u0ec6\u0ede\u0ee1\u0f02\u0f02\u0f42\u0f49\u0f4b\u0f6e\u0f8a"+ + "\u0f8e\u1002\u102c\u1041\u1041\u1052\u1057\u105c\u105f\u1063\u1063\u1067"+ + "\u1068\u1070\u1072\u1077\u1083\u1090\u1090\u10d2\u10fc\u10ff\u124a\u124c"+ + "\u124f\u1252\u1258\u125a\u125a\u125c\u125f\u1262\u128a\u128c\u128f\u1292"+ + "\u12b2\u12b4\u12b7\u12ba\u12c0\u12c2\u12c2\u12c4\u12c7\u12ca\u12d8\u12da"+ + "\u1312\u1314\u1317\u131a\u135c\u1382\u1391\u13a2\u13f6\u1403\u166e\u1671"+ + "\u1681\u1683\u169c\u16a2\u16ec\u1702\u170e\u1710\u1713\u1722\u1733\u1742"+ + "\u1753\u1762\u176e\u1770\u1772\u1782\u17b5\u17de\u17de\u1822\u1844\u1846"+ + "\u1879\u1882\u18aa\u18ac\u18ac\u18b2\u18f7\u1902\u191e\u1952\u196f\u1972"+ + "\u1976\u1982\u19ad\u19c3\u19c9\u1a02\u1a18\u1a22\u1a56\u1b07\u1b35\u1b47"+ + "\u1b4d\u1b85\u1ba2\u1bb0\u1bb1\u1bbc\u1be7\u1c02\u1c25\u1c4f\u1c51\u1c5c"+ + "\u1c79\u1ceb\u1cee\u1cf0\u1cf3\u1cf7\u1cf8\u2137\u213a\u2d32\u2d69\u2d82"+ + "\u2d98\u2da2\u2da8\u2daa\u2db0\u2db2\u2db8\u2dba\u2dc0\u2dc2\u2dc8\u2dca"+ + "\u2dd0\u2dd2\u2dd8\u2dda\u2de0\u3008\u3008\u303e\u303e\u3043\u3098\u30a1"+ + "\u30a1\u30a3\u30fc\u3101\u3101\u3107\u312f\u3133\u3190\u31a2\u31bc\u31f2"+ + "\u3201\u3402\u3402\u4db7\u4db7\u4e02\u4e02\u9fce\u9fce\ua002\ua016\ua018"+ + "\ua48e\ua4d2\ua4f9\ua502\ua60d\ua612\ua621\ua62c\ua62d\ua670\ua670\ua6a2"+ + "\ua6e7\ua7fd\ua803\ua805\ua807\ua809\ua80c\ua80e\ua824\ua842\ua875\ua884"+ + "\ua8b5\ua8f4\ua8f9\ua8fd\ua8fd\ua90c\ua927\ua932\ua948\ua962\ua97e\ua986"+ + "\ua9b4\uaa02\uaa2a\uaa42\uaa44\uaa46\uaa4d\uaa62\uaa71\uaa73\uaa78\uaa7c"+ + "\uaa7c\uaa82\uaab1\uaab3\uaab3\uaab7\uaab8\uaabb\uaabf\uaac2\uaac2\uaac4"+ + "\uaac4\uaadd\uaade\uaae2\uaaec\uaaf4\uaaf4\uab03\uab08\uab0b\uab10\uab13"+ + "\uab18\uab22\uab28\uab2a\uab30\uabc2\uabe4\uac02\uac02\ud7a5\ud7a5\ud7b2"+ + "\ud7c8\ud7cd\ud7fd\uf902\ufa6f\ufa72\ufadb\ufb1f\ufb1f\ufb21\ufb2a\ufb2c"+ + "\ufb38\ufb3a\ufb3e\ufb40\ufb40\ufb42\ufb43\ufb45\ufb46\ufb48\ufbb3\ufbd5"+ + "\ufd3f\ufd52\ufd91\ufd94\ufdc9\ufdf2\ufdfd\ufe72\ufe76\ufe78\ufefe\uff68"+ + "\uff71\uff73\uff9f\uffa2\uffc0\uffc4\uffc9\uffcc\uffd1\uffd4\uffd9\uffdc"+ + "\uffde\f\2\u01c7\u01c7\u01ca\u01ca\u01cd\u01cd\u01f4\u01f4\u1f8a\u1f91"+ + "\u1f9a\u1fa1\u1faa\u1fb1\u1fbe\u1fbe\u1fce\u1fce\u1ffe\u1ffe\u0242\2C"+ + "\\\u00c2\u00d8\u00da\u00e0\u0102\u0102\u0104\u0104\u0106\u0106\u0108\u0108"+ + "\u010a\u010a\u010c\u010c\u010e\u010e\u0110\u0110\u0112\u0112\u0114\u0114"+ + "\u0116\u0116\u0118\u0118\u011a\u011a\u011c\u011c\u011e\u011e\u0120\u0120"+ + "\u0122\u0122\u0124\u0124\u0126\u0126\u0128\u0128\u012a\u012a\u012c\u012c"+ + "\u012e\u012e\u0130\u0130\u0132\u0132\u0134\u0134\u0136\u0136\u0138\u0138"+ + "\u013b\u013b\u013d\u013d\u013f\u013f\u0141\u0141\u0143\u0143\u0145\u0145"+ + "\u0147\u0147\u0149\u0149\u014c\u014c\u014e\u014e\u0150\u0150\u0152\u0152"+ + "\u0154\u0154\u0156\u0156\u0158\u0158\u015a\u015a\u015c\u015c\u015e\u015e"+ + "\u0160\u0160\u0162\u0162\u0164\u0164\u0166\u0166\u0168\u0168\u016a\u016a"+ + "\u016c\u016c\u016e\u016e\u0170\u0170\u0172\u0172\u0174\u0174\u0176\u0176"+ + "\u0178\u0178\u017a\u017b\u017d\u017d\u017f\u017f\u0183\u0184\u0186\u0186"+ + "\u0188\u0189\u018b\u018d\u0190\u0193\u0195\u0196\u0198\u019a\u019e\u019f"+ + "\u01a1\u01a2\u01a4\u01a4\u01a6\u01a6\u01a8\u01a9\u01ab\u01ab\u01ae\u01ae"+ + "\u01b0\u01b1\u01b3\u01b5\u01b7\u01b7\u01b9\u01ba\u01be\u01be\u01c6\u01c6"+ + "\u01c9\u01c9\u01cc\u01cc\u01cf\u01cf\u01d1\u01d1\u01d3\u01d3\u01d5\u01d5"+ + "\u01d7\u01d7\u01d9\u01d9\u01db\u01db\u01dd\u01dd\u01e0\u01e0\u01e2\u01e2"+ + "\u01e4\u01e4\u01e6\u01e6\u01e8\u01e8\u01ea\u01ea\u01ec\u01ec\u01ee\u01ee"+ + "\u01f0\u01f0\u01f3\u01f3\u01f6\u01f6\u01f8\u01fa\u01fc\u01fc\u01fe\u01fe"+ + "\u0200\u0200\u0202\u0202\u0204\u0204\u0206\u0206\u0208\u0208\u020a\u020a"+ + "\u020c\u020c\u020e\u020e\u0210\u0210\u0212\u0212\u0214\u0214\u0216\u0216"+ + "\u0218\u0218\u021a\u021a\u021c\u021c\u021e\u021e\u0220\u0220\u0222\u0222"+ + "\u0224\u0224\u0226\u0226\u0228\u0228\u022a\u022a\u022c\u022c\u022e\u022e"+ + "\u0230\u0230\u0232\u0232\u0234\u0234\u023c\u023d\u023f\u0240\u0243\u0243"+ + "\u0245\u0248\u024a\u024a\u024c\u024c\u024e\u024e\u0250\u0250\u0372\u0372"+ + "\u0374\u0374\u0378\u0378\u0388\u0388\u038a\u038c\u038e\u038e\u0390\u0391"+ + "\u0393\u03a3\u03a5\u03ad\u03d1\u03d1\u03d4\u03d6\u03da\u03da\u03dc\u03dc"+ + "\u03de\u03de\u03e0\u03e0\u03e2\u03e2\u03e4\u03e4\u03e6\u03e6\u03e8\u03e8"+ + "\u03ea\u03ea\u03ec\u03ec\u03ee\u03ee\u03f0\u03f0\u03f6\u03f6\u03f9\u03f9"+ + "\u03fb\u03fc\u03ff\u0431\u0462\u0462\u0464\u0464\u0466\u0466\u0468\u0468"+ + "\u046a\u046a\u046c\u046c\u046e\u046e\u0470\u0470\u0472\u0472\u0474\u0474"+ + "\u0476\u0476\u0478\u0478\u047a\u047a\u047c\u047c\u047e\u047e\u0480\u0480"+ + "\u0482\u0482\u048c\u048c\u048e\u048e\u0490\u0490\u0492\u0492\u0494\u0494"+ + "\u0496\u0496\u0498\u0498\u049a\u049a\u049c\u049c\u049e\u049e\u04a0\u04a0"+ + "\u04a2\u04a2\u04a4\u04a4\u04a6\u04a6\u04a8\u04a8\u04aa\u04aa\u04ac\u04ac"+ + "\u04ae\u04ae\u04b0\u04b0\u04b2\u04b2\u04b4\u04b4\u04b6\u04b6\u04b8\u04b8"+ + "\u04ba\u04ba\u04bc\u04bc\u04be\u04be\u04c0\u04c0\u04c2\u04c3\u04c5\u04c5"+ + "\u04c7\u04c7\u04c9\u04c9\u04cb\u04cb\u04cd\u04cd\u04cf\u04cf\u04d2\u04d2"+ + "\u04d4\u04d4\u04d6\u04d6\u04d8\u04d8\u04da\u04da\u04dc\u04dc\u04de\u04de"+ + "\u04e0\u04e0\u04e2\u04e2\u04e4\u04e4\u04e6\u04e6\u04e8\u04e8\u04ea\u04ea"+ + "\u04ec\u04ec\u04ee\u04ee\u04f0\u04f0\u04f2\u04f2\u04f4\u04f4\u04f6\u04f6"+ + "\u04f8\u04f8\u04fa\u04fa\u04fc\u04fc\u04fe\u04fe\u0500\u0500\u0502\u0502"+ + "\u0504\u0504\u0506\u0506\u0508\u0508\u050a\u050a\u050c\u050c\u050e\u050e"+ + "\u0510\u0510\u0512\u0512\u0514\u0514\u0516\u0516\u0518\u0518\u051a\u051a"+ + "\u051c\u051c\u051e\u051e\u0520\u0520\u0522\u0522\u0524\u0524\u0526\u0526"+ + "\u0528\u0528\u0533\u0558\u10a2\u10c7\u10c9\u10c9\u10cf\u10cf\u1e02\u1e02"+ + "\u1e04\u1e04\u1e06\u1e06\u1e08\u1e08\u1e0a\u1e0a\u1e0c\u1e0c\u1e0e\u1e0e"+ + "\u1e10\u1e10\u1e12\u1e12\u1e14\u1e14\u1e16\u1e16\u1e18\u1e18\u1e1a\u1e1a"+ + "\u1e1c\u1e1c\u1e1e\u1e1e\u1e20\u1e20\u1e22\u1e22\u1e24\u1e24\u1e26\u1e26"+ + "\u1e28\u1e28\u1e2a\u1e2a\u1e2c\u1e2c\u1e2e\u1e2e\u1e30\u1e30\u1e32\u1e32"+ + "\u1e34\u1e34\u1e36\u1e36\u1e38\u1e38\u1e3a\u1e3a\u1e3c\u1e3c\u1e3e\u1e3e"+ + "\u1e40\u1e40\u1e42\u1e42\u1e44\u1e44\u1e46\u1e46\u1e48\u1e48\u1e4a\u1e4a"+ + "\u1e4c\u1e4c\u1e4e\u1e4e\u1e50\u1e50\u1e52\u1e52\u1e54\u1e54\u1e56\u1e56"+ + "\u1e58\u1e58\u1e5a\u1e5a\u1e5c\u1e5c\u1e5e\u1e5e\u1e60\u1e60\u1e62\u1e62"+ + "\u1e64\u1e64\u1e66\u1e66\u1e68\u1e68\u1e6a\u1e6a\u1e6c\u1e6c\u1e6e\u1e6e"+ + "\u1e70\u1e70\u1e72\u1e72\u1e74\u1e74\u1e76\u1e76\u1e78\u1e78\u1e7a\u1e7a"+ + "\u1e7c\u1e7c\u1e7e\u1e7e\u1e80\u1e80\u1e82\u1e82\u1e84\u1e84\u1e86\u1e86"+ + "\u1e88\u1e88\u1e8a\u1e8a\u1e8c\u1e8c\u1e8e\u1e8e\u1e90\u1e90\u1e92\u1e92"+ + "\u1e94\u1e94\u1e96\u1e96\u1ea0\u1ea0\u1ea2\u1ea2\u1ea4\u1ea4\u1ea6\u1ea6"+ + "\u1ea8\u1ea8\u1eaa\u1eaa\u1eac\u1eac\u1eae\u1eae\u1eb0\u1eb0\u1eb2\u1eb2"+ + "\u1eb4\u1eb4\u1eb6\u1eb6\u1eb8\u1eb8\u1eba\u1eba\u1ebc\u1ebc\u1ebe\u1ebe"+ + "\u1ec0\u1ec0\u1ec2\u1ec2\u1ec4\u1ec4\u1ec6\u1ec6\u1ec8\u1ec8\u1eca\u1eca"+ + "\u1ecc\u1ecc\u1ece\u1ece\u1ed0\u1ed0\u1ed2\u1ed2\u1ed4\u1ed4\u1ed6\u1ed6"+ + "\u1ed8\u1ed8\u1eda\u1eda\u1edc\u1edc\u1ede\u1ede\u1ee0\u1ee0\u1ee2\u1ee2"+ + "\u1ee4\u1ee4\u1ee6\u1ee6\u1ee8\u1ee8\u1eea\u1eea\u1eec\u1eec\u1eee\u1eee"+ + "\u1ef0\u1ef0\u1ef2\u1ef2\u1ef4\u1ef4\u1ef6\u1ef6\u1ef8\u1ef8\u1efa\u1efa"+ + "\u1efc\u1efc\u1efe\u1efe\u1f00\u1f00\u1f0a\u1f11\u1f1a\u1f1f\u1f2a\u1f31"+ + "\u1f3a\u1f41\u1f4a\u1f4f\u1f5b\u1f5b\u1f5d\u1f5d\u1f5f\u1f5f\u1f61\u1f61"+ + "\u1f6a\u1f71\u1fba\u1fbd\u1fca\u1fcd\u1fda\u1fdd\u1fea\u1fee\u1ffa\u1ffd"+ + "\u2104\u2104\u2109\u2109\u210d\u210f\u2112\u2114\u2117\u2117\u211b\u211f"+ + "\u2126\u2126\u2128\u2128\u212a\u212a\u212c\u212f\u2132\u2135\u2140\u2141"+ + "\u2147\u2147\u2185\u2185\u2c02\u2c30\u2c62\u2c62\u2c64\u2c66\u2c69\u2c69"+ + "\u2c6b\u2c6b\u2c6d\u2c6d\u2c6f\u2c72\u2c74\u2c74\u2c77\u2c77\u2c80\u2c82"+ + "\u2c84\u2c84\u2c86\u2c86\u2c88\u2c88\u2c8a\u2c8a\u2c8c\u2c8c\u2c8e\u2c8e"+ + "\u2c90\u2c90\u2c92\u2c92\u2c94\u2c94\u2c96\u2c96\u2c98\u2c98\u2c9a\u2c9a"+ + "\u2c9c\u2c9c\u2c9e\u2c9e\u2ca0\u2ca0\u2ca2\u2ca2\u2ca4\u2ca4\u2ca6\u2ca6"+ + "\u2ca8\u2ca8\u2caa\u2caa\u2cac\u2cac\u2cae\u2cae\u2cb0\u2cb0\u2cb2\u2cb2"+ + "\u2cb4\u2cb4\u2cb6\u2cb6\u2cb8\u2cb8\u2cba\u2cba\u2cbc\u2cbc\u2cbe\u2cbe"+ + "\u2cc0\u2cc0\u2cc2\u2cc2\u2cc4\u2cc4\u2cc6\u2cc6\u2cc8\u2cc8\u2cca\u2cca"+ + "\u2ccc\u2ccc\u2cce\u2cce\u2cd0\u2cd0\u2cd2\u2cd2\u2cd4\u2cd4\u2cd6\u2cd6"+ + "\u2cd8\u2cd8\u2cda\u2cda\u2cdc\u2cdc\u2cde\u2cde\u2ce0\u2ce0\u2ce2\u2ce2"+ + "\u2ce4\u2ce4\u2ced\u2ced\u2cef\u2cef\u2cf4\u2cf4\ua642\ua642\ua644\ua644"+ + "\ua646\ua646\ua648\ua648\ua64a\ua64a\ua64c\ua64c\ua64e\ua64e\ua650\ua650"+ + "\ua652\ua652\ua654\ua654\ua656\ua656\ua658\ua658\ua65a\ua65a\ua65c\ua65c"+ + "\ua65e\ua65e\ua660\ua660\ua662\ua662\ua664\ua664\ua666\ua666\ua668\ua668"+ + "\ua66a\ua66a\ua66c\ua66c\ua66e\ua66e\ua682\ua682\ua684\ua684\ua686\ua686"+ + "\ua688\ua688\ua68a\ua68a\ua68c\ua68c\ua68e\ua68e\ua690\ua690\ua692\ua692"+ + "\ua694\ua694\ua696\ua696\ua698\ua698\ua724\ua724\ua726\ua726\ua728\ua728"+ + "\ua72a\ua72a\ua72c\ua72c\ua72e\ua72e\ua730\ua730\ua734\ua734\ua736\ua736"+ + "\ua738\ua738\ua73a\ua73a\ua73c\ua73c\ua73e\ua73e\ua740\ua740\ua742\ua742"+ + "\ua744\ua744\ua746\ua746\ua748\ua748\ua74a\ua74a\ua74c\ua74c\ua74e\ua74e"+ + "\ua750\ua750\ua752\ua752\ua754\ua754\ua756\ua756\ua758\ua758\ua75a\ua75a"+ + "\ua75c\ua75c\ua75e\ua75e\ua760\ua760\ua762\ua762\ua764\ua764\ua766\ua766"+ + "\ua768\ua768\ua76a\ua76a\ua76c\ua76c\ua76e\ua76e\ua770\ua770\ua77b\ua77b"+ + "\ua77d\ua77d\ua77f\ua780\ua782\ua782\ua784\ua784\ua786\ua786\ua788\ua788"+ + "\ua78d\ua78d\ua78f\ua78f\ua792\ua792\ua794\ua794\ua7a2\ua7a2\ua7a4\ua7a4"+ + "\ua7a6\ua7a6\ua7a8\ua7a8\ua7aa\ua7aa\ua7ac\ua7ac\uff23\uff3c%\2\62;\u0662"+ + "\u066b\u06f2\u06fb\u07c2\u07cb\u0968\u0971\u09e8\u09f1\u0a68\u0a71\u0ae8"+ + "\u0af1\u0b68\u0b71\u0be8\u0bf1\u0c68\u0c71\u0ce8\u0cf1\u0d68\u0d71\u0e52"+ + "\u0e5b\u0ed2\u0edb\u0f22\u0f2b\u1042\u104b\u1092\u109b\u17e2\u17eb\u1812"+ + "\u181b\u1948\u1951\u19d2\u19db\u1a82\u1a8b\u1a92\u1a9b\u1b52\u1b5b\u1bb2"+ + "\u1bbb\u1c42\u1c4b\u1c52\u1c5b\ua622\ua62b\ua8d2\ua8db\ua902\ua90b\ua9d2"+ + "\ua9db\uaa52\uaa5b\uabf2\uabfb\uff12\uff1b\t\2\u16f0\u16f2\u2162\u2184"+ + "\u2187\u218a\u3009\u3009\u3023\u302b\u303a\u303c\ua6e8\ua6f1\5\2$$&&^"+ + "^\2\u0a82\2\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2"+ + "\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2\27\3\2\2\2\2\31\3\2\2\2\2"+ + "\33\3\2\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2\2\2\2#\3\2\2\2\2%\3\2\2\2"+ + "\2\'\3\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2\2\2/\3\2\2\2\2\61\3\2\2\2"+ + "\2\63\3\2\2\2\2\65\3\2\2\2\2\67\3\2\2\2\29\3\2\2\2\2;\3\2\2\2\2=\3\2\2"+ + "\2\2?\3\2\2\2\2A\3\2\2\2\2C\3\2\2\2\2E\3\2\2\2\2G\3\2\2\2\2I\3\2\2\2\2"+ + "K\3\2\2\2\2M\3\2\2\2\2O\3\2\2\2\2Q\3\2\2\2\2S\3\2\2\2\2U\3\2\2\2\2W\3"+ + "\2\2\2\2Y\3\2\2\2\2[\3\2\2\2\2]\3\2\2\2\2_\3\2\2\2\2a\3\2\2\2\2c\3\2\2"+ + "\2\2e\3\2\2\2\2g\3\2\2\2\2i\3\2\2\2\2k\3\2\2\2\2m\3\2\2\2\2o\3\2\2\2\2"+ + "q\3\2\2\2\2s\3\2\2\2\2u\3\2\2\2\2w\3\2\2\2\2y\3\2\2\2\2{\3\2\2\2\2}\3"+ + "\2\2\2\2\177\3\2\2\2\2\u0081\3\2\2\2\2\u0083\3\2\2\2\2\u0085\3\2\2\2\2"+ + "\u0087\3\2\2\2\2\u0089\3\2\2\2\2\u008b\3\2\2\2\2\u008d\3\2\2\2\2\u008f"+ + "\3\2\2\2\2\u0091\3\2\2\2\2\u0093\3\2\2\2\2\u0095\3\2\2\2\2\u0097\3\2\2"+ + "\2\2\u0099\3\2\2\2\2\u009b\3\2\2\2\2\u009d\3\2\2\2\2\u009f\3\2\2\2\2\u00a1"+ + "\3\2\2\2\2\u00a3\3\2\2\2\2\u00a5\3\2\2\2\2\u00a7\3\2\2\2\2\u00a9\3\2\2"+ + "\2\2\u00ab\3\2\2\2\2\u00ad\3\2\2\2\2\u00af\3\2\2\2\2\u00b1\3\2\2\2\2\u00b3"+ + "\3\2\2\2\2\u00b5\3\2\2\2\2\u00b7\3\2\2\2\2\u00b9\3\2\2\2\2\u00bb\3\2\2"+ + "\2\2\u00bd\3\2\2\2\2\u00bf\3\2\2\2\2\u00c1\3\2\2\2\2\u00c3\3\2\2\2\2\u00c5"+ + "\3\2\2\2\2\u00c7\3\2\2\2\2\u00c9\3\2\2\2\2\u00cb\3\2\2\2\2\u00cd\3\2\2"+ + "\2\2\u00cf\3\2\2\2\2\u00d1\3\2\2\2\2\u00d3\3\2\2\2\2\u00d5\3\2\2\2\2\u00d7"+ + "\3\2\2\2\2\u00d9\3\2\2\2\2\u00db\3\2\2\2\2\u00dd\3\2\2\2\2\u00df\3\2\2"+ + "\2\2\u00e1\3\2\2\2\2\u00e3\3\2\2\2\2\u00e5\3\2\2\2\2\u00e7\3\2\2\2\2\u00e9"+ + "\3\2\2\2\2\u00eb\3\2\2\2\2\u00ed\3\2\2\2\2\u00ef\3\2\2\2\2\u00f1\3\2\2"+ + "\2\2\u00f3\3\2\2\2\2\u00f5\3\2\2\2\2\u00f7\3\2\2\2\2\u00f9\3\2\2\2\2\u00fb"+ + "\3\2\2\2\2\u00fd\3\2\2\2\2\u00ff\3\2\2\2\2\u0101\3\2\2\2\2\u0103\3\2\2"+ + "\2\2\u0105\3\2\2\2\2\u0107\3\2\2\2\2\u0109\3\2\2\2\2\u010b\3\2\2\2\2\u010d"+ + "\3\2\2\2\2\u010f\3\2\2\2\2\u0111\3\2\2\2\2\u0113\3\2\2\2\2\u011b\3\2\2"+ + "\2\2\u011f\3\2\2\2\2\u0123\3\2\2\2\2\u0125\3\2\2\2\2\u0127\3\2\2\2\2\u0129"+ + "\3\2\2\2\2\u012b\3\2\2\2\2\u012d\3\2\2\2\2\u012f\3\2\2\2\2\u0139\3\2\2"+ + "\2\2\u013b\3\2\2\2\2\u013d\3\2\2\2\2\u013f\3\2\2\2\2\u0141\3\2\2\2\2\u0143"+ + "\3\2\2\2\2\u0145\3\2\2\2\3\u0147\3\2\2\2\3\u0149\3\2\2\2\3\u014b\3\2\2"+ + "\2\3\u014d\3\2\2\2\3\u014f\3\2\2\2\3\u0151\3\2\2\2\3\u0153\3\2\2\2\3\u0155"+ + "\3\2\2\2\3\u0157\3\2\2\2\3\u0159\3\2\2\2\3\u015b\3\2\2\2\3\u015d\3\2\2"+ + "\2\3\u015f\3\2\2\2\3\u0161\3\2\2\2\3\u0163\3\2\2\2\3\u0165\3\2\2\2\3\u0167"+ + "\3\2\2\2\3\u0169\3\2\2\2\3\u016b\3\2\2\2\3\u016d\3\2\2\2\3\u016f\3\2\2"+ + "\2\3\u0171\3\2\2\2\3\u0173\3\2\2\2\3\u0175\3\2\2\2\3\u0177\3\2\2\2\3\u0179"+ + "\3\2\2\2\3\u017b\3\2\2\2\3\u017d\3\2\2\2\3\u017f\3\2\2\2\3\u0181\3\2\2"+ + "\2\3\u0183\3\2\2\2\3\u0185\3\2\2\2\3\u0187\3\2\2\2\3\u0189\3\2\2\2\3\u018b"+ + "\3\2\2\2\3\u018d\3\2\2\2\3\u018f\3\2\2\2\3\u0191\3\2\2\2\3\u0193\3\2\2"+ + "\2\3\u0195\3\2\2\2\3\u0197\3\2\2\2\3\u0199\3\2\2\2\3\u019b\3\2\2\2\3\u019d"+ + "\3\2\2\2\3\u019f\3\2\2\2\3\u01a1\3\2\2\2\3\u01a3\3\2\2\2\3\u01a5\3\2\2"+ + "\2\3\u01a7\3\2\2\2\3\u01a9\3\2\2\2\3\u01ab\3\2\2\2\3\u01ad\3\2\2\2\3\u01af"+ + "\3\2\2\2\3\u01b1\3\2\2\2\3\u01b3\3\2\2\2\3\u01b5\3\2\2\2\3\u01b7\3\2\2"+ + "\2\3\u01b9\3\2\2\2\3\u01bb\3\2\2\2\3\u01bd\3\2\2\2\3\u01bf\3\2\2\2\3\u01c1"+ + "\3\2\2\2\3\u01c3\3\2\2\2\3\u01c5\3\2\2\2\3\u01c7\3\2\2\2\3\u01c9\3\2\2"+ + "\2\3\u01cb\3\2\2\2\3\u01cd\3\2\2\2\3\u01cf\3\2\2\2\3\u01d1\3\2\2\2\3\u01d3"+ + "\3\2\2\2\3\u01d5\3\2\2\2\3\u01d7\3\2\2\2\3\u01d9\3\2\2\2\3\u01db\3\2\2"+ + "\2\3\u01dd\3\2\2\2\3\u01df\3\2\2\2\3\u01e1\3\2\2\2\3\u01e3\3\2\2\2\3\u01e5"+ + "\3\2\2\2\3\u01e7\3\2\2\2\3\u01e9\3\2\2\2\3\u01eb\3\2\2\2\3\u01ed\3\2\2"+ + "\2\3\u01ef\3\2\2\2\3\u01f1\3\2\2\2\3\u01f3\3\2\2\2\3\u01f5\3\2\2\2\3\u01f7"+ + "\3\2\2\2\3\u01f9\3\2\2\2\3\u01fb\3\2\2\2\3\u01fd\3\2\2\2\3\u01ff\3\2\2"+ + "\2\3\u0201\3\2\2\2\3\u0203\3\2\2\2\3\u0205\3\2\2\2\3\u0207\3\2\2\2\3\u0209"+ + "\3\2\2\2\3\u020b\3\2\2\2\3\u020d\3\2\2\2\3\u020f\3\2\2\2\3\u0211\3\2\2"+ + "\2\3\u0213\3\2\2\2\3\u0215\3\2\2\2\3\u0217\3\2\2\2\3\u0219\3\2\2\2\3\u021b"+ + "\3\2\2\2\3\u021d\3\2\2\2\3\u021f\3\2\2\2\3\u0221\3\2\2\2\3\u0223\3\2\2"+ + "\2\3\u0225\3\2\2\2\3\u0227\3\2\2\2\3\u0229\3\2\2\2\3\u022b\3\2\2\2\3\u022d"+ + "\3\2\2\2\3\u022f\3\2\2\2\3\u0231\3\2\2\2\3\u0233\3\2\2\2\3\u0235\3\2\2"+ + "\2\3\u0237\3\2\2\2\4\u0239\3\2\2\2\4\u023b\3\2\2\2\4\u023d\3\2\2\2\4\u023f"+ + "\3\2\2\2\4\u0241\3\2\2\2\5\u0243\3\2\2\2\5\u0245\3\2\2\2\5\u0247\3\2\2"+ + "\2\5\u0249\3\2\2\2\5\u024b\3\2\2\2\5\u024d\3\2\2\2\5\u024f\3\2\2\2\6\u0251"+ + "\3\2\2\2\6\u0253\3\2\2\2\6\u0255\3\2\2\2\6\u0257\3\2\2\2\6\u0259\3\2\2"+ + "\2\6\u025b\3\2\2\2\6\u025d\3\2\2\2\6\u025f\3\2\2\2\6\u0261\3\2\2\2\6\u0263"+ + "\3\2\2\2\6\u0265\3\2\2\2\6\u0267\3\2\2\2\6\u0269\3\2\2\2\6\u026b\3\2\2"+ + "\2\6\u026d\3\2\2\2\6\u026f\3\2\2\2\6\u0271\3\2\2\2\6\u0273\3\2\2\2\6\u0275"+ + "\3\2\2\2\6\u0277\3\2\2\2\6\u0279\3\2\2\2\6\u027b\3\2\2\2\6\u027d\3\2\2"+ + "\2\6\u027f\3\2\2\2\6\u0281\3\2\2\2\6\u0283\3\2\2\2\6\u0285\3\2\2\2\6\u0287"+ + "\3\2\2\2\6\u0289\3\2\2\2\6\u028b\3\2\2\2\6\u028d\3\2\2\2\6\u028f\3\2\2"+ + "\2\6\u0291\3\2\2\2\6\u0293\3\2\2\2\6\u0295\3\2\2\2\6\u0297\3\2\2\2\6\u0299"+ + "\3\2\2\2\6\u029b\3\2\2\2\6\u029d\3\2\2\2\6\u029f\3\2\2\2\6\u02a1\3\2\2"+ + "\2\6\u02a3\3\2\2\2\6\u02a5\3\2\2\2\6\u02a7\3\2\2\2\6\u02a9\3\2\2\2\6\u02ab"+ + "\3\2\2\2\6\u02ad\3\2\2\2\6\u02af\3\2\2\2\6\u02b1\3\2\2\2\6\u02b3\3\2\2"+ + "\2\6\u02b5\3\2\2\2\6\u02b7\3\2\2\2\6\u02b9\3\2\2\2\6\u02bb\3\2\2\2\6\u02bd"+ + "\3\2\2\2\6\u02bf\3\2\2\2\6\u02c1\3\2\2\2\6\u02c3\3\2\2\2\6\u02c5\3\2\2"+ + "\2\6\u02c7\3\2\2\2\6\u02c9\3\2\2\2\6\u02cb\3\2\2\2\6\u02cd\3\2\2\2\6\u02cf"+ + "\3\2\2\2\6\u02d1\3\2\2\2\6\u02d3\3\2\2\2\6\u02d5\3\2\2\2\7\u02d7\3\2\2"+ + "\2\t\u02e2\3\2\2\2\13\u02f1\3\2\2\2\r\u02fc\3\2\2\2\17\u0303\3\2\2\2\21"+ + "\u0305\3\2\2\2\23\u0309\3\2\2\2\25\u030b\3\2\2\2\27\u030d\3\2\2\2\31\u0311"+ + "\3\2\2\2\33\u0313\3\2\2\2\35\u0317\3\2\2\2\37\u0319\3\2\2\2!\u031b\3\2"+ + "\2\2#\u031d\3\2\2\2%\u031f\3\2\2\2\'\u0321\3\2\2\2)\u0323\3\2\2\2+\u0325"+ + "\3\2\2\2-\u0327\3\2\2\2/\u032a\3\2\2\2\61\u032d\3\2\2\2\63\u0330\3\2\2"+ + "\2\65\u0333\3\2\2\2\67\u0335\3\2\2\29\u0337\3\2\2\2;\u0339\3\2\2\2=\u033b"+ + "\3\2\2\2?\u033e\3\2\2\2A\u0341\3\2\2\2C\u0344\3\2\2\2E\u0347\3\2\2\2G"+ + "\u034a\3\2\2\2I\u034d\3\2\2\2K\u0350\3\2\2\2M\u0353\3\2\2\2O\u0356\3\2"+ + "\2\2Q\u035a\3\2\2\2S\u035d\3\2\2\2U\u035f\3\2\2\2W\u0361\3\2\2\2Y\u0363"+ + "\3\2\2\2[\u0366\3\2\2\2]\u0368\3\2\2\2_\u036a\3\2\2\2a\u036d\3\2\2\2c"+ + "\u0370\3\2\2\2e\u0373\3\2\2\2g\u0377\3\2\2\2i\u037b\3\2\2\2k\u037e\3\2"+ + "\2\2m\u0382\3\2\2\2o\u0384\3\2\2\2q\u038e\3\2\2\2s\u039a\3\2\2\2u\u03a3"+ + "\3\2\2\2w\u03a9\3\2\2\2y\u03b1\3\2\2\2{\u03b8\3\2\2\2}\u03be\3\2\2\2\177"+ + "\u03c8\3\2\2\2\u0081\u03cc\3\2\2\2\u0083\u03d3\3\2\2\2\u0085\u03d7\3\2"+ + "\2\2\u0087\u03db\3\2\2\2\u0089\u03e5\3\2\2\2\u008b\u03f1\3\2\2\2\u008d"+ + "\u03f4\3\2\2\2\u008f\u03fe\3\2\2\2\u0091\u0403\3\2\2\2\u0093\u0408\3\2"+ + "\2\2\u0095\u040e\3\2\2\2\u0097\u0415\3\2\2\2\u0099\u041b\3\2\2\2\u009b"+ + "\u041e\3\2\2\2\u009d\u0423\3\2\2\2\u009f\u0428\3\2\2\2\u00a1\u042c\3\2"+ + "\2\2\u00a3\u0432\3\2\2\2\u00a5\u043a\3\2\2\2\u00a7\u043e\3\2\2\2\u00a9"+ + "\u0441\3\2\2\2\u00ab\u0447\3\2\2\2\u00ad\u044d\3\2\2\2\u00af\u0454\3\2"+ + "\2\2\u00b1\u045d\3\2\2\2\u00b3\u0463\3\2\2\2\u00b5\u0466\3\2\2\2\u00b7"+ + "\u0469\3\2\2\2\u00b9\u046c\3\2\2\2\u00bb\u0476\3\2\2\2\u00bd\u0480\3\2"+ + "\2\2\u00bf\u0484\3\2\2\2\u00c1\u048b\3\2\2\2\u00c3\u0495\3\2\2\2\u00c5"+ + "\u049a\3\2\2\2\u00c7\u049f\3\2\2\2\u00c9\u04a3\3\2\2\2\u00cb\u04a7\3\2"+ + "\2\2\u00cd\u04b1\3\2\2\2\u00cf\u04b8\3\2\2\2\u00d1\u04c2\3\2\2\2\u00d3"+ + "\u04cc\3\2\2\2\u00d5\u04d4\3\2\2\2\u00d7\u04db\3\2\2\2\u00d9\u04e3\3\2"+ + "\2\2\u00db\u04ed\3\2\2\2\u00dd\u04f6\3\2\2\2\u00df\u04fb\3\2\2\2\u00e1"+ + "\u0502\3\2\2\2\u00e3\u050d\3\2\2\2\u00e5\u0512\3\2\2\2\u00e7\u0518\3\2"+ + "\2\2\u00e9\u0520\3\2\2\2\u00eb\u0529\3\2\2\2\u00ed\u0530\3\2\2\2\u00ef"+ + "\u0536\3\2\2\2\u00f1\u053f\3\2\2\2\u00f3\u0547\3\2\2\2\u00f5\u0550\3\2"+ + "\2\2\u00f7\u0559\3\2\2\2\u00f9\u055f\3\2\2\2\u00fb\u0564\3\2\2\2\u00fd"+ + "\u056a\3\2\2\2\u00ff\u0573\3\2\2\2\u0101\u057a\3\2\2\2\u0103\u0583\3\2"+ + "\2\2\u0105\u058f\3\2\2\2\u0107\u0597\3\2\2\2\u0109\u059b\3\2\2\2\u010b"+ + "\u05a3\3\2\2\2\u010d\u05a7\3\2\2\2\u010f\u05c3\3\2\2\2\u0111\u061e\3\2"+ + "\2\2\u0113\u067d\3\2\2\2\u0115\u067f\3\2\2\2\u0117\u0681\3\2\2\2\u0119"+ + "\u0683\3\2\2\2\u011b\u0685\3\2\2\2\u011d\u068f\3\2\2\2\u011f\u0691\3\2"+ + "\2\2\u0121\u069b\3\2\2\2\u0123\u06a6\3\2\2\2\u0125\u06a8\3\2\2\2\u0127"+ + "\u06c0\3\2\2\2\u0129\u06c2\3\2\2\2\u012b\u06c5\3\2\2\2\u012d\u06c8\3\2"+ + "\2\2\u012f\u06cb\3\2\2\2\u0131\u06d4\3\2\2\2\u0133\u06d6\3\2\2\2\u0135"+ + "\u06dd\3\2\2\2\u0137\u06e6\3\2\2\2\u0139\u06e8\3\2\2\2\u013b\u06ea\3\2"+ + "\2\2\u013d\u06ec\3\2\2\2\u013f\u06ee\3\2\2\2\u0141\u06f0\3\2\2\2\u0143"+ + "\u06f2\3\2\2\2\u0145\u06f4\3\2\2\2\u0147\u06f6\3\2\2\2\u0149\u06fb\3\2"+ + "\2\2\u014b\u0700\3\2\2\2\u014d\u0705\3\2\2\2\u014f\u070a\3\2\2\2\u0151"+ + "\u070e\3\2\2\2\u0153\u0712\3\2\2\2\u0155\u0716\3\2\2\2\u0157\u071a\3\2"+ + "\2\2\u0159\u071e\3\2\2\2\u015b\u0722\3\2\2\2\u015d\u0726\3\2\2\2\u015f"+ + "\u072a\3\2\2\2\u0161\u072e\3\2\2\2\u0163\u0732\3\2\2\2\u0165\u0736\3\2"+ + "\2\2\u0167\u073a\3\2\2\2\u0169\u073e\3\2\2\2\u016b\u0742\3\2\2\2\u016d"+ + "\u0746\3\2\2\2\u016f\u074a\3\2\2\2\u0171\u074e\3\2\2\2\u0173\u0752\3\2"+ + "\2\2\u0175\u0756\3\2\2\2\u0177\u075a\3\2\2\2\u0179\u075e\3\2\2\2\u017b"+ + "\u0762\3\2\2\2\u017d\u0766\3\2\2\2\u017f\u076a\3\2\2\2\u0181\u076e\3\2"+ + "\2\2\u0183\u0772\3\2\2\2\u0185\u0776\3\2\2\2\u0187\u077a\3\2\2\2\u0189"+ + "\u077e\3\2\2\2\u018b\u0782\3\2\2\2\u018d\u0786\3\2\2\2\u018f\u078a\3\2"+ + "\2\2\u0191\u078e\3\2\2\2\u0193\u0792\3\2\2\2\u0195\u0796\3\2\2\2\u0197"+ + "\u079a\3\2\2\2\u0199\u079e\3\2\2\2\u019b\u07a2\3\2\2\2\u019d\u07a6\3\2"+ + "\2\2\u019f\u07aa\3\2\2\2\u01a1\u07ae\3\2\2\2\u01a3\u07b2\3\2\2\2\u01a5"+ + "\u07b6\3\2\2\2\u01a7\u07ba\3\2\2\2\u01a9\u07be\3\2\2\2\u01ab\u07c3\3\2"+ + "\2\2\u01ad\u07c8\3\2\2\2\u01af\u07cc\3\2\2\2\u01b1\u07d0\3\2\2\2\u01b3"+ + "\u07d4\3\2\2\2\u01b5\u07d8\3\2\2\2\u01b7\u07dc\3\2\2\2\u01b9\u07e0\3\2"+ + "\2\2\u01bb\u07e4\3\2\2\2\u01bd\u07e8\3\2\2\2\u01bf\u07ec\3\2\2\2\u01c1"+ + "\u07f0\3\2\2\2\u01c3\u07f4\3\2\2\2\u01c5\u07f8\3\2\2\2\u01c7\u07fc\3\2"+ + "\2\2\u01c9\u0800\3\2\2\2\u01cb\u0804\3\2\2\2\u01cd\u0808\3\2\2\2\u01cf"+ + "\u080c\3\2\2\2\u01d1\u0810\3\2\2\2\u01d3\u0814\3\2\2\2\u01d5\u0818\3\2"+ + "\2\2\u01d7\u081c\3\2\2\2\u01d9\u0820\3\2\2\2\u01db\u0824\3\2\2\2\u01dd"+ + "\u0828\3\2\2\2\u01df\u082c\3\2\2\2\u01e1\u0830\3\2\2\2\u01e3\u0834\3\2"+ + "\2\2\u01e5\u0838\3\2\2\2\u01e7\u083c\3\2\2\2\u01e9\u0840\3\2\2\2\u01eb"+ + "\u0844\3\2\2\2\u01ed\u0848\3\2\2\2\u01ef\u084c\3\2\2\2\u01f1\u0850\3\2"+ + "\2\2\u01f3\u0854\3\2\2\2\u01f5\u0858\3\2\2\2\u01f7\u085c\3\2\2\2\u01f9"+ + "\u0860\3\2\2\2\u01fb\u0864\3\2\2\2\u01fd\u0868\3\2\2\2\u01ff\u086c\3\2"+ + "\2\2\u0201\u0870\3\2\2\2\u0203\u0874\3\2\2\2\u0205\u0878\3\2\2\2\u0207"+ + "\u087c\3\2\2\2\u0209\u0880\3\2\2\2\u020b\u0884\3\2\2\2\u020d\u0888\3\2"+ + "\2\2\u020f\u088c\3\2\2\2\u0211\u0890\3\2\2\2\u0213\u0894\3\2\2\2\u0215"+ + "\u0898\3\2\2\2\u0217\u089c\3\2\2\2\u0219\u08a0\3\2\2\2\u021b\u08a4\3\2"+ + "\2\2\u021d\u08a8\3\2\2\2\u021f\u08ac\3\2\2\2\u0221\u08b0\3\2\2\2\u0223"+ + "\u08b4\3\2\2\2\u0225\u08b8\3\2\2\2\u0227\u08bc\3\2\2\2\u0229\u08c0\3\2"+ + "\2\2\u022b\u08c4\3\2\2\2\u022d\u08c8\3\2\2\2\u022f\u08cc\3\2\2\2\u0231"+ + "\u08d0\3\2\2\2\u0233\u08d6\3\2\2\2\u0235\u08da\3\2\2\2\u0237\u08de\3\2"+ + "\2\2\u0239\u08e2\3\2\2\2\u023b\u08e6\3\2\2\2\u023d\u08ee\3\2\2\2\u023f"+ + "\u08f3\3\2\2\2\u0241\u08f5\3\2\2\2\u0243\u08fb\3\2\2\2\u0245\u0904\3\2"+ + "\2\2\u0247\u0908\3\2\2\2\u0249\u0910\3\2\2\2\u024b\u0912\3\2\2\2\u024d"+ + "\u0915\3\2\2\2\u024f\u091a\3\2\2\2\u0251\u091e\3\2\2\2\u0253\u0923\3\2"+ + "\2\2\u0255\u0928\3\2\2\2\u0257\u092d\3\2\2\2\u0259\u0931\3\2\2\2\u025b"+ + "\u0935\3\2\2\2\u025d\u093a\3\2\2\2\u025f\u093e\3\2\2\2\u0261\u0942\3\2"+ + "\2\2\u0263\u0946\3\2\2\2\u0265\u094a\3\2\2\2\u0267\u094e\3\2\2\2\u0269"+ + "\u0952\3\2\2\2\u026b\u0956\3\2\2\2\u026d\u095a\3\2\2\2\u026f\u095e\3\2"+ + "\2\2\u0271\u0962\3\2\2\2\u0273\u0966\3\2\2\2\u0275\u096a\3\2\2\2\u0277"+ + "\u096e\3\2\2\2\u0279\u0972\3\2\2\2\u027b\u0976\3\2\2\2\u027d\u097a\3\2"+ + "\2\2\u027f\u097e\3\2\2\2\u0281\u0982\3\2\2\2\u0283\u0986\3\2\2\2\u0285"+ + "\u098a\3\2\2\2\u0287\u098e\3\2\2\2\u0289\u0992\3\2\2\2\u028b\u0996\3\2"+ + "\2\2\u028d\u099a\3\2\2\2\u028f\u099e\3\2\2\2\u0291\u09a2\3\2\2\2\u0293"+ + "\u09a6\3\2\2\2\u0295\u09aa\3\2\2\2\u0297\u09ae\3\2\2\2\u0299\u09b2\3\2"+ + "\2\2\u029b\u09b6\3\2\2\2\u029d\u09ba\3\2\2\2\u029f\u09be\3\2\2\2\u02a1"+ + "\u09c2\3\2\2\2\u02a3\u09c6\3\2\2\2\u02a5\u09ca\3\2\2\2\u02a7\u09ce\3\2"+ + "\2\2\u02a9\u09d2\3\2\2\2\u02ab\u09d4\3\2\2\2\u02ad\u09d8\3\2\2\2\u02af"+ + "\u09dc\3\2\2\2\u02b1\u09e0\3\2\2\2\u02b3\u09e4\3\2\2\2\u02b5\u09e8\3\2"+ + "\2\2\u02b7\u09ec\3\2\2\2\u02b9\u09f1\3\2\2\2\u02bb\u09f6\3\2\2\2\u02bd"+ + "\u09fa\3\2\2\2\u02bf\u09fe\3\2\2\2\u02c1\u0a02\3\2\2\2\u02c3\u0a06\3\2"+ + "\2\2\u02c5\u0a0a\3\2\2\2\u02c7\u0a0e\3\2\2\2\u02c9\u0a12\3\2\2\2\u02cb"+ + "\u0a16\3\2\2\2\u02cd\u0a1a\3\2\2\2\u02cf\u0a1e\3\2\2\2\u02d1\u0a24\3\2"+ + "\2\2\u02d3\u0a28\3\2\2\2\u02d5\u0a2c\3\2\2\2\u02d7\u02d8\7%\2\2\u02d8"+ + "\u02d9\7#\2\2\u02d9\u02dd\3\2\2\2\u02da\u02dc\n\2\2\2\u02db\u02da\3\2"+ + "\2\2\u02dc\u02df\3\2\2\2\u02dd\u02db\3\2\2\2\u02dd\u02de\3\2\2\2\u02de"+ + "\u02e0\3\2\2\2\u02df\u02dd\3\2\2\2\u02e0\u02e1\b\2\2\2\u02e1\b\3\2\2\2"+ + "\u02e2\u02e3\7\61\2\2\u02e3\u02e4\7,\2\2\u02e4\u02e9\3\2\2\2\u02e5\u02e8"+ + "\5\t\3\2\u02e6\u02e8\13\2\2\2\u02e7\u02e5\3\2\2\2\u02e7\u02e6\3\2\2\2"+ + "\u02e8\u02eb\3\2\2\2\u02e9\u02ea\3\2\2\2\u02e9\u02e7\3\2\2\2\u02ea\u02ec"+ + "\3\2\2\2\u02eb\u02e9\3\2\2\2\u02ec\u02ed\7,\2\2\u02ed\u02ee\7\61\2\2\u02ee"+ + "\u02ef\3\2\2\2\u02ef\u02f0\b\3\2\2\u02f0\n\3\2\2\2\u02f1\u02f2\7\61\2"+ + "\2\u02f2\u02f3\7\61\2\2\u02f3\u02f7\3\2\2\2\u02f4\u02f6\n\2\2\2\u02f5"+ + "\u02f4\3\2\2\2\u02f6\u02f9\3\2\2\2\u02f7\u02f5\3\2\2\2\u02f7\u02f8\3\2"+ + "\2\2\u02f8\u02fa\3\2\2\2\u02f9\u02f7\3\2\2\2\u02fa\u02fb\b\4\2\2\u02fb"+ + "\f\3\2\2\2\u02fc\u02fd\t\3\2\2\u02fd\u02fe\3\2\2\2\u02fe\u02ff\b\5\3\2"+ + "\u02ff\16\3\2\2\2\u0300\u0304\7\f\2\2\u0301\u0302\7\17\2\2\u0302\u0304"+ + "\7\f\2\2\u0303\u0300\3\2\2\2\u0303\u0301\3\2\2\2\u0304\20\3\2\2\2\u0305"+ + "\u0306\7\60\2\2\u0306\u0307\7\60\2\2\u0307\u0308\7\60\2\2\u0308\22\3\2"+ + "\2\2\u0309\u030a\7\60\2\2\u030a\24\3\2\2\2\u030b\u030c\7.\2\2\u030c\26"+ + "\3\2\2\2\u030d\u030e\7*\2\2\u030e\u030f\3\2\2\2\u030f\u0310\b\n\4\2\u0310"+ + "\30\3\2\2\2\u0311\u0312\7+\2\2\u0312\32\3\2\2\2\u0313\u0314\7]\2\2\u0314"+ + "\u0315\3\2\2\2\u0315\u0316\b\f\4\2\u0316\34\3\2\2\2\u0317\u0318\7_\2\2"+ + "\u0318\36\3\2\2\2\u0319\u031a\7}\2\2\u031a \3\2\2\2\u031b\u031c\7\177"+ + "\2\2\u031c\"\3\2\2\2\u031d\u031e\7,\2\2\u031e$\3\2\2\2\u031f\u0320\7\'"+ + "\2\2\u0320&\3\2\2\2\u0321\u0322\7\61\2\2\u0322(\3\2\2\2\u0323\u0324\7"+ + "-\2\2\u0324*\3\2\2\2\u0325\u0326\7/\2\2\u0326,\3\2\2\2\u0327\u0328\7-"+ + "\2\2\u0328\u0329\7-\2\2\u0329.\3\2\2\2\u032a\u032b\7/\2\2\u032b\u032c"+ + "\7/\2\2\u032c\60\3\2\2\2\u032d\u032e\7(\2\2\u032e\u032f\7(\2\2\u032f\62"+ + "\3\2\2\2\u0330\u0331\7~\2\2\u0331\u0332\7~\2\2\u0332\64\3\2\2\2\u0333"+ + "\u0334\7#\2\2\u0334\66\3\2\2\2\u0335\u0336\7<\2\2\u03368\3\2\2\2\u0337"+ + "\u0338\7=\2\2\u0338:\3\2\2\2\u0339\u033a\7?\2\2\u033a<\3\2\2\2\u033b\u033c"+ + "\7-\2\2\u033c\u033d\7?\2\2\u033d>\3\2\2\2\u033e\u033f\7/\2\2\u033f\u0340"+ + "\7?\2\2\u0340@\3\2\2\2\u0341\u0342\7,\2\2\u0342\u0343\7?\2\2\u0343B\3"+ + "\2\2\2\u0344\u0345\7\61\2\2\u0345\u0346\7?\2\2\u0346D\3\2\2\2\u0347\u0348"+ + "\7\'\2\2\u0348\u0349\7?\2\2\u0349F\3\2\2\2\u034a\u034b\7/\2\2\u034b\u034c"+ + "\7@\2\2\u034cH\3\2\2\2\u034d\u034e\7?\2\2\u034e\u034f\7@\2\2\u034fJ\3"+ + "\2\2\2\u0350\u0351\7\60\2\2\u0351\u0352\7\60\2\2\u0352L\3\2\2\2\u0353"+ + "\u0354\7<\2\2\u0354\u0355\7<\2\2\u0355N\3\2\2\2\u0356\u0357\7A\2\2\u0357"+ + "\u0358\7<\2\2\u0358\u0359\7<\2\2\u0359P\3\2\2\2\u035a\u035b\7=\2\2\u035b"+ + "\u035c\7=\2\2\u035cR\3\2\2\2\u035d\u035e\7%\2\2\u035eT\3\2\2\2\u035f\u0360"+ + "\7B\2\2\u0360V\3\2\2\2\u0361\u0362\7A\2\2\u0362X\3\2\2\2\u0363\u0364\7"+ + "A\2\2\u0364\u0365\7<\2\2\u0365Z\3\2\2\2\u0366\u0367\7>\2\2\u0367\\\3\2"+ + "\2\2\u0368\u0369\7@\2\2\u0369^\3\2\2\2\u036a\u036b\7>\2\2\u036b\u036c"+ + "\7?\2\2\u036c`\3\2\2\2\u036d\u036e\7@\2\2\u036e\u036f\7?\2\2\u036fb\3"+ + "\2\2\2\u0370\u0371\7#\2\2\u0371\u0372\7?\2\2\u0372d\3\2\2\2\u0373\u0374"+ + "\7#\2\2\u0374\u0375\7?\2\2\u0375\u0376\7?\2\2\u0376f\3\2\2\2\u0377\u0378"+ + "\7c\2\2\u0378\u0379\7u\2\2\u0379\u037a\7A\2\2\u037ah\3\2\2\2\u037b\u037c"+ + "\7?\2\2\u037c\u037d\7?\2\2\u037dj\3\2\2\2\u037e\u037f\7?\2\2\u037f\u0380"+ + "\7?\2\2\u0380\u0381\7?\2\2\u0381l\3\2\2\2\u0382\u0383\7)\2\2\u0383n\3"+ + "\2\2\2\u0384\u0385\7t\2\2\u0385\u0386\7g\2\2\u0386\u0387\7v\2\2\u0387"+ + "\u0388\7w\2\2\u0388\u0389\7t\2\2\u0389\u038a\7p\2\2\u038a\u038b\7B\2\2"+ + "\u038b\u038c\3\2\2\2\u038c\u038d\5\u0127\u0092\2\u038dp\3\2\2\2\u038e"+ + "\u038f\7e\2\2\u038f\u0390\7q\2\2\u0390\u0391\7p\2\2\u0391\u0392\7v\2\2"+ + "\u0392\u0393\7k\2\2\u0393\u0394\7p\2\2\u0394\u0395\7w\2\2\u0395\u0396"+ + "\7g\2\2\u0396\u0397\7B\2\2\u0397\u0398\3\2\2\2\u0398\u0399\5\u0127\u0092"+ + "\2\u0399r\3\2\2\2\u039a\u039b\7d\2\2\u039b\u039c\7t\2\2\u039c\u039d\7"+ + "g\2\2\u039d\u039e\7c\2\2\u039e\u039f\7m\2\2\u039f\u03a0\7B\2\2\u03a0\u03a1"+ + "\3\2\2\2\u03a1\u03a2\5\u0127\u0092\2\u03a2t\3\2\2\2\u03a3\u03a4\7B\2\2"+ + "\u03a4\u03a5\7h\2\2\u03a5\u03a6\7k\2\2\u03a6\u03a7\7n\2\2\u03a7\u03a8"+ + "\7g\2\2\u03a8v\3\2\2\2\u03a9\u03aa\7r\2\2\u03aa\u03ab\7c\2\2\u03ab\u03ac"+ + "\7e\2\2\u03ac\u03ad\7m\2\2\u03ad\u03ae\7c\2\2\u03ae\u03af\7i\2\2\u03af"+ + "\u03b0\7g\2\2\u03b0x\3\2\2\2\u03b1\u03b2\7k\2\2\u03b2\u03b3\7o\2\2\u03b3"+ + "\u03b4\7r\2\2\u03b4\u03b5\7q\2\2\u03b5\u03b6\7t\2\2\u03b6\u03b7\7v\2\2"+ + "\u03b7z\3\2\2\2\u03b8\u03b9\7e\2\2\u03b9\u03ba\7n\2\2\u03ba\u03bb\7c\2"+ + "\2\u03bb\u03bc\7u\2\2\u03bc\u03bd\7u\2\2\u03bd|\3\2\2\2\u03be\u03bf\7"+ + "k\2\2\u03bf\u03c0\7p\2\2\u03c0\u03c1\7v\2\2\u03c1\u03c2\7g\2\2\u03c2\u03c3"+ + "\7t\2\2\u03c3\u03c4\7h\2\2\u03c4\u03c5\7c\2\2\u03c5\u03c6\7e\2\2\u03c6"+ + "\u03c7\7g\2\2\u03c7~\3\2\2\2\u03c8\u03c9\7h\2\2\u03c9\u03ca\7w\2\2\u03ca"+ + "\u03cb\7p\2\2\u03cb\u0080\3\2\2\2\u03cc\u03cd\7q\2\2\u03cd\u03ce\7d\2"+ + "\2\u03ce\u03cf\7l\2\2\u03cf\u03d0\7g\2\2\u03d0\u03d1\7e\2\2\u03d1\u03d2"+ + "\7v\2\2\u03d2\u0082\3\2\2\2\u03d3\u03d4\7x\2\2\u03d4\u03d5\7c\2\2\u03d5"+ + "\u03d6\7n\2\2\u03d6\u0084\3\2\2\2\u03d7\u03d8\7x\2\2\u03d8\u03d9\7c\2"+ + "\2\u03d9\u03da\7t\2\2\u03da\u0086\3\2\2\2\u03db\u03dc\7v\2\2\u03dc\u03dd"+ + "\7{\2\2\u03dd\u03de\7r\2\2\u03de\u03df\7g\2\2\u03df\u03e0\7c\2\2\u03e0"+ + "\u03e1\7n\2\2\u03e1\u03e2\7k\2\2\u03e2\u03e3\7c\2\2\u03e3\u03e4\7u\2\2"+ + "\u03e4\u0088\3\2\2\2\u03e5\u03e6\7e\2\2\u03e6\u03e7\7q\2\2\u03e7\u03e8"+ + "\7p\2\2\u03e8\u03e9\7u\2\2\u03e9\u03ea\7v\2\2\u03ea\u03eb\7t\2\2\u03eb"+ + "\u03ec\7w\2\2\u03ec\u03ed\7e\2\2\u03ed\u03ee\7v\2\2\u03ee\u03ef\7q\2\2"+ + "\u03ef\u03f0\7t\2\2\u03f0\u008a\3\2\2\2\u03f1\u03f2\7d\2\2\u03f2\u03f3"+ + "\7{\2\2\u03f3\u008c\3\2\2\2\u03f4\u03f5\7e\2\2\u03f5\u03f6\7q\2\2\u03f6"+ + "\u03f7\7o\2\2\u03f7\u03f8\7r\2\2\u03f8\u03f9\7c\2\2\u03f9\u03fa\7p\2\2"+ + "\u03fa\u03fb\7k\2\2\u03fb\u03fc\7q\2\2\u03fc\u03fd\7p\2\2\u03fd\u008e"+ + "\3\2\2\2\u03fe\u03ff\7k\2\2\u03ff\u0400\7p\2\2\u0400\u0401\7k\2\2\u0401"+ + "\u0402\7v\2\2\u0402\u0090\3\2\2\2\u0403\u0404\7v\2\2\u0404\u0405\7j\2"+ + "\2\u0405\u0406\7k\2\2\u0406\u0407\7u\2\2\u0407\u0092\3\2\2\2\u0408\u0409"+ + "\7u\2\2\u0409\u040a\7w\2\2\u040a\u040b\7r\2\2\u040b\u040c\7g\2\2\u040c"+ + "\u040d\7t\2\2\u040d\u0094\3\2\2\2\u040e\u040f\7v\2\2\u040f\u0410\7{\2"+ + "\2\u0410\u0411\7r\2\2\u0411\u0412\7g\2\2\u0412\u0413\7q\2\2\u0413\u0414"+ + "\7h\2\2\u0414\u0096\3\2\2\2\u0415\u0416\7y\2\2\u0416\u0417\7j\2\2\u0417"+ + "\u0418\7g\2\2\u0418\u0419\7t\2\2\u0419\u041a\7g\2\2\u041a\u0098\3\2\2"+ + "\2\u041b\u041c\7k\2\2\u041c\u041d\7h\2\2\u041d\u009a\3\2\2\2\u041e\u041f"+ + "\7g\2\2\u041f\u0420\7n\2\2\u0420\u0421\7u\2\2\u0421\u0422\7g\2\2\u0422"+ + "\u009c\3\2\2\2\u0423\u0424\7y\2\2\u0424\u0425\7j\2\2\u0425\u0426\7g\2"+ + "\2\u0426\u0427\7p\2\2\u0427\u009e\3\2\2\2\u0428\u0429\7v\2\2\u0429\u042a"+ + "\7t\2\2\u042a\u042b\7{\2\2\u042b\u00a0\3\2\2\2\u042c\u042d\7e\2\2\u042d"+ + "\u042e\7c\2\2\u042e\u042f\7v\2\2\u042f\u0430\7e\2\2\u0430\u0431\7j\2\2"+ + "\u0431\u00a2\3\2\2\2\u0432\u0433\7h\2\2\u0433\u0434\7k\2\2\u0434\u0435"+ + "\7p\2\2\u0435\u0436\7c\2\2\u0436\u0437\7n\2\2\u0437\u0438\7n\2\2\u0438"+ + "\u0439\7{\2\2\u0439\u00a4\3\2\2\2\u043a\u043b\7h\2\2\u043b\u043c\7q\2"+ + "\2\u043c\u043d\7t\2\2\u043d\u00a6\3\2\2\2\u043e\u043f\7f\2\2\u043f\u0440"+ + "\7q\2\2\u0440\u00a8\3\2\2\2\u0441\u0442\7y\2\2\u0442\u0443\7j\2\2\u0443"+ + "\u0444\7k\2\2\u0444\u0445\7n\2\2\u0445\u0446\7g\2\2\u0446\u00aa\3\2\2"+ + "\2\u0447\u0448\7v\2\2\u0448\u0449\7j\2\2\u0449\u044a\7t\2\2\u044a\u044b"+ + "\7q\2\2\u044b\u044c\7y\2\2\u044c\u00ac\3\2\2\2\u044d\u044e\7t\2\2\u044e"+ + "\u044f\7g\2\2\u044f\u0450\7v\2\2\u0450\u0451\7w\2\2\u0451\u0452\7t\2\2"+ + "\u0452\u0453\7p\2\2\u0453\u00ae\3\2\2\2\u0454\u0455\7e\2\2\u0455\u0456"+ + "\7q\2\2\u0456\u0457\7p\2\2\u0457\u0458\7v\2\2\u0458\u0459\7k\2\2\u0459"+ + "\u045a\7p\2\2\u045a\u045b\7w\2\2\u045b\u045c\7g\2\2\u045c\u00b0\3\2\2"+ + "\2\u045d\u045e\7d\2\2\u045e\u045f\7t\2\2\u045f\u0460\7g\2\2\u0460\u0461"+ + "\7c\2\2\u0461\u0462\7m\2\2\u0462\u00b2\3\2\2\2\u0463\u0464\7c\2\2\u0464"+ + "\u0465\7u\2\2\u0465\u00b4\3\2\2\2\u0466\u0467\7k\2\2\u0467\u0468\7u\2"+ + "\2\u0468\u00b6\3\2\2\2\u0469\u046a\7k\2\2\u046a\u046b\7p\2\2\u046b\u00b8"+ + "\3\2\2\2\u046c\u046d\7#\2\2\u046d\u046e\7k\2\2\u046e\u046f\7u\2\2\u046f"+ + "\u0472\3\2\2\2\u0470\u0473\5\r\5\2\u0471\u0473\5\17\6\2\u0472\u0470\3"+ + "\2\2\2\u0472\u0471\3\2\2\2\u0473\u0474\3\2\2\2\u0474\u0472\3\2\2\2\u0474"+ + "\u0475\3\2\2\2\u0475\u00ba\3\2\2\2\u0476\u0477\7#\2\2\u0477\u0478\7k\2"+ + "\2\u0478\u0479\7p\2\2\u0479\u047c\3\2\2\2\u047a\u047d\5\r\5\2\u047b\u047d"+ + "\5\17\6\2\u047c\u047a\3\2\2\2\u047c\u047b\3\2\2\2\u047d\u047e\3\2\2\2"+ + "\u047e\u047c\3\2\2\2\u047e\u047f\3\2\2\2\u047f\u00bc\3\2\2\2\u0480\u0481"+ + "\7q\2\2\u0481\u0482\7w\2\2\u0482\u0483\7v\2\2\u0483\u00be\3\2\2\2\u0484"+ + "\u0485\7B\2\2\u0485\u0486\7h\2\2\u0486\u0487\7k\2\2\u0487\u0488\7g\2\2"+ + "\u0488\u0489\7n\2\2\u0489\u048a\7f\2\2\u048a\u00c0\3\2\2\2\u048b\u048c"+ + "\7B\2\2\u048c\u048d\7r\2\2\u048d\u048e\7t\2\2\u048e\u048f\7q\2\2\u048f"+ + "\u0490\7r\2\2\u0490\u0491\7g\2\2\u0491\u0492\7t\2\2\u0492\u0493\7v\2\2"+ + "\u0493\u0494\7{\2\2\u0494\u00c2\3\2\2\2\u0495\u0496\7B\2\2\u0496\u0497"+ + "\7i\2\2\u0497\u0498\7g\2\2\u0498\u0499\7v\2\2\u0499\u00c4\3\2\2\2\u049a"+ + "\u049b\7B\2\2\u049b\u049c\7u\2\2\u049c\u049d\7g\2\2\u049d\u049e\7v\2\2"+ + "\u049e\u00c6\3\2\2\2\u049f\u04a0\7i\2\2\u04a0\u04a1\7g\2\2\u04a1\u04a2"+ + "\7v\2\2\u04a2\u00c8\3\2\2\2\u04a3\u04a4\7u\2\2\u04a4\u04a5\7g\2\2\u04a5"+ + "\u04a6\7v\2\2\u04a6\u00ca\3\2\2\2\u04a7\u04a8\7B\2\2\u04a8\u04a9\7t\2"+ + "\2\u04a9\u04aa\7g\2\2\u04aa\u04ab\7e\2\2\u04ab\u04ac\7g\2\2\u04ac\u04ad"+ + "\7k\2\2\u04ad\u04ae\7x\2\2\u04ae\u04af\7g\2\2\u04af\u04b0\7t\2\2\u04b0"+ + "\u00cc\3\2\2\2\u04b1\u04b2\7B\2\2\u04b2\u04b3\7r\2\2\u04b3\u04b4\7c\2"+ + "\2\u04b4\u04b5\7t\2\2\u04b5\u04b6\7c\2\2\u04b6\u04b7\7o\2\2\u04b7\u00ce"+ + "\3\2\2\2\u04b8\u04b9\7B\2\2\u04b9\u04ba\7u\2\2\u04ba\u04bb\7g\2\2\u04bb"+ + "\u04bc\7v\2\2\u04bc\u04bd\7r\2\2\u04bd\u04be\7c\2\2\u04be\u04bf\7t\2\2"+ + "\u04bf\u04c0\7c\2\2\u04c0\u04c1\7o\2\2\u04c1\u00d0\3\2\2\2\u04c2\u04c3"+ + "\7B\2\2\u04c3\u04c4\7f\2\2\u04c4\u04c5\7g\2\2\u04c5\u04c6\7n\2\2\u04c6"+ + "\u04c7\7g\2\2\u04c7\u04c8\7i\2\2\u04c8\u04c9\7c\2\2\u04c9\u04ca\7v\2\2"+ + "\u04ca\u04cb\7g\2\2\u04cb\u00d2\3\2\2\2\u04cc\u04cd\7f\2\2\u04cd\u04ce"+ + "\7{\2\2\u04ce\u04cf\7p\2\2\u04cf\u04d0\7c\2\2\u04d0\u04d1\7o\2\2\u04d1"+ + "\u04d2\7k\2\2\u04d2\u04d3\7e\2\2\u04d3\u00d4\3\2\2\2\u04d4\u04d5\7r\2"+ + "\2\u04d5\u04d6\7w\2\2\u04d6\u04d7\7d\2\2\u04d7\u04d8\7n\2\2\u04d8\u04d9"+ + "\7k\2\2\u04d9\u04da\7e\2\2\u04da\u00d6\3\2\2\2\u04db\u04dc\7r\2\2\u04dc"+ + "\u04dd\7t\2\2\u04dd\u04de\7k\2\2\u04de\u04df\7x\2\2\u04df\u04e0\7c\2\2"+ + "\u04e0\u04e1\7v\2\2\u04e1\u04e2\7g\2\2\u04e2\u00d8\3\2\2\2\u04e3\u04e4"+ + "\7r\2\2\u04e4\u04e5\7t\2\2\u04e5\u04e6\7q\2\2\u04e6\u04e7\7v\2\2\u04e7"+ + "\u04e8\7g\2\2\u04e8\u04e9\7e\2\2\u04e9\u04ea\7v\2\2\u04ea\u04eb\7g\2\2"+ + "\u04eb\u04ec\7f\2\2\u04ec\u00da\3\2\2\2\u04ed\u04ee\7k\2\2\u04ee\u04ef"+ + "\7p\2\2\u04ef\u04f0\7v\2\2\u04f0\u04f1\7g\2\2\u04f1\u04f2\7t\2\2\u04f2"+ + "\u04f3\7p\2\2\u04f3\u04f4\7c\2\2\u04f4\u04f5\7n\2\2\u04f5\u00dc\3\2\2"+ + "\2\u04f6\u04f7\7g\2\2\u04f7\u04f8\7p\2\2\u04f8\u04f9\7w\2\2\u04f9\u04fa"+ + "\7o\2\2\u04fa\u00de\3\2\2\2\u04fb\u04fc\7u\2\2\u04fc\u04fd\7g\2\2\u04fd"+ + "\u04fe\7c\2\2\u04fe\u04ff\7n\2\2\u04ff\u0500\7g\2\2\u0500\u0501\7f\2\2"+ + "\u0501\u00e0\3\2\2\2\u0502\u0503\7c\2\2\u0503\u0504\7p\2\2\u0504\u0505"+ + "\7p\2\2\u0505\u0506\7q\2\2\u0506\u0507\7v\2\2\u0507\u0508\7c\2\2\u0508"+ + "\u0509\7v\2\2\u0509\u050a\7k\2\2\u050a\u050b\7q\2\2\u050b\u050c\7p\2\2"+ + "\u050c\u00e2\3\2\2\2\u050d\u050e\7f\2\2\u050e\u050f\7c\2\2\u050f\u0510"+ + "\7v\2\2\u0510\u0511\7c\2\2\u0511\u00e4\3\2\2\2\u0512\u0513\7k\2\2\u0513"+ + "\u0514\7p\2\2\u0514\u0515\7p\2\2\u0515\u0516\7g\2\2\u0516\u0517\7t\2\2"+ + "\u0517\u00e6\3\2\2\2\u0518\u0519\7v\2\2\u0519\u051a\7c\2\2\u051a\u051b"+ + "\7k\2\2\u051b\u051c\7n\2\2\u051c\u051d\7t\2\2\u051d\u051e\7g\2\2\u051e"+ + "\u051f\7e\2\2\u051f\u00e8\3\2\2\2\u0520\u0521\7q\2\2\u0521\u0522\7r\2"+ + "\2\u0522\u0523\7g\2\2\u0523\u0524\7t\2\2\u0524\u0525\7c\2\2\u0525\u0526"+ + "\7v\2\2\u0526\u0527\7q\2\2\u0527\u0528\7t\2\2\u0528\u00ea\3\2\2\2\u0529"+ + "\u052a\7k\2\2\u052a\u052b\7p\2\2\u052b\u052c\7n\2\2\u052c\u052d\7k\2\2"+ + "\u052d\u052e\7p\2\2\u052e\u052f\7g\2\2\u052f\u00ec\3\2\2\2\u0530\u0531"+ + "\7k\2\2\u0531\u0532\7p\2\2\u0532\u0533\7h\2\2\u0533\u0534\7k\2\2\u0534"+ + "\u0535\7z\2\2\u0535\u00ee\3\2\2\2\u0536\u0537\7g\2\2\u0537\u0538\7z\2"+ + "\2\u0538\u0539\7v\2\2\u0539\u053a\7g\2\2\u053a\u053b\7t\2\2\u053b\u053c"+ + "\7p\2\2\u053c\u053d\7c\2\2\u053d\u053e\7n\2\2\u053e\u00f0\3\2\2\2\u053f"+ + "\u0540\7u\2\2\u0540\u0541\7w\2\2\u0541\u0542\7u\2\2\u0542\u0543\7r\2\2"+ + "\u0543\u0544\7g\2\2\u0544\u0545\7p\2\2\u0545\u0546\7f\2\2\u0546\u00f2"+ + "\3\2\2\2\u0547\u0548\7q\2\2\u0548\u0549\7x\2\2\u0549\u054a\7g\2\2\u054a"+ + "\u054b\7t\2\2\u054b\u054c\7t\2\2\u054c\u054d\7k\2\2\u054d\u054e\7f\2\2"+ + "\u054e\u054f\7g\2\2\u054f\u00f4\3\2\2\2\u0550\u0551\7c\2\2\u0551\u0552"+ + "\7d\2\2\u0552\u0553\7u\2\2\u0553\u0554\7v\2\2\u0554\u0555\7t\2\2\u0555"+ + "\u0556\7c\2\2\u0556\u0557\7e\2\2\u0557\u0558\7v\2\2\u0558\u00f6\3\2\2"+ + "\2\u0559\u055a\7h\2\2\u055a\u055b\7k\2\2\u055b\u055c\7p\2\2\u055c\u055d"+ + "\7c\2\2\u055d\u055e\7n\2\2\u055e\u00f8\3\2\2\2\u055f\u0560\7q\2\2\u0560"+ + "\u0561\7r\2\2\u0561\u0562\7g\2\2\u0562\u0563\7p\2\2\u0563\u00fa\3\2\2"+ + "\2\u0564\u0565\7e\2\2\u0565\u0566\7q\2\2\u0566\u0567\7p\2\2\u0567\u0568"+ + "\7u\2\2\u0568\u0569\7v\2\2\u0569\u00fc\3\2\2\2\u056a\u056b\7n\2\2\u056b"+ + "\u056c\7c\2\2\u056c\u056d\7v\2\2\u056d\u056e\7g\2\2\u056e\u056f\7k\2\2"+ + "\u056f\u0570\7p\2\2\u0570\u0571\7k\2\2\u0571\u0572\7v\2\2\u0572\u00fe"+ + "\3\2\2\2\u0573\u0574\7x\2\2\u0574\u0575\7c\2\2\u0575\u0576\7t\2\2\u0576"+ + "\u0577\7c\2\2\u0577\u0578\7t\2\2\u0578\u0579\7i\2\2\u0579\u0100\3\2\2"+ + "\2\u057a\u057b\7p\2\2\u057b\u057c\7q\2\2\u057c\u057d\7k\2\2\u057d\u057e"+ + "\7p\2\2\u057e\u057f\7n\2\2\u057f\u0580\7k\2\2\u0580\u0581\7p\2\2\u0581"+ + "\u0582\7g\2\2\u0582\u0102\3\2\2\2\u0583\u0584\7e\2\2\u0584\u0585\7t\2"+ + "\2\u0585\u0586\7q\2\2\u0586\u0587\7u\2\2\u0587\u0588\7u\2\2\u0588\u0589"+ + "\7k\2\2\u0589\u058a\7p\2\2\u058a\u058b\7n\2\2\u058b\u058c\7k\2\2\u058c"+ + "\u058d\7p\2\2\u058d\u058e\7g\2\2\u058e\u0104\3\2\2\2\u058f\u0590\7t\2"+ + "\2\u0590\u0591\7g\2\2\u0591\u0592\7k\2\2\u0592\u0593\7h\2\2\u0593\u0594"+ + "\7k\2\2\u0594\u0595\7g\2\2\u0595\u0596\7f\2\2\u0596\u0106\3\2\2\2\u0597"+ + "\u0598\7$\2\2\u0598\u0599\3\2\2\2\u0599\u059a\b\u0082\5\2\u059a\u0108"+ + "\3\2\2\2\u059b\u059c\7$\2\2\u059c\u059d\7$\2\2\u059d\u059e\7$\2\2\u059e"+ + "\u059f\3\2\2\2\u059f\u05a0\b\u0083\6\2\u05a0\u010a\3\2\2\2\u05a1\u05a4"+ + "\5\u010d\u0085\2\u05a2\u05a4\5\u010f\u0086\2\u05a3\u05a1\3\2\2\2\u05a3"+ + "\u05a2\3\2\2\2\u05a4\u010c\3\2\2\2\u05a5\u05a8\5\u010f\u0086\2\u05a6\u05a8"+ + "\5\u0113\u0088\2\u05a7\u05a5\3\2\2\2\u05a7\u05a6\3\2\2\2\u05a8\u05a9\3"+ + "\2\2\2\u05a9\u05aa\t\4\2\2\u05aa\u010e\3\2\2\2\u05ab\u05af\5\u0117\u008a"+ + "\2\u05ac\u05ae\5\u0115\u0089\2\u05ad\u05ac\3\2\2\2\u05ae\u05b1\3\2\2\2"+ + "\u05af\u05ad\3\2\2\2\u05af\u05b0\3\2\2\2\u05b0\u05b4\3\2\2\2\u05b1\u05af"+ + "\3\2\2\2\u05b2\u05b4\7\62\2\2\u05b3\u05ab\3\2\2\2\u05b3\u05b2\3\2\2\2"+ + "\u05b3\u05b4\3\2\2\2\u05b4\u05b5\3\2\2\2\u05b5\u05c4\7\60\2\2\u05b6\u05bb"+ + "\5\u0117\u008a\2\u05b7\u05ba\5\u0115\u0089\2\u05b8\u05ba\7a\2\2\u05b9"+ + "\u05b7\3\2\2\2\u05b9\u05b8\3\2\2\2\u05ba\u05bd\3\2\2\2\u05bb\u05b9\3\2"+ + "\2\2\u05bb\u05bc\3\2\2\2\u05bc\u05be\3\2\2\2\u05bd\u05bb\3\2\2\2\u05be"+ + "\u05bf\5\u0115\u0089\2\u05bf\u05c1\3\2\2\2\u05c0\u05b6\3\2\2\2\u05c0\u05c1"+ + "\3\2\2\2\u05c1\u05c2\3\2\2\2\u05c2\u05c4\7\60\2\2\u05c3\u05b3\3\2\2\2"+ + "\u05c3\u05c0\3\2\2\2\u05c4\u0619\3\2\2\2\u05c5\u05c7\5\u0115\u0089\2\u05c6"+ + "\u05c5\3\2\2\2\u05c7\u05c8\3\2\2\2\u05c8\u05c6\3\2\2\2\u05c8\u05c9\3\2"+ + "\2\2\u05c9\u061a\3\2\2\2\u05ca\u05cd\5\u0115\u0089\2\u05cb\u05ce\5\u0115"+ + "\u0089\2\u05cc\u05ce\7a\2\2\u05cd\u05cb\3\2\2\2\u05cd\u05cc\3\2\2\2\u05ce"+ + "\u05cf\3\2\2\2\u05cf\u05cd\3\2\2\2\u05cf\u05d0\3\2\2\2\u05d0\u05d1\3\2"+ + "\2\2\u05d1\u05d2\5\u0115\u0089\2\u05d2\u061a\3\2\2\2\u05d3\u05d5\5\u0115"+ + "\u0089\2\u05d4\u05d3\3\2\2\2\u05d5\u05d6\3\2\2\2\u05d6\u05d4\3\2\2\2\u05d6"+ + "\u05d7\3\2\2\2\u05d7\u05d8\3\2\2\2\u05d8\u05da\t\5\2\2\u05d9\u05db\t\6"+ + "\2\2\u05da\u05d9\3\2\2\2\u05da\u05db\3\2\2\2\u05db\u05dd\3\2\2\2\u05dc"+ + "\u05de\5\u0115\u0089\2\u05dd\u05dc\3\2\2\2\u05de\u05df\3\2\2\2\u05df\u05dd"+ + "\3\2\2\2\u05df\u05e0\3\2\2\2\u05e0\u061a\3\2\2\2\u05e1\u05e3\5\u0115\u0089"+ + "\2\u05e2\u05e1\3\2\2\2\u05e3\u05e4\3\2\2\2\u05e4\u05e2\3\2\2\2\u05e4\u05e5"+ + "\3\2\2\2\u05e5\u05e6\3\2\2\2\u05e6\u05e8\t\5\2\2\u05e7\u05e9\t\6\2\2\u05e8"+ + "\u05e7\3\2\2\2\u05e8\u05e9\3\2\2\2\u05e9\u05ea\3\2\2\2\u05ea\u05ed\5\u0115"+ + "\u0089\2\u05eb\u05ee\5\u0115\u0089\2\u05ec\u05ee\7a\2\2\u05ed\u05eb\3"+ + "\2\2\2\u05ed\u05ec\3\2\2\2\u05ee\u05ef\3\2\2\2\u05ef\u05ed\3\2\2\2\u05ef"+ + "\u05f0\3\2\2\2\u05f0\u05f1\3\2\2\2\u05f1\u05f2\5\u0115\u0089\2\u05f2\u061a"+ + "\3\2\2\2\u05f3\u05f6\5\u0115\u0089\2\u05f4\u05f7\5\u0115\u0089\2\u05f5"+ + "\u05f7\7a\2\2\u05f6\u05f4\3\2\2\2\u05f6\u05f5\3\2\2\2\u05f7\u05f8\3\2"+ + "\2\2\u05f8\u05f6\3\2\2\2\u05f8\u05f9\3\2\2\2\u05f9\u05fa\3\2\2\2\u05fa"+ + "\u05fb\5\u0115\u0089\2\u05fb\u05fd\t\5\2\2\u05fc\u05fe\t\6\2\2\u05fd\u05fc"+ + "\3\2\2\2\u05fd\u05fe\3\2\2\2\u05fe\u0600\3\2\2\2\u05ff\u0601\5\u0115\u0089"+ + "\2\u0600\u05ff\3\2\2\2\u0601\u0602\3\2\2\2\u0602\u0600\3\2\2\2\u0602\u0603"+ + "\3\2\2\2\u0603\u061a\3\2\2\2\u0604\u0607\5\u0115\u0089\2\u0605\u0608\5"+ + "\u0115\u0089\2\u0606\u0608\7a\2\2\u0607\u0605\3\2\2\2\u0607\u0606\3\2"+ + "\2\2\u0608\u0609\3\2\2\2\u0609\u0607\3\2\2\2\u0609\u060a\3\2\2\2\u060a"+ + "\u060b\3\2\2\2\u060b\u060c\5\u0115\u0089\2\u060c\u060e\t\5\2\2\u060d\u060f"+ + "\t\6\2\2\u060e\u060d\3\2\2\2\u060e\u060f\3\2\2\2\u060f\u0610\3\2\2\2\u0610"+ + "\u0613\5\u0115\u0089\2\u0611\u0614\5\u0115\u0089\2\u0612\u0614\7a\2\2"+ + "\u0613\u0611\3\2\2\2\u0613\u0612\3\2\2\2\u0614\u0615\3\2\2\2\u0615\u0613"+ + "\3\2\2\2\u0615\u0616\3\2\2\2\u0616\u0617\3\2\2\2\u0617\u0618\5\u0115\u0089"+ + "\2\u0618\u061a\3\2\2\2\u0619\u05c6\3\2\2\2\u0619\u05ca\3\2\2\2\u0619\u05d4"+ + "\3\2\2\2\u0619\u05e2\3\2\2\2\u0619\u05f3\3\2\2\2\u0619\u0604\3\2\2\2\u061a"+ + "\u0110\3\2\2\2\u061b\u061f\5\u0113\u0088\2\u061c\u061f\5\u011b\u008c\2"+ + "\u061d\u061f\5\u011f\u008e\2\u061e\u061b\3\2\2\2\u061e\u061c\3\2\2\2\u061e"+ + "\u061d\3\2\2\2\u061f\u0620\3\2\2\2\u0620\u0621\7N\2\2\u0621\u0112\3\2"+ + "\2\2\u0622\u067e\7\62\2\2\u0623\u0627\5\u0117\u008a\2\u0624\u0626\5\u0115"+ + "\u0089\2\u0625\u0624\3\2\2\2\u0626\u0629\3\2\2\2\u0627\u0625\3\2\2\2\u0627"+ + "\u0628\3\2\2\2\u0628\u067e\3\2\2\2\u0629\u0627\3\2\2\2\u062a\u062d\5\u0117"+ + "\u008a\2\u062b\u062e\5\u0115\u0089\2\u062c\u062e\7a\2\2\u062d\u062b\3"+ + "\2\2\2\u062d\u062c\3\2\2\2\u062e\u062f\3\2\2\2\u062f\u062d\3\2\2\2\u062f"+ + "\u0630\3\2\2\2\u0630\u0631\3\2\2\2\u0631\u0632\5\u0115\u0089\2\u0632\u067e"+ + "\3\2\2\2\u0633\u0637\5\u0117\u008a\2\u0634\u0636\5\u0115\u0089\2\u0635"+ + "\u0634\3\2\2\2\u0636\u0639\3\2\2\2\u0637\u0635\3\2\2\2\u0637\u0638\3\2"+ + "\2\2\u0638\u063a\3\2\2\2\u0639\u0637\3\2\2\2\u063a\u063c\t\5\2\2\u063b"+ + "\u063d\t\6\2\2\u063c\u063b\3\2\2\2\u063c\u063d\3\2\2\2\u063d\u063f\3\2"+ + "\2\2\u063e\u0640\5\u0115\u0089\2\u063f\u063e\3\2\2\2\u0640\u0641\3\2\2"+ + "\2\u0641\u063f\3\2\2\2\u0641\u0642\3\2\2\2\u0642\u067e\3\2\2\2\u0643\u0647"+ + "\5\u0117\u008a\2\u0644\u0646\5\u0115\u0089\2\u0645\u0644\3\2\2\2\u0646"+ + "\u0649\3\2\2\2\u0647\u0645\3\2\2\2\u0647\u0648\3\2\2\2\u0648\u064a\3\2"+ + "\2\2\u0649\u0647\3\2\2\2\u064a\u064c\t\5\2\2\u064b\u064d\t\6\2\2\u064c"+ + "\u064b\3\2\2\2\u064c\u064d\3\2\2\2\u064d\u064e\3\2\2\2\u064e\u0651\5\u0115"+ + "\u0089\2\u064f\u0652\5\u0115\u0089\2\u0650\u0652\7a\2\2\u0651\u064f\3"+ + "\2\2\2\u0651\u0650\3\2\2\2\u0652\u0653\3\2\2\2\u0653\u0651\3\2\2\2\u0653"+ + "\u0654\3\2\2\2\u0654\u0655\3\2\2\2\u0655\u0656\5\u0115\u0089\2\u0656\u067e"+ + "\3\2\2\2\u0657\u065a\5\u0117\u008a\2\u0658\u065b\5\u0115\u0089\2\u0659"+ + "\u065b\7a\2\2\u065a\u0658\3\2\2\2\u065a\u0659\3\2\2\2\u065b\u065c\3\2"+ + "\2\2\u065c\u065a\3\2\2\2\u065c\u065d\3\2\2\2\u065d\u065e\3\2\2\2\u065e"+ + "\u065f\5\u0115\u0089\2\u065f\u0661\t\5\2\2\u0660\u0662\t\6\2\2\u0661\u0660"+ + "\3\2\2\2\u0661\u0662\3\2\2\2\u0662\u0664\3\2\2\2\u0663\u0665\5\u0115\u0089"+ + "\2\u0664\u0663\3\2\2\2\u0665\u0666\3\2\2\2\u0666\u0664\3\2\2\2\u0666\u0667"+ + "\3\2\2\2\u0667\u067e\3\2\2\2\u0668\u066b\5\u0117\u008a\2\u0669\u066c\5"+ + "\u0115\u0089\2\u066a\u066c\7a\2\2\u066b\u0669\3\2\2\2\u066b\u066a\3\2"+ + "\2\2\u066c\u066d\3\2\2\2\u066d\u066b\3\2\2\2\u066d\u066e\3\2\2\2\u066e"+ + "\u066f\3\2\2\2\u066f\u0670\5\u0115\u0089\2\u0670\u0672\t\5\2\2\u0671\u0673"+ + "\t\6\2\2\u0672\u0671\3\2\2\2\u0672\u0673\3\2\2\2\u0673\u0674\3\2\2\2\u0674"+ + "\u0677\5\u0115\u0089\2\u0675\u0678\5\u0115\u0089\2\u0676\u0678\7a\2\2"+ + "\u0677\u0675\3\2\2\2\u0677\u0676\3\2\2\2\u0678\u0679\3\2\2\2\u0679\u0677"+ + "\3\2\2\2\u0679\u067a\3\2\2\2\u067a\u067b\3\2\2\2\u067b\u067c\5\u0115\u0089"+ + "\2\u067c\u067e\3\2\2\2\u067d\u0622\3\2\2\2\u067d\u0623\3\2\2\2\u067d\u062a"+ + "\3\2\2\2\u067d\u0633\3\2\2\2\u067d\u0643\3\2\2\2\u067d\u0657\3\2\2\2\u067d"+ + "\u0668\3\2\2\2\u067e\u0114\3\2\2\2\u067f\u0680\5\u0143\u00a0\2\u0680\u0116"+ + "\3\2\2\2\u0681\u0682\5\u0119\u008b\2\u0682\u0118\3\2\2\2\u0683\u0684\t"+ + "\7\2\2\u0684\u011a\3\2\2\2\u0685\u0686\7\62\2\2\u0686\u0687\t\b\2\2\u0687"+ + "\u068c\5\u011d\u008d\2\u0688\u068b\5\u011d\u008d\2\u0689\u068b\7a\2\2"+ + "\u068a\u0688\3\2\2\2\u068a\u0689\3\2\2\2\u068b\u068e\3\2\2\2\u068c\u068a"+ + "\3\2\2\2\u068c\u068d\3\2\2\2\u068d\u011c\3\2\2\2\u068e\u068c\3\2\2\2\u068f"+ + "\u0690\t\t\2\2\u0690\u011e\3\2\2\2\u0691\u0692\7\62\2\2\u0692\u0693\t"+ + "\n\2\2\u0693\u0698\5\u0121\u008f\2\u0694\u0697\5\u0121\u008f\2\u0695\u0697"+ + "\7a\2\2\u0696\u0694\3\2\2\2\u0696\u0695\3\2\2\2\u0697\u069a\3\2\2\2\u0698"+ + "\u0696\3\2\2\2\u0698\u0699\3\2\2\2\u0699\u0120\3\2\2\2\u069a\u0698\3\2"+ + "\2\2\u069b\u069c\t\13\2\2\u069c\u0122\3\2\2\2\u069d\u069e\7v\2\2\u069e"+ + "\u069f\7t\2\2\u069f\u06a0\7w\2\2\u06a0\u06a7\7g\2\2\u06a1\u06a2\7h\2\2"+ + "\u06a2\u06a3\7c\2\2\u06a3\u06a4\7n\2\2\u06a4\u06a5\7u\2\2\u06a5\u06a7"+ + "\7g\2\2\u06a6\u069d\3\2\2\2\u06a6\u06a1\3\2\2\2\u06a7\u0124\3\2\2\2\u06a8"+ + "\u06a9\7p\2\2\u06a9\u06aa\7w\2\2\u06aa\u06ab\7n\2\2\u06ab\u06ac\7n\2\2"+ + "\u06ac\u0126\3\2\2\2\u06ad\u06b0\5\u0137\u009a\2\u06ae\u06b0\7a\2\2\u06af"+ + "\u06ad\3\2\2\2\u06af\u06ae\3\2\2\2\u06b0\u06b6\3\2\2\2\u06b1\u06b5\5\u0137"+ + "\u009a\2\u06b2\u06b5\7a\2\2\u06b3\u06b5\5\u0115\u0089\2\u06b4\u06b1\3"+ + "\2\2\2\u06b4\u06b2\3\2\2\2\u06b4\u06b3\3\2\2\2\u06b5\u06b8\3\2\2\2\u06b6"+ + "\u06b4\3\2\2\2\u06b6\u06b7\3\2\2\2\u06b7\u06c1\3\2\2\2\u06b8\u06b6\3\2"+ + "\2\2\u06b9\u06bb\7b\2\2\u06ba\u06bc\n\f\2\2\u06bb\u06ba\3\2\2\2\u06bc"+ + "\u06bd\3\2\2\2\u06bd\u06bb\3\2\2\2\u06bd\u06be\3\2\2\2\u06be\u06bf\3\2"+ + "\2\2\u06bf\u06c1\7b\2\2\u06c0\u06af\3\2\2\2\u06c0\u06b9\3\2\2\2\u06c1"+ + "\u0128\3\2\2\2\u06c2\u06c3\7B\2\2\u06c3\u06c4\5\u0127\u0092\2\u06c4\u012a"+ + "\3\2\2\2\u06c5\u06c6\5\u0127\u0092\2\u06c6\u06c7\7B\2\2\u06c7\u012c\3"+ + "\2\2\2\u06c8\u06c9\7&\2\2\u06c9\u06ca\5\u0127\u0092\2\u06ca\u012e\3\2"+ + "\2\2\u06cb\u06ce\7)\2\2\u06cc\u06cf\5\u0131\u0097\2\u06cd\u06cf\13\2\2"+ + "\2\u06ce\u06cc\3\2\2\2\u06ce\u06cd\3\2\2\2\u06cf\u06d0\3\2\2\2\u06d0\u06d1"+ + "\7)\2\2\u06d1\u0130\3\2\2\2\u06d2\u06d5\5\u0133\u0098\2\u06d3\u06d5\5"+ + "\u0135\u0099\2\u06d4\u06d2\3\2\2\2\u06d4\u06d3\3\2\2\2\u06d5\u0132\3\2"+ + "\2\2\u06d6\u06d7\7^\2\2\u06d7\u06d8\7w\2\2\u06d8\u06d9\5\u011d\u008d\2"+ + "\u06d9\u06da\5\u011d\u008d\2\u06da\u06db\5\u011d\u008d\2\u06db\u06dc\5"+ + "\u011d\u008d\2\u06dc\u0134\3\2\2\2\u06dd\u06de\7^\2\2\u06de\u06df\t\r"+ + "\2\2\u06df\u0136\3\2\2\2\u06e0\u06e7\5\u0139\u009b\2\u06e1\u06e7\5\u013b"+ + "\u009c\2\u06e2\u06e7\5\u013d\u009d\2\u06e3\u06e7\5\u013f\u009e\2\u06e4"+ + "\u06e7\5\u0141\u009f\2\u06e5\u06e7\5\u0145\u00a1\2\u06e6\u06e0\3\2\2\2"+ + "\u06e6\u06e1\3\2\2\2\u06e6\u06e2\3\2\2\2\u06e6\u06e3\3\2\2\2\u06e6\u06e4"+ + "\3\2\2\2\u06e6\u06e5\3\2\2\2\u06e7\u0138\3\2\2\2\u06e8\u06e9\t\16\2\2"+ + "\u06e9\u013a\3\2\2\2\u06ea\u06eb\t\17\2\2\u06eb\u013c\3\2\2\2\u06ec\u06ed"+ + "\t\20\2\2\u06ed\u013e\3\2\2\2\u06ee\u06ef\t\21\2\2\u06ef\u0140\3\2\2\2"+ + "\u06f0\u06f1\t\22\2\2\u06f1\u0142\3\2\2\2\u06f2\u06f3\t\23\2\2\u06f3\u0144"+ + "\3\2\2\2\u06f4\u06f5\t\24\2\2\u06f5\u0146\3\2\2\2\u06f6\u06f7\7+\2\2\u06f7"+ + "\u06f8\3\2\2\2\u06f8\u06f9\b\u00a2\7\2\u06f9\u06fa\b\u00a2\b\2\u06fa\u0148"+ + "\3\2\2\2\u06fb\u06fc\7_\2\2\u06fc\u06fd\3\2\2\2\u06fd\u06fe\b\u00a3\7"+ + "\2\u06fe\u06ff\b\u00a3\t\2\u06ff\u014a\3\2\2\2\u0700\u0701\5\27\n\2\u0701"+ + "\u0702\3\2\2\2\u0702\u0703\b\u00a4\4\2\u0703\u0704\b\u00a4\n\2\u0704\u014c"+ + "\3\2\2\2\u0705\u0706\5\33\f\2\u0706\u0707\3\2\2\2\u0707\u0708\b\u00a5"+ + "\4\2\u0708\u0709\b\u00a5\13\2\u0709\u014e\3\2\2\2\u070a\u070b\5\37\16"+ + "\2\u070b\u070c\3\2\2\2\u070c\u070d\b\u00a6\f\2\u070d\u0150\3\2\2\2\u070e"+ + "\u070f\5!\17\2\u070f\u0710\3\2\2\2\u0710\u0711\b\u00a7\r\2\u0711\u0152"+ + "\3\2\2\2\u0712\u0713\5\23\b\2\u0713\u0714\3\2\2\2\u0714\u0715\b\u00a8"+ + "\16\2\u0715\u0154\3\2\2\2\u0716\u0717\5\25\t\2\u0717\u0718\3\2\2\2\u0718"+ + "\u0719\b\u00a9\17\2\u0719\u0156\3\2\2\2\u071a\u071b\5#\20\2\u071b\u071c"+ + "\3\2\2\2\u071c\u071d\b\u00aa\20\2\u071d\u0158\3\2\2\2\u071e\u071f\5%\21"+ + "\2\u071f\u0720\3\2\2\2\u0720\u0721\b\u00ab\21\2\u0721\u015a\3\2\2\2\u0722"+ + "\u0723\5\'\22\2\u0723\u0724\3\2\2\2\u0724\u0725\b\u00ac\22\2\u0725\u015c"+ + "\3\2\2\2\u0726\u0727\5)\23\2\u0727\u0728\3\2\2\2\u0728\u0729\b\u00ad\23"+ + "\2\u0729\u015e\3\2\2\2\u072a\u072b\5+\24\2\u072b\u072c\3\2\2\2\u072c\u072d"+ + "\b\u00ae\24\2\u072d\u0160\3\2\2\2\u072e\u072f\5-\25\2\u072f\u0730\3\2"+ + "\2\2\u0730\u0731\b\u00af\25\2\u0731\u0162\3\2\2\2\u0732\u0733\5/\26\2"+ + "\u0733\u0734\3\2\2\2\u0734\u0735\b\u00b0\26\2\u0735\u0164\3\2\2\2\u0736"+ + "\u0737\5\61\27\2\u0737\u0738\3\2\2\2\u0738\u0739\b\u00b1\27\2\u0739\u0166"+ + "\3\2\2\2\u073a\u073b\5\63\30\2\u073b\u073c\3\2\2\2\u073c\u073d\b\u00b2"+ + "\30\2\u073d\u0168\3\2\2\2\u073e\u073f\5\65\31\2\u073f\u0740\3\2\2\2\u0740"+ + "\u0741\b\u00b3\31\2\u0741\u016a\3\2\2\2\u0742\u0743\5\67\32\2\u0743\u0744"+ + "\3\2\2\2\u0744\u0745\b\u00b4\32\2\u0745\u016c\3\2\2\2\u0746\u0747\59\33"+ + "\2\u0747\u0748\3\2\2\2\u0748\u0749\b\u00b5\33\2\u0749\u016e\3\2\2\2\u074a"+ + "\u074b\5;\34\2\u074b\u074c\3\2\2\2\u074c\u074d\b\u00b6\34\2\u074d\u0170"+ + "\3\2\2\2\u074e\u074f\5=\35\2\u074f\u0750\3\2\2\2\u0750\u0751\b\u00b7\35"+ + "\2\u0751\u0172\3\2\2\2\u0752\u0753\5?\36\2\u0753\u0754\3\2\2\2\u0754\u0755"+ + "\b\u00b8\36\2\u0755\u0174\3\2\2\2\u0756\u0757\5A\37\2\u0757\u0758\3\2"+ + "\2\2\u0758\u0759\b\u00b9\37\2\u0759\u0176\3\2\2\2\u075a\u075b\5C \2\u075b"+ + "\u075c\3\2\2\2\u075c\u075d\b\u00ba \2\u075d\u0178\3\2\2\2\u075e\u075f"+ + "\5E!\2\u075f\u0760\3\2\2\2\u0760\u0761\b\u00bb!\2\u0761\u017a\3\2\2\2"+ + "\u0762\u0763\5G\"\2\u0763\u0764\3\2\2\2\u0764\u0765\b\u00bc\"\2\u0765"+ + "\u017c\3\2\2\2\u0766\u0767\5I#\2\u0767\u0768\3\2\2\2\u0768\u0769\b\u00bd"+ + "#\2\u0769\u017e\3\2\2\2\u076a\u076b\5K$\2\u076b\u076c\3\2\2\2\u076c\u076d"+ + "\b\u00be$\2\u076d\u0180\3\2\2\2\u076e\u076f\5\21\7\2\u076f\u0770\3\2\2"+ + "\2\u0770\u0771\b\u00bf%\2\u0771\u0182\3\2\2\2\u0772\u0773\5M%\2\u0773"+ + "\u0774\3\2\2\2\u0774\u0775\b\u00c0&\2\u0775\u0184\3\2\2\2\u0776\u0777"+ + "\5O&\2\u0777\u0778\3\2\2\2\u0778\u0779\b\u00c1\'\2\u0779\u0186\3\2\2\2"+ + "\u077a\u077b\5Q\'\2\u077b\u077c\3\2\2\2\u077c\u077d\b\u00c2(\2\u077d\u0188"+ + "\3\2\2\2\u077e\u077f\5S(\2\u077f\u0780\3\2\2\2\u0780\u0781\b\u00c3)\2"+ + "\u0781\u018a\3\2\2\2\u0782\u0783\5U)\2\u0783\u0784\3\2\2\2\u0784\u0785"+ + "\b\u00c4*\2\u0785\u018c\3\2\2\2\u0786\u0787\5W*\2\u0787\u0788\3\2\2\2"+ + "\u0788\u0789\b\u00c5+\2\u0789\u018e\3\2\2\2\u078a\u078b\5Y+\2\u078b\u078c"+ + "\3\2\2\2\u078c\u078d\b\u00c6,\2\u078d\u0190\3\2\2\2\u078e\u078f\5[,\2"+ + "\u078f\u0790\3\2\2\2\u0790\u0791\b\u00c7-\2\u0791\u0192\3\2\2\2\u0792"+ + "\u0793\5]-\2\u0793\u0794\3\2\2\2\u0794\u0795\b\u00c8.\2\u0795\u0194\3"+ + "\2\2\2\u0796\u0797\5_.\2\u0797\u0798\3\2\2\2\u0798\u0799\b\u00c9/\2\u0799"+ + "\u0196\3\2\2\2\u079a\u079b\5a/\2\u079b\u079c\3\2\2\2\u079c\u079d\b\u00ca"+ + "\60\2\u079d\u0198\3\2\2\2\u079e\u079f\5c\60\2\u079f\u07a0\3\2\2\2\u07a0"+ + "\u07a1\b\u00cb\61\2\u07a1\u019a\3\2\2\2\u07a2\u07a3\5e\61\2\u07a3\u07a4"+ + "\3\2\2\2\u07a4\u07a5\b\u00cc\62\2\u07a5\u019c\3\2\2\2\u07a6\u07a7\5\u00b9"+ + "[\2\u07a7\u07a8\3\2\2\2\u07a8\u07a9\b\u00cd\63\2\u07a9\u019e\3\2\2\2\u07aa"+ + "\u07ab\5\u00bb\\\2\u07ab\u07ac\3\2\2\2\u07ac\u07ad\b\u00ce\64\2\u07ad"+ + "\u01a0\3\2\2\2\u07ae\u07af\5g\62\2\u07af\u07b0\3\2\2\2\u07b0\u07b1\b\u00cf"+ + "\65\2\u07b1\u01a2\3\2\2\2\u07b2\u07b3\5i\63\2\u07b3\u07b4\3\2\2\2\u07b4"+ + "\u07b5\b\u00d0\66\2\u07b5\u01a4\3\2\2\2\u07b6\u07b7\5k\64\2\u07b7\u07b8"+ + "\3\2\2\2\u07b8\u07b9\b\u00d1\67\2\u07b9\u01a6\3\2\2\2\u07ba\u07bb\5m\65"+ + "\2\u07bb\u07bc\3\2\2\2\u07bc\u07bd\b\u00d28\2\u07bd\u01a8\3\2\2\2\u07be"+ + "\u07bf\5\u0107\u0082\2\u07bf\u07c0\3\2\2\2\u07c0\u07c1\b\u00d3\5\2\u07c1"+ + "\u07c2\b\u00d39\2\u07c2\u01aa\3\2\2\2\u07c3\u07c4\5\u0109\u0083\2\u07c4"+ + "\u07c5\3\2\2\2\u07c5\u07c6\b\u00d4\6\2\u07c6\u07c7\b\u00d4:\2\u07c7\u01ac"+ + "\3\2\2\2\u07c8\u07c9\5\u0083@\2\u07c9\u07ca\3\2\2\2\u07ca\u07cb\b\u00d5"+ + ";\2\u07cb\u01ae\3\2\2\2\u07cc\u07cd\5\u0085A\2\u07cd\u07ce\3\2\2\2\u07ce"+ + "\u07cf\b\u00d6<\2\u07cf\u01b0\3\2\2\2\u07d0\u07d1\5\u0081?\2\u07d1\u07d2"+ + "\3\2\2\2\u07d2\u07d3\b\u00d7=\2\u07d3\u01b2\3\2\2\2\u07d4\u07d5\5\u0093"+ + "H\2\u07d5\u07d6\3\2\2\2\u07d6\u07d7\b\u00d8>\2\u07d7\u01b4\3\2\2\2\u07d8"+ + "\u07d9\5\u00b7Z\2\u07d9\u07da\3\2\2\2\u07da\u07db\b\u00d9?\2\u07db\u01b6"+ + "\3\2\2\2\u07dc\u07dd\5\u00bd]\2\u07dd\u07de\3\2\2\2\u07de\u07df\b\u00da"+ + "@"; + private static final String _serializedATNSegment1 = + "\2\u07df\u01b8\3\2\2\2\u07e0\u07e1\5\u00bf^\2\u07e1\u07e2\3\2\2\2\u07e2"+ + "\u07e3\b\u00dbA\2\u07e3\u01ba\3\2\2\2\u07e4\u07e5\5u9\2\u07e5\u07e6\3"+ + "\2\2\2\u07e6\u07e7\b\u00dcB\2\u07e7\u01bc\3\2\2\2\u07e8\u07e9\5\u00c1"+ + "_\2\u07e9\u07ea\3\2\2\2\u07ea\u07eb\b\u00ddC\2\u07eb\u01be\3\2\2\2\u07ec"+ + "\u07ed\5\u00c3`\2\u07ed\u07ee\3\2\2\2\u07ee\u07ef\b\u00deD\2\u07ef\u01c0"+ + "\3\2\2\2\u07f0\u07f1\5\u00c5a\2\u07f1\u07f2\3\2\2\2\u07f2\u07f3\b\u00df"+ + "E\2\u07f3\u01c2\3\2\2\2\u07f4\u07f5\5\u00cbd\2\u07f5\u07f6\3\2\2\2\u07f6"+ + "\u07f7\b\u00e0F\2\u07f7\u01c4\3\2\2\2\u07f8\u07f9\5\u00cde\2\u07f9\u07fa"+ + "\3\2\2\2\u07fa\u07fb\b\u00e1G\2\u07fb\u01c6\3\2\2\2\u07fc\u07fd\5\u00cf"+ + "f\2\u07fd\u07fe\3\2\2\2\u07fe\u07ff\b\u00e2H\2\u07ff\u01c8\3\2\2\2\u0800"+ + "\u0801\5\u00d1g\2\u0801\u0802\3\2\2\2\u0802\u0803\b\u00e3I\2\u0803\u01ca"+ + "\3\2\2\2\u0804\u0805\5\u00abT\2\u0805\u0806\3\2\2\2\u0806\u0807\b\u00e4"+ + "J\2\u0807\u01cc\3\2\2\2\u0808\u0809\5\u00adU\2\u0809\u080a\3\2\2\2\u080a"+ + "\u080b\b\u00e5K\2\u080b\u01ce\3\2\2\2\u080c\u080d\5\u00afV\2\u080d\u080e"+ + "\3\2\2\2\u080e\u080f\b\u00e6L\2\u080f\u01d0\3\2\2\2\u0810\u0811\5\u00b1"+ + "W\2\u0811\u0812\3\2\2\2\u0812\u0813\b\u00e7M\2\u0813\u01d2\3\2\2\2\u0814"+ + "\u0815\5o\66\2\u0815\u0816\3\2\2\2\u0816\u0817\b\u00e8N\2\u0817\u01d4"+ + "\3\2\2\2\u0818\u0819\5q\67\2\u0819\u081a\3\2\2\2\u081a\u081b\b\u00e9O"+ + "\2\u081b\u01d6\3\2\2\2\u081c\u081d\5s8\2\u081d\u081e\3\2\2\2\u081e\u081f"+ + "\b\u00eaP\2\u081f\u01d8\3\2\2\2\u0820\u0821\5\u0099K\2\u0821\u0822\3\2"+ + "\2\2\u0822\u0823\b\u00ebQ\2\u0823\u01da\3\2\2\2\u0824\u0825\5\u009bL\2"+ + "\u0825\u0826\3\2\2\2\u0826\u0827\b\u00ecR\2\u0827\u01dc\3\2\2\2\u0828"+ + "\u0829\5\u009dM\2\u0829\u082a\3\2\2\2\u082a\u082b\b\u00edS\2\u082b\u01de"+ + "\3\2\2\2\u082c\u082d\5\u009fN\2\u082d\u082e\3\2\2\2\u082e\u082f\b\u00ee"+ + "T\2\u082f\u01e0\3\2\2\2\u0830\u0831\5\u00a1O\2\u0831\u0832\3\2\2\2\u0832"+ + "\u0833\b\u00efU\2\u0833\u01e2\3\2\2\2\u0834\u0835\5\u00a3P\2\u0835\u0836"+ + "\3\2\2\2\u0836\u0837\b\u00f0V\2\u0837\u01e4\3\2\2\2\u0838\u0839\5\u00a5"+ + "Q\2\u0839\u083a\3\2\2\2\u083a\u083b\b\u00f1W\2\u083b\u01e6\3\2\2\2\u083c"+ + "\u083d\5\u00a7R\2\u083d\u083e\3\2\2\2\u083e\u083f\b\u00f2X\2\u083f\u01e8"+ + "\3\2\2\2\u0840\u0841\5\u00a9S\2\u0841\u0842\3\2\2\2\u0842\u0843\b\u00f3"+ + "Y\2\u0843\u01ea\3\2\2\2\u0844\u0845\5\u00d5i\2\u0845\u0846\3\2\2\2\u0846"+ + "\u0847\b\u00f4Z\2\u0847\u01ec\3\2\2\2\u0848\u0849\5\u00d7j\2\u0849\u084a"+ + "\3\2\2\2\u084a\u084b\b\u00f5[\2\u084b\u01ee\3\2\2\2\u084c\u084d\5\u00d9"+ + "k\2\u084d\u084e\3\2\2\2\u084e\u084f\b\u00f6\\\2\u084f\u01f0\3\2\2\2\u0850"+ + "\u0851\5\u00dbl\2\u0851\u0852\3\2\2\2\u0852\u0853\b\u00f7]\2\u0853\u01f2"+ + "\3\2\2\2\u0854\u0855\5\u00ddm\2\u0855\u0856\3\2\2\2\u0856\u0857\b\u00f8"+ + "^\2\u0857\u01f4\3\2\2\2\u0858\u0859\5\u00dfn\2\u0859\u085a\3\2\2\2\u085a"+ + "\u085b\b\u00f9_\2\u085b\u01f6\3\2\2\2\u085c\u085d\5\u00e1o\2\u085d\u085e"+ + "\3\2\2\2\u085e\u085f\b\u00fa`\2\u085f\u01f8\3\2\2\2\u0860\u0861\5\u00e3"+ + "p\2\u0861\u0862\3\2\2\2\u0862\u0863\b\u00fba\2\u0863\u01fa\3\2\2\2\u0864"+ + "\u0865\5\u00e5q\2\u0865\u0866\3\2\2\2\u0866\u0867\b\u00fcb\2\u0867\u01fc"+ + "\3\2\2\2\u0868\u0869\5\u00e7r\2\u0869\u086a\3\2\2\2\u086a\u086b\b\u00fd"+ + "c\2\u086b\u01fe\3\2\2\2\u086c\u086d\5\u00e9s\2\u086d\u086e\3\2\2\2\u086e"+ + "\u086f\b\u00fed\2\u086f\u0200\3\2\2\2\u0870\u0871\5\u00ebt\2\u0871\u0872"+ + "\3\2\2\2\u0872\u0873\b\u00ffe\2\u0873\u0202\3\2\2\2\u0874\u0875\5\u00ed"+ + "u\2\u0875\u0876\3\2\2\2\u0876\u0877\b\u0100f\2\u0877\u0204\3\2\2\2\u0878"+ + "\u0879\5\u00efv\2\u0879\u087a\3\2\2\2\u087a\u087b\b\u0101g\2\u087b\u0206"+ + "\3\2\2\2\u087c\u087d\5\u00f1w\2\u087d\u087e\3\2\2\2\u087e\u087f\b\u0102"+ + "h\2\u087f\u0208\3\2\2\2\u0880\u0881\5\u00f3x\2\u0881\u0882\3\2\2\2\u0882"+ + "\u0883\b\u0103i\2\u0883\u020a\3\2\2\2\u0884\u0885\5\u00f5y\2\u0885\u0886"+ + "\3\2\2\2\u0886\u0887\b\u0104j\2\u0887\u020c\3\2\2\2\u0888\u0889\5\u00f7"+ + "z\2\u0889\u088a\3\2\2\2\u088a\u088b\b\u0105k\2\u088b\u020e\3\2\2\2\u088c"+ + "\u088d\5\u00f9{\2\u088d\u088e\3\2\2\2\u088e\u088f\b\u0106l\2\u088f\u0210"+ + "\3\2\2\2\u0890\u0891\5\u00fb|\2\u0891\u0892\3\2\2\2\u0892\u0893\b\u0107"+ + "m\2\u0893\u0212\3\2\2\2\u0894\u0895\5\u00fd}\2\u0895\u0896\3\2\2\2\u0896"+ + "\u0897\b\u0108n\2\u0897\u0214\3\2\2\2\u0898\u0899\5\u00ff~\2\u0899\u089a"+ + "\3\2\2\2\u089a\u089b\b\u0109o\2\u089b\u0216\3\2\2\2\u089c\u089d\5\u0101"+ + "\177\2\u089d\u089e\3\2\2\2\u089e\u089f\b\u010ap\2\u089f\u0218\3\2\2\2"+ + "\u08a0\u08a1\5\u0103\u0080\2\u08a1\u08a2\3\2\2\2\u08a2\u08a3\b\u010bq"+ + "\2\u08a3\u021a\3\2\2\2\u08a4\u08a5\5\u0105\u0081\2\u08a5\u08a6\3\2\2\2"+ + "\u08a6\u08a7\b\u010cr\2\u08a7\u021c\3\2\2\2\u08a8\u08a9\5\u0123\u0090"+ + "\2\u08a9\u08aa\3\2\2\2\u08aa\u08ab\b\u010ds\2\u08ab\u021e\3\2\2\2\u08ac"+ + "\u08ad\5\u0113\u0088\2\u08ad\u08ae\3\2\2\2\u08ae\u08af\b\u010et\2\u08af"+ + "\u0220\3\2\2\2\u08b0\u08b1\5\u011b\u008c\2\u08b1\u08b2\3\2\2\2\u08b2\u08b3"+ + "\b\u010fu\2\u08b3\u0222\3\2\2\2\u08b4\u08b5\5\u011f\u008e\2\u08b5\u08b6"+ + "\3\2\2\2\u08b6\u08b7\b\u0110v\2\u08b7\u0224\3\2\2\2\u08b8\u08b9\5\u012f"+ + "\u0096\2\u08b9\u08ba\3\2\2\2\u08ba\u08bb\b\u0111w\2\u08bb\u0226\3\2\2"+ + "\2\u08bc\u08bd\5\u010b\u0084\2\u08bd\u08be\3\2\2\2\u08be\u08bf\b\u0112"+ + "x\2\u08bf\u0228\3\2\2\2\u08c0\u08c1\5\u0125\u0091\2\u08c1\u08c2\3\2\2"+ + "\2\u08c2\u08c3\b\u0113y\2\u08c3\u022a\3\2\2\2\u08c4\u08c5\5\u0111\u0087"+ + "\2\u08c5\u08c6\3\2\2\2\u08c6\u08c7\b\u0114z\2\u08c7\u022c\3\2\2\2\u08c8"+ + "\u08c9\5\u0127\u0092\2\u08c9\u08ca\3\2\2\2\u08ca\u08cb\b\u0115{\2\u08cb"+ + "\u022e\3\2\2\2\u08cc\u08cd\5\u0129\u0093\2\u08cd\u08ce\3\2\2\2\u08ce\u08cf"+ + "\b\u0116|\2\u08cf\u0230\3\2\2\2\u08d0\u08d1\5\u012b\u0094\2\u08d1\u08d2"+ + "\3\2\2\2\u08d2\u08d3\b\u0117}\2\u08d3\u0232\3\2\2\2\u08d4\u08d7\5\13\4"+ + "\2\u08d5\u08d7\5\t\3\2\u08d6\u08d4\3\2\2\2\u08d6\u08d5\3\2\2\2\u08d7\u08d8"+ + "\3\2\2\2\u08d8\u08d9\b\u0118\2\2\u08d9\u0234\3\2\2\2\u08da\u08db\5\r\5"+ + "\2\u08db\u08dc\3\2\2\2\u08dc\u08dd\b\u0119\3\2\u08dd\u0236\3\2\2\2\u08de"+ + "\u08df\5\17\6\2\u08df\u08e0\3\2\2\2\u08e0\u08e1\b\u011a\3\2\u08e1\u0238"+ + "\3\2\2\2\u08e2\u08e3\7$\2\2\u08e3\u08e4\3\2\2\2\u08e4\u08e5\b\u011b\7"+ + "\2\u08e5\u023a\3\2\2\2\u08e6\u08e7\5\u012d\u0095\2\u08e7\u023c\3\2\2\2"+ + "\u08e8\u08ea\n\25\2\2\u08e9\u08e8\3\2\2\2\u08ea\u08eb\3\2\2\2\u08eb\u08e9"+ + "\3\2\2\2\u08eb\u08ec\3\2\2\2\u08ec\u08ef\3\2\2\2\u08ed\u08ef\7&\2\2\u08ee"+ + "\u08e9\3\2\2\2\u08ee\u08ed\3\2\2\2\u08ef\u023e\3\2\2\2\u08f0\u08f1\7^"+ + "\2\2\u08f1\u08f4\13\2\2\2\u08f2\u08f4\5\u0133\u0098\2\u08f3\u08f0\3\2"+ + "\2\2\u08f3\u08f2\3\2\2\2\u08f4\u0240\3\2\2\2\u08f5\u08f6\7&\2\2\u08f6"+ + "\u08f7\7}\2\2\u08f7\u08f8\3\2\2\2\u08f8\u08f9\b\u011f~\2\u08f9\u0242\3"+ + "\2\2\2\u08fa\u08fc\5\u0245\u0121\2\u08fb\u08fa\3\2\2\2\u08fb\u08fc\3\2"+ + "\2\2\u08fc\u08fd\3\2\2\2\u08fd\u08fe\7$\2\2\u08fe\u08ff\7$\2\2\u08ff\u0900"+ + "\7$\2\2\u0900\u0901\3\2\2\2\u0901\u0902\b\u0120\7\2\u0902\u0244\3\2\2"+ + "\2\u0903\u0905\7$\2\2\u0904\u0903\3\2\2\2\u0905\u0906\3\2\2\2\u0906\u0904"+ + "\3\2\2\2\u0906\u0907\3\2\2\2\u0907\u0246\3\2\2\2\u0908\u0909\5\u012d\u0095"+ + "\2\u0909\u0248\3\2\2\2\u090a\u090c\n\25\2\2\u090b\u090a\3\2\2\2\u090c"+ + "\u090d\3\2\2\2\u090d\u090b\3\2\2\2\u090d\u090e\3\2\2\2\u090e\u0911\3\2"+ + "\2\2\u090f\u0911\7&\2\2\u0910\u090b\3\2\2\2\u0910\u090f\3\2\2\2\u0911"+ + "\u024a\3\2\2\2\u0912\u0913\7^\2\2\u0913\u0914\13\2\2\2\u0914\u024c\3\2"+ + "\2\2\u0915\u0916\7&\2\2\u0916\u0917\7}\2\2\u0917\u0918\3\2\2\2\u0918\u0919"+ + "\b\u0125~\2\u0919\u024e\3\2\2\2\u091a\u091b\5\17\6\2\u091b\u091c\3\2\2"+ + "\2\u091c\u091d\b\u0126\3\2\u091d\u0250\3\2\2\2\u091e\u091f\5!\17\2\u091f"+ + "\u0920\3\2\2\2\u0920\u0921\b\u0127\7\2\u0921\u0922\b\u0127\r\2\u0922\u0252"+ + "\3\2\2\2\u0923\u0924\5\27\n\2\u0924\u0925\3\2\2\2\u0925\u0926\b\u0128"+ + "\4\2\u0926\u0927\b\u0128\n\2\u0927\u0254\3\2\2\2\u0928\u0929\5\33\f\2"+ + "\u0929\u092a\3\2\2\2\u092a\u092b\b\u0129\4\2\u092b\u092c\b\u0129\13\2"+ + "\u092c\u0256\3\2\2\2\u092d\u092e\7+\2\2\u092e\u092f\3\2\2\2\u092f\u0930"+ + "\b\u012a\b\2\u0930\u0258\3\2\2\2\u0931\u0932\7_\2\2\u0932\u0933\3\2\2"+ + "\2\u0933\u0934\b\u012b\t\2\u0934\u025a\3\2\2\2\u0935\u0936\5\37\16\2\u0936"+ + "\u0937\3\2\2\2\u0937\u0938\b\u012c~\2\u0938\u0939\b\u012c\f\2\u0939\u025c"+ + "\3\2\2\2\u093a\u093b\5\23\b\2\u093b\u093c\3\2\2\2\u093c\u093d\b\u012d"+ + "\16\2\u093d\u025e\3\2\2\2\u093e\u093f\5\25\t\2\u093f\u0940\3\2\2\2\u0940"+ + "\u0941\b\u012e\17\2\u0941\u0260\3\2\2\2\u0942\u0943\5#\20\2\u0943\u0944"+ + "\3\2\2\2\u0944\u0945\b\u012f\20\2\u0945\u0262\3\2\2\2\u0946\u0947\5%\21"+ + "\2\u0947\u0948\3\2\2\2\u0948\u0949\b\u0130\21\2\u0949\u0264\3\2\2\2\u094a"+ + "\u094b\5\'\22\2\u094b\u094c\3\2\2\2\u094c\u094d\b\u0131\22\2\u094d\u0266"+ + "\3\2\2\2\u094e\u094f\5)\23\2\u094f\u0950\3\2\2\2\u0950\u0951\b\u0132\23"+ + "\2\u0951\u0268\3\2\2\2\u0952\u0953\5+\24\2\u0953\u0954\3\2\2\2\u0954\u0955"+ + "\b\u0133\24\2\u0955\u026a\3\2\2\2\u0956\u0957\5-\25\2\u0957\u0958\3\2"+ + "\2\2\u0958\u0959\b\u0134\25\2\u0959\u026c\3\2\2\2\u095a\u095b\5/\26\2"+ + "\u095b\u095c\3\2\2\2\u095c\u095d\b\u0135\26\2\u095d\u026e\3\2\2\2\u095e"+ + "\u095f\5\61\27\2\u095f\u0960\3\2\2\2\u0960\u0961\b\u0136\27\2\u0961\u0270"+ + "\3\2\2\2\u0962\u0963\5\63\30\2\u0963\u0964\3\2\2\2\u0964\u0965\b\u0137"+ + "\30\2\u0965\u0272\3\2\2\2\u0966\u0967\5\65\31\2\u0967\u0968\3\2\2\2\u0968"+ + "\u0969\b\u0138\31\2\u0969\u0274\3\2\2\2\u096a\u096b\5\67\32\2\u096b\u096c"+ + "\3\2\2\2\u096c\u096d\b\u0139\32\2\u096d\u0276\3\2\2\2\u096e\u096f\59\33"+ + "\2\u096f\u0970\3\2\2\2\u0970\u0971\b\u013a\33\2\u0971\u0278\3\2\2\2\u0972"+ + "\u0973\5;\34\2\u0973\u0974\3\2\2\2\u0974\u0975\b\u013b\34\2\u0975\u027a"+ + "\3\2\2\2\u0976\u0977\5=\35\2\u0977\u0978\3\2\2\2\u0978\u0979\b\u013c\35"+ + "\2\u0979\u027c\3\2\2\2\u097a\u097b\5?\36\2\u097b\u097c\3\2\2\2\u097c\u097d"+ + "\b\u013d\36\2\u097d\u027e\3\2\2\2\u097e\u097f\5A\37\2\u097f\u0980\3\2"+ + "\2\2\u0980\u0981\b\u013e\37\2\u0981\u0280\3\2\2\2\u0982\u0983\5C \2\u0983"+ + "\u0984\3\2\2\2\u0984\u0985\b\u013f \2\u0985\u0282\3\2\2\2\u0986\u0987"+ + "\5E!\2\u0987\u0988\3\2\2\2\u0988\u0989\b\u0140!\2\u0989\u0284\3\2\2\2"+ + "\u098a\u098b\5G\"\2\u098b\u098c\3\2\2\2\u098c\u098d\b\u0141\"\2\u098d"+ + "\u0286\3\2\2\2\u098e\u098f\5I#\2\u098f\u0990\3\2\2\2\u0990\u0991\b\u0142"+ + "#\2\u0991\u0288\3\2\2\2\u0992\u0993\5K$\2\u0993\u0994\3\2\2\2\u0994\u0995"+ + "\b\u0143$\2\u0995\u028a\3\2\2\2\u0996\u0997\5M%\2\u0997\u0998\3\2\2\2"+ + "\u0998\u0999\b\u0144&\2\u0999\u028c\3\2\2\2\u099a\u099b\5O&\2\u099b\u099c"+ + "\3\2\2\2\u099c\u099d\b\u0145\'\2\u099d\u028e\3\2\2\2\u099e\u099f\5Q\'"+ + "\2\u099f\u09a0\3\2\2\2\u09a0\u09a1\b\u0146(\2\u09a1\u0290\3\2\2\2\u09a2"+ + "\u09a3\5S(\2\u09a3\u09a4\3\2\2\2\u09a4\u09a5\b\u0147)\2\u09a5\u0292\3"+ + "\2\2\2\u09a6\u09a7\5U)\2\u09a7\u09a8\3\2\2\2\u09a8\u09a9\b\u0148*\2\u09a9"+ + "\u0294\3\2\2\2\u09aa\u09ab\5W*\2\u09ab\u09ac\3\2\2\2\u09ac\u09ad\b\u0149"+ + "+\2\u09ad\u0296\3\2\2\2\u09ae\u09af\5Y+\2\u09af\u09b0\3\2\2\2\u09b0\u09b1"+ + "\b\u014a,\2\u09b1\u0298\3\2\2\2\u09b2\u09b3\5[,\2\u09b3\u09b4\3\2\2\2"+ + "\u09b4\u09b5\b\u014b-\2\u09b5\u029a\3\2\2\2\u09b6\u09b7\5]-\2\u09b7\u09b8"+ + "\3\2\2\2\u09b8\u09b9\b\u014c.\2\u09b9\u029c\3\2\2\2\u09ba\u09bb\5_.\2"+ + "\u09bb\u09bc\3\2\2\2\u09bc\u09bd\b\u014d/\2\u09bd\u029e\3\2\2\2\u09be"+ + "\u09bf\5a/\2\u09bf\u09c0\3\2\2\2\u09c0\u09c1\b\u014e\60\2\u09c1\u02a0"+ + "\3\2\2\2\u09c2\u09c3\5c\60\2\u09c3\u09c4\3\2\2\2\u09c4\u09c5\b\u014f\61"+ + "\2\u09c5\u02a2\3\2\2\2\u09c6\u09c7\5e\61\2\u09c7\u09c8\3\2\2\2\u09c8\u09c9"+ + "\b\u0150\62\2\u09c9\u02a4\3\2\2\2\u09ca\u09cb\5\u00b3X\2\u09cb\u09cc\3"+ + "\2\2\2\u09cc\u09cd\b\u0151\177\2\u09cd\u02a6\3\2\2\2\u09ce\u09cf\5\u00b5"+ + "Y\2\u09cf\u09d0\3\2\2\2\u09d0\u09d1\b\u0152?\2\u09d1\u02a8\3\2\2\2\u09d2"+ + "\u09d3\5\u00b7Z\2\u09d3\u02aa\3\2\2\2\u09d4\u09d5\5\u00b9[\2\u09d5\u09d6"+ + "\3\2\2\2\u09d6\u09d7\b\u0154\63\2\u09d7\u02ac\3\2\2\2\u09d8\u09d9\5\u00bb"+ + "\\\2\u09d9\u09da\3\2\2\2\u09da\u09db\b\u0155\64\2\u09db\u02ae\3\2\2\2"+ + "\u09dc\u09dd\5g\62\2\u09dd\u09de\3\2\2\2\u09de\u09df\b\u0156\65\2\u09df"+ + "\u02b0\3\2\2\2\u09e0\u09e1\5i\63\2\u09e1\u09e2\3\2\2\2\u09e2\u09e3\b\u0157"+ + "\66\2\u09e3\u02b2\3\2\2\2\u09e4\u09e5\5k\64\2\u09e5\u09e6\3\2\2\2\u09e6"+ + "\u09e7\b\u0158\67\2\u09e7\u02b4\3\2\2\2\u09e8\u09e9\5m\65\2\u09e9\u09ea"+ + "\3\2\2\2\u09ea\u09eb\b\u01598\2\u09eb\u02b6\3\2\2\2\u09ec\u09ed\5\u0107"+ + "\u0082\2\u09ed\u09ee\3\2\2\2\u09ee\u09ef\b\u015a\5\2\u09ef\u09f0\b\u015a"+ + "9\2\u09f0\u02b8\3\2\2\2\u09f1\u09f2\5\u0109\u0083\2\u09f2\u09f3\3\2\2"+ + "\2\u09f3\u09f4\b\u015b\6\2\u09f4\u09f5\b\u015b:\2\u09f5\u02ba\3\2\2\2"+ + "\u09f6\u09f7\5\u0123\u0090\2\u09f7\u09f8\3\2\2\2\u09f8\u09f9\b\u015cs"+ + "\2\u09f9\u02bc\3\2\2\2\u09fa\u09fb\5\u0113\u0088\2\u09fb\u09fc\3\2\2\2"+ + "\u09fc\u09fd\b\u015dt\2\u09fd\u02be\3\2\2\2\u09fe\u09ff\5\u011b\u008c"+ + "\2\u09ff\u0a00\3\2\2\2\u0a00\u0a01\b\u015eu\2\u0a01\u02c0\3\2\2\2\u0a02"+ + "\u0a03\5\u011f\u008e\2\u0a03\u0a04\3\2\2\2\u0a04\u0a05\b\u015fv\2\u0a05"+ + "\u02c2\3\2\2\2\u0a06\u0a07\5\u012f\u0096\2\u0a07\u0a08\3\2\2\2\u0a08\u0a09"+ + "\b\u0160w\2\u0a09\u02c4\3\2\2\2\u0a0a\u0a0b\5\u010b\u0084\2\u0a0b\u0a0c"+ + "\3\2\2\2\u0a0c\u0a0d\b\u0161x\2\u0a0d\u02c6\3\2\2\2\u0a0e\u0a0f\5\u0125"+ + "\u0091\2\u0a0f\u0a10\3\2\2\2\u0a10\u0a11\b\u0162y\2\u0a11\u02c8\3\2\2"+ + "\2\u0a12\u0a13\5\u0111\u0087\2\u0a13\u0a14\3\2\2\2\u0a14\u0a15\b\u0163"+ + "z\2\u0a15\u02ca\3\2\2\2\u0a16\u0a17\5\u0127\u0092\2\u0a17\u0a18\3\2\2"+ + "\2\u0a18\u0a19\b\u0164{\2\u0a19\u02cc\3\2\2\2\u0a1a\u0a1b\5\u0129\u0093"+ + "\2\u0a1b\u0a1c\3\2\2\2\u0a1c\u0a1d\b\u0165|\2\u0a1d\u02ce\3\2\2\2\u0a1e"+ + "\u0a1f\5\u012b\u0094\2\u0a1f\u0a20\3\2\2\2\u0a20\u0a21\b\u0166}\2\u0a21"+ + "\u02d0\3\2\2\2\u0a22\u0a25\5\13\4\2\u0a23\u0a25\5\t\3\2\u0a24\u0a22\3"+ + "\2\2\2\u0a24\u0a23\3\2\2\2\u0a25\u0a26\3\2\2\2\u0a26\u0a27\b\u0167\2\2"+ + "\u0a27\u02d2\3\2\2\2\u0a28\u0a29\5\r\5\2\u0a29\u0a2a\3\2\2\2\u0a2a\u0a2b"+ + "\b\u0168\3\2\u0a2b\u02d4\3\2\2\2\u0a2c\u0a2d\5\17\6\2\u0a2d\u0a2e\3\2"+ + "\2\2\u0a2e\u0a2f\b\u0169\3\2\u0a2f\u02d6\3\2\2\2W\2\3\4\5\6\u02dd\u02e7"+ + "\u02e9\u02f7\u0303\u0472\u0474\u047c\u047e\u05a3\u05a7\u05af\u05b3\u05b9"+ + "\u05bb\u05c0\u05c3\u05c8\u05cd\u05cf\u05d6\u05da\u05df\u05e4\u05e8\u05ed"+ + "\u05ef\u05f6\u05f8\u05fd\u0602\u0607\u0609\u060e\u0613\u0615\u0619\u061e"+ + "\u0627\u062d\u062f\u0637\u063c\u0641\u0647\u064c\u0651\u0653\u065a\u065c"+ + "\u0661\u0666\u066b\u066d\u0672\u0677\u0679\u067d\u068a\u068c\u0696\u0698"+ + "\u06a6\u06af\u06b4\u06b6\u06bd\u06c0\u06ce\u06d4\u06e6\u08d6\u08eb\u08ee"+ + "\u08f3\u08fb\u0906\u090d\u0910\u0a24\u0080\2\3\2\b\2\2\7\3\2\7\4\2\7\5"+ + "\2\6\2\2\t\f\2\t\16\2\t\13\2\t\r\2\t\17\2\t\20\2\t\t\2\t\n\2\t\21\2\t"+ + "\22\2\t\23\2\t\24\2\t\25\2\t\26\2\t\27\2\t\30\2\t\31\2\t\32\2\t\33\2\t"+ + "\34\2\t\35\2\t\36\2\t\37\2\t \2\t!\2\t\"\2\t#\2\t$\2\t%\2\t\b\2\t&\2\t"+ + "\'\2\t(\2\t)\2\t*\2\t+\2\t,\2\t-\2\t.\2\t/\2\t\60\2\t\61\2\t\62\2\t\\"+ + "\2\t]\2\t\63\2\t\64\2\t\65\2\t\66\2\t\u0083\2\t\u0084\2\tA\2\tB\2\t@\2"+ + "\tI\2\t[\2\t^\2\t_\2\t:\2\t`\2\ta\2\tb\2\te\2\tf\2\tg\2\th\2\tU\2\tV\2"+ + "\tW\2\tX\2\t\67\2\t8\2\t9\2\tL\2\tM\2\tN\2\tO\2\tP\2\tQ\2\tR\2\tS\2\t"+ + "T\2\tj\2\tk\2\tl\2\tm\2\tn\2\to\2\tp\2\tq\2\tr\2\ts\2\tt\2\tu\2\tv\2\t"+ + "w\2\tx\2\ty\2\tz\2\t{\2\t|\2\t}\2\t~\2\t\177\2\t\u0080\2\t\u0081\2\t\u0082"+ + "\2\t\u008c\2\t\u0089\2\t\u008a\2\t\u008b\2\t\u0092\2\t\u0085\2\t\u008d"+ + "\2\t\u0088\2\t\u008e\2\t\u008f\2\t\u0090\2\7\6\2\tZ\2"; + public static final String _serializedATN = Utils.join( + new String[] { + _serializedATNSegment0, + _serializedATNSegment1 + }, + "" + ); + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + static { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/kotlin/UnicodeClasses.g4 b/app/src/main/java/com/tyron/code/language/kotlin/UnicodeClasses.g4 new file mode 100644 index 00000000..14d1caeb --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/kotlin/UnicodeClasses.g4 @@ -0,0 +1,1647 @@ +/** + * Taken from http://www.antlr3.org/grammar/1345144569663/AntlrUnicode.txt + */ + +lexer grammar UnicodeClasses; + +UNICODE_CLASS_LL: + '\u0061'..'\u007A' | + '\u00B5' | + '\u00DF'..'\u00F6' | + '\u00F8'..'\u00FF' | + '\u0101' | + '\u0103' | + '\u0105' | + '\u0107' | + '\u0109' | + '\u010B' | + '\u010D' | + '\u010F' | + '\u0111' | + '\u0113' | + '\u0115' | + '\u0117' | + '\u0119' | + '\u011B' | + '\u011D' | + '\u011F' | + '\u0121' | + '\u0123' | + '\u0125' | + '\u0127' | + '\u0129' | + '\u012B' | + '\u012D' | + '\u012F' | + '\u0131' | + '\u0133' | + '\u0135' | + '\u0137' | + '\u0138' | + '\u013A' | + '\u013C' | + '\u013E' | + '\u0140' | + '\u0142' | + '\u0144' | + '\u0146' | + '\u0148' | + '\u0149' | + '\u014B' | + '\u014D' | + '\u014F' | + '\u0151' | + '\u0153' | + '\u0155' | + '\u0157' | + '\u0159' | + '\u015B' | + '\u015D' | + '\u015F' | + '\u0161' | + '\u0163' | + '\u0165' | + '\u0167' | + '\u0169' | + '\u016B' | + '\u016D' | + '\u016F' | + '\u0171' | + '\u0173' | + '\u0175' | + '\u0177' | + '\u017A' | + '\u017C' | + '\u017E'..'\u0180' | + '\u0183' | + '\u0185' | + '\u0188' | + '\u018C' | + '\u018D' | + '\u0192' | + '\u0195' | + '\u0199'..'\u019B' | + '\u019E' | + '\u01A1' | + '\u01A3' | + '\u01A5' | + '\u01A8' | + '\u01AA' | + '\u01AB' | + '\u01AD' | + '\u01B0' | + '\u01B4' | + '\u01B6' | + '\u01B9' | + '\u01BA' | + '\u01BD'..'\u01BF' | + '\u01C6' | + '\u01C9' | + '\u01CC' | + '\u01CE' | + '\u01D0' | + '\u01D2' | + '\u01D4' | + '\u01D6' | + '\u01D8' | + '\u01DA' | + '\u01DC' | + '\u01DD' | + '\u01DF' | + '\u01E1' | + '\u01E3' | + '\u01E5' | + '\u01E7' | + '\u01E9' | + '\u01EB' | + '\u01ED' | + '\u01EF' | + '\u01F0' | + '\u01F3' | + '\u01F5' | + '\u01F9' | + '\u01FB' | + '\u01FD' | + '\u01FF' | + '\u0201' | + '\u0203' | + '\u0205' | + '\u0207' | + '\u0209' | + '\u020B' | + '\u020D' | + '\u020F' | + '\u0211' | + '\u0213' | + '\u0215' | + '\u0217' | + '\u0219' | + '\u021B' | + '\u021D' | + '\u021F' | + '\u0221' | + '\u0223' | + '\u0225' | + '\u0227' | + '\u0229' | + '\u022B' | + '\u022D' | + '\u022F' | + '\u0231' | + '\u0233'..'\u0239' | + '\u023C' | + '\u023F' | + '\u0240' | + '\u0242' | + '\u0247' | + '\u0249' | + '\u024B' | + '\u024D' | + '\u024F'..'\u0293' | + '\u0295'..'\u02AF' | + '\u0371' | + '\u0373' | + '\u0377' | + '\u037B'..'\u037D' | + '\u0390' | + '\u03AC'..'\u03CE' | + '\u03D0' | + '\u03D1' | + '\u03D5'..'\u03D7' | + '\u03D9' | + '\u03DB' | + '\u03DD' | + '\u03DF' | + '\u03E1' | + '\u03E3' | + '\u03E5' | + '\u03E7' | + '\u03E9' | + '\u03EB' | + '\u03ED' | + '\u03EF'..'\u03F3' | + '\u03F5' | + '\u03F8' | + '\u03FB' | + '\u03FC' | + '\u0430'..'\u045F' | + '\u0461' | + '\u0463' | + '\u0465' | + '\u0467' | + '\u0469' | + '\u046B' | + '\u046D' | + '\u046F' | + '\u0471' | + '\u0473' | + '\u0475' | + '\u0477' | + '\u0479' | + '\u047B' | + '\u047D' | + '\u047F' | + '\u0481' | + '\u048B' | + '\u048D' | + '\u048F' | + '\u0491' | + '\u0493' | + '\u0495' | + '\u0497' | + '\u0499' | + '\u049B' | + '\u049D' | + '\u049F' | + '\u04A1' | + '\u04A3' | + '\u04A5' | + '\u04A7' | + '\u04A9' | + '\u04AB' | + '\u04AD' | + '\u04AF' | + '\u04B1' | + '\u04B3' | + '\u04B5' | + '\u04B7' | + '\u04B9' | + '\u04BB' | + '\u04BD' | + '\u04BF' | + '\u04C2' | + '\u04C4' | + '\u04C6' | + '\u04C8' | + '\u04CA' | + '\u04CC' | + '\u04CE' | + '\u04CF' | + '\u04D1' | + '\u04D3' | + '\u04D5' | + '\u04D7' | + '\u04D9' | + '\u04DB' | + '\u04DD' | + '\u04DF' | + '\u04E1' | + '\u04E3' | + '\u04E5' | + '\u04E7' | + '\u04E9' | + '\u04EB' | + '\u04ED' | + '\u04EF' | + '\u04F1' | + '\u04F3' | + '\u04F5' | + '\u04F7' | + '\u04F9' | + '\u04FB' | + '\u04FD' | + '\u04FF' | + '\u0501' | + '\u0503' | + '\u0505' | + '\u0507' | + '\u0509' | + '\u050B' | + '\u050D' | + '\u050F' | + '\u0511' | + '\u0513' | + '\u0515' | + '\u0517' | + '\u0519' | + '\u051B' | + '\u051D' | + '\u051F' | + '\u0521' | + '\u0523' | + '\u0525' | + '\u0527' | + '\u0561'..'\u0587' | + '\u1D00'..'\u1D2B' | + '\u1D6B'..'\u1D77' | + '\u1D79'..'\u1D9A' | + '\u1E01' | + '\u1E03' | + '\u1E05' | + '\u1E07' | + '\u1E09' | + '\u1E0B' | + '\u1E0D' | + '\u1E0F' | + '\u1E11' | + '\u1E13' | + '\u1E15' | + '\u1E17' | + '\u1E19' | + '\u1E1B' | + '\u1E1D' | + '\u1E1F' | + '\u1E21' | + '\u1E23' | + '\u1E25' | + '\u1E27' | + '\u1E29' | + '\u1E2B' | + '\u1E2D' | + '\u1E2F' | + '\u1E31' | + '\u1E33' | + '\u1E35' | + '\u1E37' | + '\u1E39' | + '\u1E3B' | + '\u1E3D' | + '\u1E3F' | + '\u1E41' | + '\u1E43' | + '\u1E45' | + '\u1E47' | + '\u1E49' | + '\u1E4B' | + '\u1E4D' | + '\u1E4F' | + '\u1E51' | + '\u1E53' | + '\u1E55' | + '\u1E57' | + '\u1E59' | + '\u1E5B' | + '\u1E5D' | + '\u1E5F' | + '\u1E61' | + '\u1E63' | + '\u1E65' | + '\u1E67' | + '\u1E69' | + '\u1E6B' | + '\u1E6D' | + '\u1E6F' | + '\u1E71' | + '\u1E73' | + '\u1E75' | + '\u1E77' | + '\u1E79' | + '\u1E7B' | + '\u1E7D' | + '\u1E7F' | + '\u1E81' | + '\u1E83' | + '\u1E85' | + '\u1E87' | + '\u1E89' | + '\u1E8B' | + '\u1E8D' | + '\u1E8F' | + '\u1E91' | + '\u1E93' | + '\u1E95'..'\u1E9D' | + '\u1E9F' | + '\u1EA1' | + '\u1EA3' | + '\u1EA5' | + '\u1EA7' | + '\u1EA9' | + '\u1EAB' | + '\u1EAD' | + '\u1EAF' | + '\u1EB1' | + '\u1EB3' | + '\u1EB5' | + '\u1EB7' | + '\u1EB9' | + '\u1EBB' | + '\u1EBD' | + '\u1EBF' | + '\u1EC1' | + '\u1EC3' | + '\u1EC5' | + '\u1EC7' | + '\u1EC9' | + '\u1ECB' | + '\u1ECD' | + '\u1ECF' | + '\u1ED1' | + '\u1ED3' | + '\u1ED5' | + '\u1ED7' | + '\u1ED9' | + '\u1EDB' | + '\u1EDD' | + '\u1EDF' | + '\u1EE1' | + '\u1EE3' | + '\u1EE5' | + '\u1EE7' | + '\u1EE9' | + '\u1EEB' | + '\u1EED' | + '\u1EEF' | + '\u1EF1' | + '\u1EF3' | + '\u1EF5' | + '\u1EF7' | + '\u1EF9' | + '\u1EFB' | + '\u1EFD' | + '\u1EFF'..'\u1F07' | + '\u1F10'..'\u1F15' | + '\u1F20'..'\u1F27' | + '\u1F30'..'\u1F37' | + '\u1F40'..'\u1F45' | + '\u1F50'..'\u1F57' | + '\u1F60'..'\u1F67' | + '\u1F70'..'\u1F7D' | + '\u1F80'..'\u1F87' | + '\u1F90'..'\u1F97' | + '\u1FA0'..'\u1FA7' | + '\u1FB0'..'\u1FB4' | + '\u1FB6' | + '\u1FB7' | + '\u1FBE' | + '\u1FC2'..'\u1FC4' | + '\u1FC6' | + '\u1FC7' | + '\u1FD0'..'\u1FD3' | + '\u1FD6' | + '\u1FD7' | + '\u1FE0'..'\u1FE7' | + '\u1FF2'..'\u1FF4' | + '\u1FF6' | + '\u1FF7' | + '\u210A' | + '\u210E' | + '\u210F' | + '\u2113' | + '\u212F' | + '\u2134' | + '\u2139' | + '\u213C' | + '\u213D' | + '\u2146'..'\u2149' | + '\u214E' | + '\u2184' | + '\u2C30'..'\u2C5E' | + '\u2C61' | + '\u2C65' | + '\u2C66' | + '\u2C68' | + '\u2C6A' | + '\u2C6C' | + '\u2C71' | + '\u2C73' | + '\u2C74' | + '\u2C76'..'\u2C7B' | + '\u2C81' | + '\u2C83' | + '\u2C85' | + '\u2C87' | + '\u2C89' | + '\u2C8B' | + '\u2C8D' | + '\u2C8F' | + '\u2C91' | + '\u2C93' | + '\u2C95' | + '\u2C97' | + '\u2C99' | + '\u2C9B' | + '\u2C9D' | + '\u2C9F' | + '\u2CA1' | + '\u2CA3' | + '\u2CA5' | + '\u2CA7' | + '\u2CA9' | + '\u2CAB' | + '\u2CAD' | + '\u2CAF' | + '\u2CB1' | + '\u2CB3' | + '\u2CB5' | + '\u2CB7' | + '\u2CB9' | + '\u2CBB' | + '\u2CBD' | + '\u2CBF' | + '\u2CC1' | + '\u2CC3' | + '\u2CC5' | + '\u2CC7' | + '\u2CC9' | + '\u2CCB' | + '\u2CCD' | + '\u2CCF' | + '\u2CD1' | + '\u2CD3' | + '\u2CD5' | + '\u2CD7' | + '\u2CD9' | + '\u2CDB' | + '\u2CDD' | + '\u2CDF' | + '\u2CE1' | + '\u2CE3' | + '\u2CE4' | + '\u2CEC' | + '\u2CEE' | + '\u2CF3' | + '\u2D00'..'\u2D25' | + '\u2D27' | + '\u2D2D' | + '\uA641' | + '\uA643' | + '\uA645' | + '\uA647' | + '\uA649' | + '\uA64B' | + '\uA64D' | + '\uA64F' | + '\uA651' | + '\uA653' | + '\uA655' | + '\uA657' | + '\uA659' | + '\uA65B' | + '\uA65D' | + '\uA65F' | + '\uA661' | + '\uA663' | + '\uA665' | + '\uA667' | + '\uA669' | + '\uA66B' | + '\uA66D' | + '\uA681' | + '\uA683' | + '\uA685' | + '\uA687' | + '\uA689' | + '\uA68B' | + '\uA68D' | + '\uA68F' | + '\uA691' | + '\uA693' | + '\uA695' | + '\uA697' | + '\uA723' | + '\uA725' | + '\uA727' | + '\uA729' | + '\uA72B' | + '\uA72D' | + '\uA72F'..'\uA731' | + '\uA733' | + '\uA735' | + '\uA737' | + '\uA739' | + '\uA73B' | + '\uA73D' | + '\uA73F' | + '\uA741' | + '\uA743' | + '\uA745' | + '\uA747' | + '\uA749' | + '\uA74B' | + '\uA74D' | + '\uA74F' | + '\uA751' | + '\uA753' | + '\uA755' | + '\uA757' | + '\uA759' | + '\uA75B' | + '\uA75D' | + '\uA75F' | + '\uA761' | + '\uA763' | + '\uA765' | + '\uA767' | + '\uA769' | + '\uA76B' | + '\uA76D' | + '\uA76F' | + '\uA771'..'\uA778' | + '\uA77A' | + '\uA77C' | + '\uA77F' | + '\uA781' | + '\uA783' | + '\uA785' | + '\uA787' | + '\uA78C' | + '\uA78E' | + '\uA791' | + '\uA793' | + '\uA7A1' | + '\uA7A3' | + '\uA7A5' | + '\uA7A7' | + '\uA7A9' | + '\uA7FA' | + '\uFB00'..'\uFB06' | + '\uFB13'..'\uFB17' | + '\uFF41'..'\uFF5A'; + +UNICODE_CLASS_LM: + '\u02B0'..'\u02C1' | + '\u02C6'..'\u02D1' | + '\u02E0'..'\u02E4' | + '\u02EC' | + '\u02EE' | + '\u0374' | + '\u037A' | + '\u0559' | + '\u0640' | + '\u06E5' | + '\u06E6' | + '\u07F4' | + '\u07F5' | + '\u07FA' | + '\u081A' | + '\u0824' | + '\u0828' | + '\u0971' | + '\u0E46' | + '\u0EC6' | + '\u10FC' | + '\u17D7' | + '\u1843' | + '\u1AA7' | + '\u1C78'..'\u1C7D' | + '\u1D2C'..'\u1D6A' | + '\u1D78' | + '\u1D9B'..'\u1DBF' | + '\u2071' | + '\u207F' | + '\u2090'..'\u209C' | + '\u2C7C' | + '\u2C7D' | + '\u2D6F' | + '\u2E2F' | + '\u3005' | + '\u3031'..'\u3035' | + '\u303B' | + '\u309D' | + '\u309E' | + '\u30FC'..'\u30FE' | + '\uA015' | + '\uA4F8'..'\uA4FD' | + '\uA60C' | + '\uA67F' | + '\uA717'..'\uA71F' | + '\uA770' | + '\uA788' | + '\uA7F8' | + '\uA7F9' | + '\uA9CF' | + '\uAA70' | + '\uAADD' | + '\uAAF3' | + '\uAAF4' | + '\uFF70' | + '\uFF9E' | + '\uFF9F'; + +UNICODE_CLASS_LO: + '\u00AA' | + '\u00BA' | + '\u01BB' | + '\u01C0'..'\u01C3' | + '\u0294' | + '\u05D0'..'\u05EA' | + '\u05F0'..'\u05F2' | + '\u0620'..'\u063F' | + '\u0641'..'\u064A' | + '\u066E' | + '\u066F' | + '\u0671'..'\u06D3' | + '\u06D5' | + '\u06EE' | + '\u06EF' | + '\u06FA'..'\u06FC' | + '\u06FF' | + '\u0710' | + '\u0712'..'\u072F' | + '\u074D'..'\u07A5' | + '\u07B1' | + '\u07CA'..'\u07EA' | + '\u0800'..'\u0815' | + '\u0840'..'\u0858' | + '\u08A0' | + '\u08A2'..'\u08AC' | + '\u0904'..'\u0939' | + '\u093D' | + '\u0950' | + '\u0958'..'\u0961' | + '\u0972'..'\u0977' | + '\u0979'..'\u097F' | + '\u0985'..'\u098C' | + '\u098F' | + '\u0990' | + '\u0993'..'\u09A8' | + '\u09AA'..'\u09B0' | + '\u09B2' | + '\u09B6'..'\u09B9' | + '\u09BD' | + '\u09CE' | + '\u09DC' | + '\u09DD' | + '\u09DF'..'\u09E1' | + '\u09F0' | + '\u09F1' | + '\u0A05'..'\u0A0A' | + '\u0A0F' | + '\u0A10' | + '\u0A13'..'\u0A28' | + '\u0A2A'..'\u0A30' | + '\u0A32' | + '\u0A33' | + '\u0A35' | + '\u0A36' | + '\u0A38' | + '\u0A39' | + '\u0A59'..'\u0A5C' | + '\u0A5E' | + '\u0A72'..'\u0A74' | + '\u0A85'..'\u0A8D' | + '\u0A8F'..'\u0A91' | + '\u0A93'..'\u0AA8' | + '\u0AAA'..'\u0AB0' | + '\u0AB2' | + '\u0AB3' | + '\u0AB5'..'\u0AB9' | + '\u0ABD' | + '\u0AD0' | + '\u0AE0' | + '\u0AE1' | + '\u0B05'..'\u0B0C' | + '\u0B0F' | + '\u0B10' | + '\u0B13'..'\u0B28' | + '\u0B2A'..'\u0B30' | + '\u0B32' | + '\u0B33' | + '\u0B35'..'\u0B39' | + '\u0B3D' | + '\u0B5C' | + '\u0B5D' | + '\u0B5F'..'\u0B61' | + '\u0B71' | + '\u0B83' | + '\u0B85'..'\u0B8A' | + '\u0B8E'..'\u0B90' | + '\u0B92'..'\u0B95' | + '\u0B99' | + '\u0B9A' | + '\u0B9C' | + '\u0B9E' | + '\u0B9F' | + '\u0BA3' | + '\u0BA4' | + '\u0BA8'..'\u0BAA' | + '\u0BAE'..'\u0BB9' | + '\u0BD0' | + '\u0C05'..'\u0C0C' | + '\u0C0E'..'\u0C10' | + '\u0C12'..'\u0C28' | + '\u0C2A'..'\u0C33' | + '\u0C35'..'\u0C39' | + '\u0C3D' | + '\u0C58' | + '\u0C59' | + '\u0C60' | + '\u0C61' | + '\u0C85'..'\u0C8C' | + '\u0C8E'..'\u0C90' | + '\u0C92'..'\u0CA8' | + '\u0CAA'..'\u0CB3' | + '\u0CB5'..'\u0CB9' | + '\u0CBD' | + '\u0CDE' | + '\u0CE0' | + '\u0CE1' | + '\u0CF1' | + '\u0CF2' | + '\u0D05'..'\u0D0C' | + '\u0D0E'..'\u0D10' | + '\u0D12'..'\u0D3A' | + '\u0D3D' | + '\u0D4E' | + '\u0D60' | + '\u0D61' | + '\u0D7A'..'\u0D7F' | + '\u0D85'..'\u0D96' | + '\u0D9A'..'\u0DB1' | + '\u0DB3'..'\u0DBB' | + '\u0DBD' | + '\u0DC0'..'\u0DC6' | + '\u0E01'..'\u0E30' | + '\u0E32' | + '\u0E33' | + '\u0E40'..'\u0E45' | + '\u0E81' | + '\u0E82' | + '\u0E84' | + '\u0E87' | + '\u0E88' | + '\u0E8A' | + '\u0E8D' | + '\u0E94'..'\u0E97' | + '\u0E99'..'\u0E9F' | + '\u0EA1'..'\u0EA3' | + '\u0EA5' | + '\u0EA7' | + '\u0EAA' | + '\u0EAB' | + '\u0EAD'..'\u0EB0' | + '\u0EB2' | + '\u0EB3' | + '\u0EBD' | + '\u0EC0'..'\u0EC4' | + '\u0EDC'..'\u0EDF' | + '\u0F00' | + '\u0F40'..'\u0F47' | + '\u0F49'..'\u0F6C' | + '\u0F88'..'\u0F8C' | + '\u1000'..'\u102A' | + '\u103F' | + '\u1050'..'\u1055' | + '\u105A'..'\u105D' | + '\u1061' | + '\u1065' | + '\u1066' | + '\u106E'..'\u1070' | + '\u1075'..'\u1081' | + '\u108E' | + '\u10D0'..'\u10FA' | + '\u10FD'..'\u1248' | + '\u124A'..'\u124D' | + '\u1250'..'\u1256' | + '\u1258' | + '\u125A'..'\u125D' | + '\u1260'..'\u1288' | + '\u128A'..'\u128D' | + '\u1290'..'\u12B0' | + '\u12B2'..'\u12B5' | + '\u12B8'..'\u12BE' | + '\u12C0' | + '\u12C2'..'\u12C5' | + '\u12C8'..'\u12D6' | + '\u12D8'..'\u1310' | + '\u1312'..'\u1315' | + '\u1318'..'\u135A' | + '\u1380'..'\u138F' | + '\u13A0'..'\u13F4' | + '\u1401'..'\u166C' | + '\u166F'..'\u167F' | + '\u1681'..'\u169A' | + '\u16A0'..'\u16EA' | + '\u1700'..'\u170C' | + '\u170E'..'\u1711' | + '\u1720'..'\u1731' | + '\u1740'..'\u1751' | + '\u1760'..'\u176C' | + '\u176E'..'\u1770' | + '\u1780'..'\u17B3' | + '\u17DC' | + '\u1820'..'\u1842' | + '\u1844'..'\u1877' | + '\u1880'..'\u18A8' | + '\u18AA' | + '\u18B0'..'\u18F5' | + '\u1900'..'\u191C' | + '\u1950'..'\u196D' | + '\u1970'..'\u1974' | + '\u1980'..'\u19AB' | + '\u19C1'..'\u19C7' | + '\u1A00'..'\u1A16' | + '\u1A20'..'\u1A54' | + '\u1B05'..'\u1B33' | + '\u1B45'..'\u1B4B' | + '\u1B83'..'\u1BA0' | + '\u1BAE' | + '\u1BAF' | + '\u1BBA'..'\u1BE5' | + '\u1C00'..'\u1C23' | + '\u1C4D'..'\u1C4F' | + '\u1C5A'..'\u1C77' | + '\u1CE9'..'\u1CEC' | + '\u1CEE'..'\u1CF1' | + '\u1CF5' | + '\u1CF6' | + '\u2135'..'\u2138' | + '\u2D30'..'\u2D67' | + '\u2D80'..'\u2D96' | + '\u2DA0'..'\u2DA6' | + '\u2DA8'..'\u2DAE' | + '\u2DB0'..'\u2DB6' | + '\u2DB8'..'\u2DBE' | + '\u2DC0'..'\u2DC6' | + '\u2DC8'..'\u2DCE' | + '\u2DD0'..'\u2DD6' | + '\u2DD8'..'\u2DDE' | + '\u3006' | + '\u303C' | + '\u3041'..'\u3096' | + '\u309F' | + '\u30A1'..'\u30FA' | + '\u30FF' | + '\u3105'..'\u312D' | + '\u3131'..'\u318E' | + '\u31A0'..'\u31BA' | + '\u31F0'..'\u31FF' | + '\u3400' | + '\u4DB5' | + '\u4E00' | + '\u9FCC' | + '\uA000'..'\uA014' | + '\uA016'..'\uA48C' | + '\uA4D0'..'\uA4F7' | + '\uA500'..'\uA60B' | + '\uA610'..'\uA61F' | + '\uA62A' | + '\uA62B' | + '\uA66E' | + '\uA6A0'..'\uA6E5' | + '\uA7FB'..'\uA801' | + '\uA803'..'\uA805' | + '\uA807'..'\uA80A' | + '\uA80C'..'\uA822' | + '\uA840'..'\uA873' | + '\uA882'..'\uA8B3' | + '\uA8F2'..'\uA8F7' | + '\uA8FB' | + '\uA90A'..'\uA925' | + '\uA930'..'\uA946' | + '\uA960'..'\uA97C' | + '\uA984'..'\uA9B2' | + '\uAA00'..'\uAA28' | + '\uAA40'..'\uAA42' | + '\uAA44'..'\uAA4B' | + '\uAA60'..'\uAA6F' | + '\uAA71'..'\uAA76' | + '\uAA7A' | + '\uAA80'..'\uAAAF' | + '\uAAB1' | + '\uAAB5' | + '\uAAB6' | + '\uAAB9'..'\uAABD' | + '\uAAC0' | + '\uAAC2' | + '\uAADB' | + '\uAADC' | + '\uAAE0'..'\uAAEA' | + '\uAAF2' | + '\uAB01'..'\uAB06' | + '\uAB09'..'\uAB0E' | + '\uAB11'..'\uAB16' | + '\uAB20'..'\uAB26' | + '\uAB28'..'\uAB2E' | + '\uABC0'..'\uABE2' | + '\uAC00' | + '\uD7A3' | + '\uD7B0'..'\uD7C6' | + '\uD7CB'..'\uD7FB' | + '\uF900'..'\uFA6D' | + '\uFA70'..'\uFAD9' | + '\uFB1D' | + '\uFB1F'..'\uFB28' | + '\uFB2A'..'\uFB36' | + '\uFB38'..'\uFB3C' | + '\uFB3E' | + '\uFB40' | + '\uFB41' | + '\uFB43' | + '\uFB44' | + '\uFB46'..'\uFBB1' | + '\uFBD3'..'\uFD3D' | + '\uFD50'..'\uFD8F' | + '\uFD92'..'\uFDC7' | + '\uFDF0'..'\uFDFB' | + '\uFE70'..'\uFE74' | + '\uFE76'..'\uFEFC' | + '\uFF66'..'\uFF6F' | + '\uFF71'..'\uFF9D' | + '\uFFA0'..'\uFFBE' | + '\uFFC2'..'\uFFC7' | + '\uFFCA'..'\uFFCF' | + '\uFFD2'..'\uFFD7' | + '\uFFDA'..'\uFFDC'; + +UNICODE_CLASS_LT: + '\u01C5' | + '\u01C8' | + '\u01CB' | + '\u01F2' | + '\u1F88'..'\u1F8F' | + '\u1F98'..'\u1F9F' | + '\u1FA8'..'\u1FAF' | + '\u1FBC' | + '\u1FCC' | + '\u1FFC'; + +UNICODE_CLASS_LU: + '\u0041'..'\u005A' | + '\u00C0'..'\u00D6' | + '\u00D8'..'\u00DE' | + '\u0100' | + '\u0102' | + '\u0104' | + '\u0106' | + '\u0108' | + '\u010A' | + '\u010C' | + '\u010E' | + '\u0110' | + '\u0112' | + '\u0114' | + '\u0116' | + '\u0118' | + '\u011A' | + '\u011C' | + '\u011E' | + '\u0120' | + '\u0122' | + '\u0124' | + '\u0126' | + '\u0128' | + '\u012A' | + '\u012C' | + '\u012E' | + '\u0130' | + '\u0132' | + '\u0134' | + '\u0136' | + '\u0139' | + '\u013B' | + '\u013D' | + '\u013F' | + '\u0141' | + '\u0143' | + '\u0145' | + '\u0147' | + '\u014A' | + '\u014C' | + '\u014E' | + '\u0150' | + '\u0152' | + '\u0154' | + '\u0156' | + '\u0158' | + '\u015A' | + '\u015C' | + '\u015E' | + '\u0160' | + '\u0162' | + '\u0164' | + '\u0166' | + '\u0168' | + '\u016A' | + '\u016C' | + '\u016E' | + '\u0170' | + '\u0172' | + '\u0174' | + '\u0176' | + '\u0178' | + '\u0179' | + '\u017B' | + '\u017D' | + '\u0181' | + '\u0182' | + '\u0184' | + '\u0186' | + '\u0187' | + '\u0189'..'\u018B' | + '\u018E'..'\u0191' | + '\u0193' | + '\u0194' | + '\u0196'..'\u0198' | + '\u019C' | + '\u019D' | + '\u019F' | + '\u01A0' | + '\u01A2' | + '\u01A4' | + '\u01A6' | + '\u01A7' | + '\u01A9' | + '\u01AC' | + '\u01AE' | + '\u01AF' | + '\u01B1'..'\u01B3' | + '\u01B5' | + '\u01B7' | + '\u01B8' | + '\u01BC' | + '\u01C4' | + '\u01C7' | + '\u01CA' | + '\u01CD' | + '\u01CF' | + '\u01D1' | + '\u01D3' | + '\u01D5' | + '\u01D7' | + '\u01D9' | + '\u01DB' | + '\u01DE' | + '\u01E0' | + '\u01E2' | + '\u01E4' | + '\u01E6' | + '\u01E8' | + '\u01EA' | + '\u01EC' | + '\u01EE' | + '\u01F1' | + '\u01F4' | + '\u01F6'..'\u01F8' | + '\u01FA' | + '\u01FC' | + '\u01FE' | + '\u0200' | + '\u0202' | + '\u0204' | + '\u0206' | + '\u0208' | + '\u020A' | + '\u020C' | + '\u020E' | + '\u0210' | + '\u0212' | + '\u0214' | + '\u0216' | + '\u0218' | + '\u021A' | + '\u021C' | + '\u021E' | + '\u0220' | + '\u0222' | + '\u0224' | + '\u0226' | + '\u0228' | + '\u022A' | + '\u022C' | + '\u022E' | + '\u0230' | + '\u0232' | + '\u023A' | + '\u023B' | + '\u023D' | + '\u023E' | + '\u0241' | + '\u0243'..'\u0246' | + '\u0248' | + '\u024A' | + '\u024C' | + '\u024E' | + '\u0370' | + '\u0372' | + '\u0376' | + '\u0386' | + '\u0388'..'\u038A' | + '\u038C' | + '\u038E' | + '\u038F' | + '\u0391'..'\u03A1' | + '\u03A3'..'\u03AB' | + '\u03CF' | + '\u03D2'..'\u03D4' | + '\u03D8' | + '\u03DA' | + '\u03DC' | + '\u03DE' | + '\u03E0' | + '\u03E2' | + '\u03E4' | + '\u03E6' | + '\u03E8' | + '\u03EA' | + '\u03EC' | + '\u03EE' | + '\u03F4' | + '\u03F7' | + '\u03F9' | + '\u03FA' | + '\u03FD'..'\u042F' | + '\u0460' | + '\u0462' | + '\u0464' | + '\u0466' | + '\u0468' | + '\u046A' | + '\u046C' | + '\u046E' | + '\u0470' | + '\u0472' | + '\u0474' | + '\u0476' | + '\u0478' | + '\u047A' | + '\u047C' | + '\u047E' | + '\u0480' | + '\u048A' | + '\u048C' | + '\u048E' | + '\u0490' | + '\u0492' | + '\u0494' | + '\u0496' | + '\u0498' | + '\u049A' | + '\u049C' | + '\u049E' | + '\u04A0' | + '\u04A2' | + '\u04A4' | + '\u04A6' | + '\u04A8' | + '\u04AA' | + '\u04AC' | + '\u04AE' | + '\u04B0' | + '\u04B2' | + '\u04B4' | + '\u04B6' | + '\u04B8' | + '\u04BA' | + '\u04BC' | + '\u04BE' | + '\u04C0' | + '\u04C1' | + '\u04C3' | + '\u04C5' | + '\u04C7' | + '\u04C9' | + '\u04CB' | + '\u04CD' | + '\u04D0' | + '\u04D2' | + '\u04D4' | + '\u04D6' | + '\u04D8' | + '\u04DA' | + '\u04DC' | + '\u04DE' | + '\u04E0' | + '\u04E2' | + '\u04E4' | + '\u04E6' | + '\u04E8' | + '\u04EA' | + '\u04EC' | + '\u04EE' | + '\u04F0' | + '\u04F2' | + '\u04F4' | + '\u04F6' | + '\u04F8' | + '\u04FA' | + '\u04FC' | + '\u04FE' | + '\u0500' | + '\u0502' | + '\u0504' | + '\u0506' | + '\u0508' | + '\u050A' | + '\u050C' | + '\u050E' | + '\u0510' | + '\u0512' | + '\u0514' | + '\u0516' | + '\u0518' | + '\u051A' | + '\u051C' | + '\u051E' | + '\u0520' | + '\u0522' | + '\u0524' | + '\u0526' | + '\u0531'..'\u0556' | + '\u10A0'..'\u10C5' | + '\u10C7' | + '\u10CD' | + '\u1E00' | + '\u1E02' | + '\u1E04' | + '\u1E06' | + '\u1E08' | + '\u1E0A' | + '\u1E0C' | + '\u1E0E' | + '\u1E10' | + '\u1E12' | + '\u1E14' | + '\u1E16' | + '\u1E18' | + '\u1E1A' | + '\u1E1C' | + '\u1E1E' | + '\u1E20' | + '\u1E22' | + '\u1E24' | + '\u1E26' | + '\u1E28' | + '\u1E2A' | + '\u1E2C' | + '\u1E2E' | + '\u1E30' | + '\u1E32' | + '\u1E34' | + '\u1E36' | + '\u1E38' | + '\u1E3A' | + '\u1E3C' | + '\u1E3E' | + '\u1E40' | + '\u1E42' | + '\u1E44' | + '\u1E46' | + '\u1E48' | + '\u1E4A' | + '\u1E4C' | + '\u1E4E' | + '\u1E50' | + '\u1E52' | + '\u1E54' | + '\u1E56' | + '\u1E58' | + '\u1E5A' | + '\u1E5C' | + '\u1E5E' | + '\u1E60' | + '\u1E62' | + '\u1E64' | + '\u1E66' | + '\u1E68' | + '\u1E6A' | + '\u1E6C' | + '\u1E6E' | + '\u1E70' | + '\u1E72' | + '\u1E74' | + '\u1E76' | + '\u1E78' | + '\u1E7A' | + '\u1E7C' | + '\u1E7E' | + '\u1E80' | + '\u1E82' | + '\u1E84' | + '\u1E86' | + '\u1E88' | + '\u1E8A' | + '\u1E8C' | + '\u1E8E' | + '\u1E90' | + '\u1E92' | + '\u1E94' | + '\u1E9E' | + '\u1EA0' | + '\u1EA2' | + '\u1EA4' | + '\u1EA6' | + '\u1EA8' | + '\u1EAA' | + '\u1EAC' | + '\u1EAE' | + '\u1EB0' | + '\u1EB2' | + '\u1EB4' | + '\u1EB6' | + '\u1EB8' | + '\u1EBA' | + '\u1EBC' | + '\u1EBE' | + '\u1EC0' | + '\u1EC2' | + '\u1EC4' | + '\u1EC6' | + '\u1EC8' | + '\u1ECA' | + '\u1ECC' | + '\u1ECE' | + '\u1ED0' | + '\u1ED2' | + '\u1ED4' | + '\u1ED6' | + '\u1ED8' | + '\u1EDA' | + '\u1EDC' | + '\u1EDE' | + '\u1EE0' | + '\u1EE2' | + '\u1EE4' | + '\u1EE6' | + '\u1EE8' | + '\u1EEA' | + '\u1EEC' | + '\u1EEE' | + '\u1EF0' | + '\u1EF2' | + '\u1EF4' | + '\u1EF6' | + '\u1EF8' | + '\u1EFA' | + '\u1EFC' | + '\u1EFE' | + '\u1F08'..'\u1F0F' | + '\u1F18'..'\u1F1D' | + '\u1F28'..'\u1F2F' | + '\u1F38'..'\u1F3F' | + '\u1F48'..'\u1F4D' | + '\u1F59' | + '\u1F5B' | + '\u1F5D' | + '\u1F5F' | + '\u1F68'..'\u1F6F' | + '\u1FB8'..'\u1FBB' | + '\u1FC8'..'\u1FCB' | + '\u1FD8'..'\u1FDB' | + '\u1FE8'..'\u1FEC' | + '\u1FF8'..'\u1FFB' | + '\u2102' | + '\u2107' | + '\u210B'..'\u210D' | + '\u2110'..'\u2112' | + '\u2115' | + '\u2119'..'\u211D' | + '\u2124' | + '\u2126' | + '\u2128' | + '\u212A'..'\u212D' | + '\u2130'..'\u2133' | + '\u213E' | + '\u213F' | + '\u2145' | + '\u2183' | + '\u2C00'..'\u2C2E' | + '\u2C60' | + '\u2C62'..'\u2C64' | + '\u2C67' | + '\u2C69' | + '\u2C6B' | + '\u2C6D'..'\u2C70' | + '\u2C72' | + '\u2C75' | + '\u2C7E'..'\u2C80' | + '\u2C82' | + '\u2C84' | + '\u2C86' | + '\u2C88' | + '\u2C8A' | + '\u2C8C' | + '\u2C8E' | + '\u2C90' | + '\u2C92' | + '\u2C94' | + '\u2C96' | + '\u2C98' | + '\u2C9A' | + '\u2C9C' | + '\u2C9E' | + '\u2CA0' | + '\u2CA2' | + '\u2CA4' | + '\u2CA6' | + '\u2CA8' | + '\u2CAA' | + '\u2CAC' | + '\u2CAE' | + '\u2CB0' | + '\u2CB2' | + '\u2CB4' | + '\u2CB6' | + '\u2CB8' | + '\u2CBA' | + '\u2CBC' | + '\u2CBE' | + '\u2CC0' | + '\u2CC2' | + '\u2CC4' | + '\u2CC6' | + '\u2CC8' | + '\u2CCA' | + '\u2CCC' | + '\u2CCE' | + '\u2CD0' | + '\u2CD2' | + '\u2CD4' | + '\u2CD6' | + '\u2CD8' | + '\u2CDA' | + '\u2CDC' | + '\u2CDE' | + '\u2CE0' | + '\u2CE2' | + '\u2CEB' | + '\u2CED' | + '\u2CF2' | + '\uA640' | + '\uA642' | + '\uA644' | + '\uA646' | + '\uA648' | + '\uA64A' | + '\uA64C' | + '\uA64E' | + '\uA650' | + '\uA652' | + '\uA654' | + '\uA656' | + '\uA658' | + '\uA65A' | + '\uA65C' | + '\uA65E' | + '\uA660' | + '\uA662' | + '\uA664' | + '\uA666' | + '\uA668' | + '\uA66A' | + '\uA66C' | + '\uA680' | + '\uA682' | + '\uA684' | + '\uA686' | + '\uA688' | + '\uA68A' | + '\uA68C' | + '\uA68E' | + '\uA690' | + '\uA692' | + '\uA694' | + '\uA696' | + '\uA722' | + '\uA724' | + '\uA726' | + '\uA728' | + '\uA72A' | + '\uA72C' | + '\uA72E' | + '\uA732' | + '\uA734' | + '\uA736' | + '\uA738' | + '\uA73A' | + '\uA73C' | + '\uA73E' | + '\uA740' | + '\uA742' | + '\uA744' | + '\uA746' | + '\uA748' | + '\uA74A' | + '\uA74C' | + '\uA74E' | + '\uA750' | + '\uA752' | + '\uA754' | + '\uA756' | + '\uA758' | + '\uA75A' | + '\uA75C' | + '\uA75E' | + '\uA760' | + '\uA762' | + '\uA764' | + '\uA766' | + '\uA768' | + '\uA76A' | + '\uA76C' | + '\uA76E' | + '\uA779' | + '\uA77B' | + '\uA77D' | + '\uA77E' | + '\uA780' | + '\uA782' | + '\uA784' | + '\uA786' | + '\uA78B' | + '\uA78D' | + '\uA790' | + '\uA792' | + '\uA7A0' | + '\uA7A2' | + '\uA7A4' | + '\uA7A6' | + '\uA7A8' | + '\uA7AA' | + '\uFF21'..'\uFF3A'; + +UNICODE_CLASS_ND: + '\u0030'..'\u0039' | + '\u0660'..'\u0669' | + '\u06F0'..'\u06F9' | + '\u07C0'..'\u07C9' | + '\u0966'..'\u096F' | + '\u09E6'..'\u09EF' | + '\u0A66'..'\u0A6F' | + '\u0AE6'..'\u0AEF' | + '\u0B66'..'\u0B6F' | + '\u0BE6'..'\u0BEF' | + '\u0C66'..'\u0C6F' | + '\u0CE6'..'\u0CEF' | + '\u0D66'..'\u0D6F' | + '\u0E50'..'\u0E59' | + '\u0ED0'..'\u0ED9' | + '\u0F20'..'\u0F29' | + '\u1040'..'\u1049' | + '\u1090'..'\u1099' | + '\u17E0'..'\u17E9' | + '\u1810'..'\u1819' | + '\u1946'..'\u194F' | + '\u19D0'..'\u19D9' | + '\u1A80'..'\u1A89' | + '\u1A90'..'\u1A99' | + '\u1B50'..'\u1B59' | + '\u1BB0'..'\u1BB9' | + '\u1C40'..'\u1C49' | + '\u1C50'..'\u1C59' | + '\uA620'..'\uA629' | + '\uA8D0'..'\uA8D9' | + '\uA900'..'\uA909' | + '\uA9D0'..'\uA9D9' | + '\uAA50'..'\uAA59' | + '\uABF0'..'\uABF9' | + '\uFF10'..'\uFF19'; + +UNICODE_CLASS_NL: + '\u16EE'..'\u16F0' | + '\u2160'..'\u2182' | + '\u2185'..'\u2188' | + '\u3007' | + '\u3021'..'\u3029' | + '\u3038'..'\u303A' | + '\uA6E6'..'\uA6EF'; \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/language/textmate/BaseIncrementalAnalyzeManager.java b/app/src/main/java/com/tyron/code/language/textmate/BaseIncrementalAnalyzeManager.java new file mode 100644 index 00000000..99514487 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/textmate/BaseIncrementalAnalyzeManager.java @@ -0,0 +1,547 @@ +package com.tyron.code.language.textmate; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import io.github.rosemoe.sora.lang.analysis.IncrementalAnalyzeManager; +import io.github.rosemoe.sora.lang.analysis.StyleReceiver; +import io.github.rosemoe.sora.lang.styling.CodeBlock; +import io.github.rosemoe.sora.lang.styling.Span; +import io.github.rosemoe.sora.lang.styling.Spans; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.Content; +import io.github.rosemoe.sora.text.ContentLine; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.util.IntPair; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; + +public abstract class BaseIncrementalAnalyzeManager implements IncrementalAnalyzeManager { + + private StyleReceiver receiver; + private ContentReference ref; + private Bundle extraArguments; + private LooperThread thread; + private volatile long runCount; + private static int sThreadId = 0; + private final static int MSG_BASE = 11451400; + private final static int MSG_INIT = MSG_BASE + 1; + private final static int MSG_MOD = MSG_BASE + 2; + private final static int MSG_EXIT = MSG_BASE + 3; + + @Override + public void setReceiver(StyleReceiver receiver) { + this.receiver = receiver; + } + + @Override + public void reset(@NonNull ContentReference content, @NonNull Bundle extraArguments) { + this.ref = content; + this.extraArguments = extraArguments; + rerun(); + } + + @Override + public void insert(CharPosition start, CharPosition end, CharSequence insertedText) { + if (thread != null) { + increaseRunCount(); + thread.handler.sendMessage(thread.handler.obtainMessage(MSG_MOD, new TextModification(IntPair.pack(start.line, start.column), IntPair.pack(end.line, end.column), insertedText))); + sendUpdate(thread.styles); + } + } + + @Override + public void delete(CharPosition start, CharPosition end, CharSequence deletedText) { + if (thread != null) { + increaseRunCount(); + thread.handler.sendMessage(thread.handler.obtainMessage(MSG_MOD, new TextModification(IntPair.pack(start.line, start.column), IntPair.pack(end.line, end.column), null))); + sendUpdate(thread.styles); + } + } + + @Override + public void rerun() { + if (thread != null) { + thread.callback = () -> { throw new CancelledException(); }; + if (thread.isAlive()) { + final Handler handler = thread.handler; + if (handler != null) { + handler.sendMessage(Message.obtain(thread.handler, MSG_EXIT)); + } + thread.abort = true; + } + } + final Content text = ref.getReference().copyText(false); + text.setUndoEnabled(false); + thread = new LooperThread(() -> thread.handler.sendMessage(thread.handler.obtainMessage(MSG_INIT, text))); + thread.setName("AsyncAnalyzer-" + nextThreadId()); + increaseRunCount(); + thread.start(); + sendUpdate(null); + } + + public abstract Result tokenizeLine(CharSequence line, S state); + + @Override + public Result getState(int line) { + final LooperThread thread = this.thread; + if (thread == Thread.currentThread()) { + return thread.states.get(line); + } + throw new SecurityException("Can not get state from non-analytical or abandoned thread"); + } + + private synchronized void increaseRunCount() { + runCount ++; + } + + private synchronized static int nextThreadId() { + sThreadId++; + return sThreadId; + } + + @Override + public void destroy() { + if (thread != null) { + thread.callback = () -> { throw new CancelledException(); }; + if (thread.isAlive()) { + thread.handler.sendMessage(Message.obtain(thread.handler, MSG_EXIT)); + thread.abort = true; + } + } + receiver = null; + ref = null; + extraArguments = null; + thread = null; + } + + private void sendUpdate(Styles styles) { + final StyleReceiver r = receiver; + if (r != null) { + r.setStyles(this, styles); + } + } + + /** + * Compute code blocks + * @param text The text. can be safely accessed. + */ + public abstract List computeBlocks(Content text, CodeBlockAnalyzeDelegate delegate); + + public Bundle getExtraArguments() { + return extraArguments; + } + + /** + * Helper class for analyzing code block + */ + public class CodeBlockAnalyzeDelegate { + + private final LooperThread + thread; + int suppressSwitch; + + CodeBlockAnalyzeDelegate(@NonNull LooperThread lp) { + thread = lp; + } + + public void setSuppressSwitch(int suppressSwitch) { + this.suppressSwitch = suppressSwitch; + } + + void reset() { + suppressSwitch = Integer.MAX_VALUE; + } + + public boolean isCancelled() { + return thread.myRunCount != runCount; + } + + public boolean isNotCancelled() { + return thread.myRunCount == runCount; + } + + } + + private class LooperThread extends Thread { + + volatile boolean abort; + Looper looper; + Handler handler; + Content shadowed; + long myRunCount; + + List> states = new ArrayList<>(); + Styles styles; + LockedSpans spans; + Runnable callback; + CodeBlockAnalyzeDelegate + delegate = new CodeBlockAnalyzeDelegate(this); + + public LooperThread(Runnable callback) { + this.callback = callback; + } + + private void tryUpdate() { + if (!abort) + sendUpdate(styles); + } + + private void initialize() { + styles = new Styles(spans = new LockedSpans()); + S state = getInitialState(); + Spans.Modifier mdf = spans.modify(); + for (int i = 0;i < shadowed.getLineCount();i++) { + ContentLine line = shadowed.getLine(i); + Result result = tokenizeLine(line, state); + state = result.state; + List spans = result.spans != null ? result. spans :generateSpansForLine(result); + states.add(result.clearSpans()); + mdf.addLineAt(i, spans); + } + styles.blocks = computeBlocks(shadowed, delegate); + styles.setSuppressSwitch(delegate.suppressSwitch); + tryUpdate(); + } + + @Override + public void run() { + Looper.prepare(); + looper = Looper.myLooper(); + handler = new Handler(looper) { + + @Override + public void handleMessage(@NonNull Message msg) { + super.handleMessage(msg); + try { + myRunCount = runCount; + delegate.reset(); + switch (msg.what) { + case MSG_INIT: + shadowed = (Content) msg.obj; + if (!abort) { + initialize(); + } + break; + case MSG_MOD: + if (!abort) { + TextModification mod = (TextModification) msg.obj; + int startLine = IntPair.getFirst(mod.start); + int endLine = IntPair.getFirst(mod.end); + if (mod.changedText == null) { + shadowed.delete(IntPair.getFirst(mod.start), IntPair.getSecond(mod.start), + IntPair.getFirst(mod.end), IntPair.getSecond(mod.end)); + S state = startLine == 0 ? getInitialState() : states.get(startLine - 1).state; + // Remove states + if (endLine >= startLine + 1) { + states.subList(startLine + 1, endLine + 1).clear(); + } + Spans.Modifier mdf = spans.modify(); + for (int i = startLine + 1;i <= endLine;i++) { + mdf.deleteLineAt(startLine + 1); + } + int line = startLine; + while (line < shadowed.getLineCount()){ + Result res = tokenizeLine(shadowed.getLine(line), state); + mdf.setSpansOnLine(line, res.spans != null ? res.spans : generateSpansForLine(res)); + Result old = states.set(line, res.clearSpans()); + if (stateEquals(old.state, res.state)) { + break; + } + state = res.state; + line ++; + } + } else { + shadowed.insert(IntPair.getFirst(mod.start), IntPair.getSecond(mod.start), mod.changedText); + S state = startLine == 0 ? getInitialState() : states.get(startLine - 1).state; + int line = startLine; + Spans.Modifier spans = styles.spans.modify(); + // Add Lines + while (line <= endLine) { + Result res = tokenizeLine(shadowed.getLine(line), state); + if (line == startLine) { + spans.setSpansOnLine(line, res.spans != null ? res.spans : generateSpansForLine(res)); + states.set(line, res.clearSpans()); + } else { + spans.addLineAt(line, res.spans != null ? res.spans : generateSpansForLine(res)); + states.add(line, res.clearSpans()); + } + state = res.state; + line++; + } + // line = end.line + 1, check whether the state equals + while (line < shadowed.getLineCount()) { + Result res = tokenizeLine(shadowed.getLine(line), state); + if (stateEquals(res.state, states.get(line).state)) { + break; + } else { + spans.setSpansOnLine(line, res.spans != null ? res.spans : generateSpansForLine(res)); + states.set(line, res.clearSpans()); + } + line ++; + } + } + } + styles.blocks = computeBlocks(shadowed, delegate); + styles.setSuppressSwitch(delegate.suppressSwitch); + tryUpdate(); + break; + case MSG_EXIT: + looper.quit(); + break; + } + } catch (Exception e) { + Log.w("AsyncAnalysis", "Thread " + Thread.currentThread().getName() + " failed", e); + } + } + + }; + + try { + callback.run(); + Looper.loop(); + } catch (CancelledException e) { + //ignored + } + } + } + + private static class LockedSpans implements Spans { + + private final Lock lock; + private final List lines; + + public LockedSpans() { + lines = new ArrayList<>(128); + lock = new ReentrantLock(); + } + + @Override + public void adjustOnDelete(CharPosition start, CharPosition end) { + + } + + @Override + public void adjustOnInsert(CharPosition start, CharPosition end) { + + } + + @Override + public int getLineCount() { + return lines.size(); + } + + @Override + public Reader read() { + return new LockedSpans.ReaderImpl(); + } + + @Override + public Modifier modify() { + return new LockedSpans.ModifierImpl(); + } + + @Override + public boolean supportsModify() { + return true; + } + + private static class Line { + + public Lock lock = new ReentrantLock(); + + public List spans; + + public Line() { + this(null); + } + + public Line(List s) { + spans = s; + } + + } + + private class ReaderImpl implements Spans.Reader { + + private LockedSpans.Line + line; + + public void moveToLine(int line) { + if (line < 0) { + if (this.line != null) { + this.line.lock.unlock(); + } + this.line = null; + } else if (line >= lines.size()) { + if (this.line != null) { + this.line.lock.unlock(); + } + this.line = null; + } else { + if (this.line != null) { + this.line.lock.unlock(); + } + boolean locked = false; + try { + locked = lock.tryLock(1, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (locked) { + try { + Line obj = lines.get(line); + if (obj.lock.tryLock()) { + this.line = obj; + } else { + this.line = null; + } + } finally { + lock.unlock(); + } + } else { + this.line = null; + } + } + } + + @Override + public int getSpanCount() { + return line == null ? 1 : line.spans.size(); + } + + @Override + public Span getSpanAt(int index) { + return line == null ? Span.obtain(0, EditorColorScheme.TEXT_NORMAL) : line.spans.get(index); + } + + @Override + public List getSpansOnLine(int line) { + ArrayList spans = new ArrayList<>(); + boolean locked = false; + try { + locked = lock.tryLock(1, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (locked) { + LockedSpans.Line + obj = null; + try { + if (line < lines.size()) { + obj = lines.get(line); + } + } finally { + lock.unlock(); + } + if (obj != null && obj.lock.tryLock()) { + try { + return Collections.unmodifiableList(obj.spans); + } finally { + obj.lock.unlock(); + } + } else { + spans.add(getSpanAt(0)); + } + } else { + spans.add(getSpanAt(0)); + } + return spans; + } + } + + private class ModifierImpl implements Modifier { + + @Override + public void setSpansOnLine(int line, List spans) { + lock.lock(); + try { + while (lines.size() <= line) { + ArrayList list = new ArrayList<>(); + list.add(Span.obtain(0, EditorColorScheme.TEXT_NORMAL)); + lines.add(new LockedSpans.Line(list)); + } + lines.get(line).spans = spans; + } finally { + lock.unlock(); + } + } + + @Override + public void addLineAt(int line, List spans) { + lock.lock(); + try { + lines.add(line, new LockedSpans.Line(spans)); + } finally { + lock.unlock(); + } + } + + @Override + public void deleteLineAt(int line) { + lock.lock(); + try { + Line obj = lines.get(line); + obj.lock.lock(); + try { + lines.remove(line); + } finally { + obj.lock.unlock(); + } + } finally { + lock.unlock(); + } + } + } + + } + + private static class TextModification { + + private final long start; + private final long end; + /** + * null for deletion + */ + private final CharSequence changedText; + + TextModification(long start, long end, CharSequence text) { + this.start = start; + this.end = end; + changedText = text; + } + } + + public static class Result extends LineTokenizeResult { + + public Result(@NonNull S_ state, @Nullable List tokens) { + super(state, tokens); + } + + public Result(@NonNull S_ state, @Nullable List tokens, @Nullable List spans) { + super(state, tokens, spans); + } + + @Override + public Result clearSpans() { + super.clearSpans(); + return this; + } + } + + private static class CancelledException extends RuntimeException {} +} + diff --git a/app/src/main/java/com/tyron/code/language/textmate/CodeBlockUtils.java b/app/src/main/java/com/tyron/code/language/textmate/CodeBlockUtils.java new file mode 100644 index 00000000..5118c643 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/textmate/CodeBlockUtils.java @@ -0,0 +1,97 @@ +package com.tyron.code.language.textmate; + +import java.util.ArrayList; +import java.util.List; + +import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; +import io.github.rosemoe.sora.langs.textmate.folding.FoldingRegions; +import io.github.rosemoe.sora.langs.textmate.folding.IndentRange; +import io.github.rosemoe.sora.langs.textmate.folding.PreviousRegion; +import io.github.rosemoe.sora.langs.textmate.folding.RangesCollector; +import io.github.rosemoe.sora.text.Content; +import io.github.rosemoe.sora.textmate.core.internal.oniguruma.OnigRegExp; +import io.github.rosemoe.sora.textmate.core.internal.oniguruma.OnigResult; +import io.github.rosemoe.sora.textmate.core.internal.oniguruma.OnigString; +import io.github.rosemoe.sora.textmate.languageconfiguration.internal.supports.Folding; + +public class CodeBlockUtils { + + @SuppressWarnings("rawtype") + public static FoldingRegions computeRanges(Content model, int tabSize , boolean offSide, Folding markers, int foldingRangesLimit, BaseIncrementalAnalyzeManager.CodeBlockAnalyzeDelegate delegate) throws Exception { + + RangesCollector result = new RangesCollector(foldingRangesLimit, tabSize); + + OnigRegExp pattern = null; + if (markers != null) { + pattern = new OnigRegExp("(" + markers.getMarkersStart() + ")|(?:" + markers.getMarkersEnd() + ")"); + } + + List previousRegions = new ArrayList<>(); + int line = model.getLineCount() + 1; + // sentinel, to make sure there's at least one entry + previousRegions.add(new PreviousRegion(-1, line, line)); + + for (line = model.getLineCount() - 1; line >= 0 && !delegate.isCancelled(); line--) { + String lineContent = model.getLineString(line); + int indent = IndentRange + .computeIndentLevel(model.getLine(line).getRawData(), model.getColumnCount(line), tabSize); + PreviousRegion previous = previousRegions.get(previousRegions.size() - 1); + if (indent == -1) { + if (offSide) { + // for offSide languages, empty lines are associated to the previous block + // note: the next block is already written to the results, so this only + // impacts the end position of the block before + previous.endAbove = line; + } + continue; // only whitespace + } + OnigResult m; + if (pattern != null && (m = pattern.search(new OnigString(lineContent), 0)) != null) { + // folding pattern match + if (m.count() >= 2) { // start pattern match + // discard all regions until the folding pattern + int i = previousRegions.size() - 1; + while (i > 0 && previousRegions.get(i).indent != -2) { + i--; + } + if (i > 0) { + //??? previousRegions.length = i + 1; + previous = previousRegions.get(i); + + // new folding range from pattern, includes the end line + result.insertFirst(line, previous.line, indent); + previous.line = line; + previous.indent = indent; + previous.endAbove = line; + continue; + } else { + // no end marker found, treat line as a regular line + } + } else { // end pattern match + previousRegions.add(new PreviousRegion(-2, line, line)); + continue; + } + } + if (previous.indent > indent) { + // discard all regions with larger indent + do { + previousRegions.remove(previousRegions.size() - 1); + previous = previousRegions.get(previousRegions.size() - 1); + } while (previous.indent > indent); + + // new folding range + int endLineNumber = previous.endAbove - 1; + if (endLineNumber - line >= 1) { // needs at east size 1 + result.insertFirst(line, endLineNumber, indent); + } + } + if (previous.indent == indent) { + previous.endAbove = line; + } else { // previous.indent < indent + // new region with a bigger indent + previousRegions.add(new PreviousRegion(indent, line, line)); + } + } + return result.toIndentRanges(model); + } +} diff --git a/app/src/main/java/com/tyron/code/language/textmate/EmptyTextMateLanguage.java b/app/src/main/java/com/tyron/code/language/textmate/EmptyTextMateLanguage.java new file mode 100644 index 00000000..3cb23283 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/textmate/EmptyTextMateLanguage.java @@ -0,0 +1,7 @@ +package com.tyron.code.language.textmate; + +import io.github.rosemoe.sora.lang.EmptyLanguage; + +public class EmptyTextMateLanguage extends EmptyLanguage { + +} diff --git a/app/src/main/java/com/tyron/code/language/xml/LanguageXML.java b/app/src/main/java/com/tyron/code/language/xml/LanguageXML.java new file mode 100644 index 00000000..bb2fbfd6 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/xml/LanguageXML.java @@ -0,0 +1,253 @@ +package com.tyron.code.language.xml; + +import android.content.res.AssetManager; +import android.os.Bundle; + +import androidx.annotation.NonNull; + +import com.tyron.builder.compiler.manifest.xml.XmlFormatPreferences; +import com.tyron.builder.compiler.manifest.xml.XmlFormatStyle; +import com.tyron.builder.compiler.manifest.xml.XmlPrettyPrinter; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; +import com.tyron.code.analyzer.BaseTextmateAnalyzer; +import com.tyron.code.util.ProjectUtils; +import com.tyron.completion.xml.lexer.XMLLexer; +import com.tyron.editor.Editor; + +import java.io.File; +import java.io.InputStreamReader; +import java.util.List; + +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; +import io.github.rosemoe.sora.lang.completion.CompletionHelper; +import io.github.rosemoe.sora.lang.completion.CompletionItem; +import io.github.rosemoe.sora.lang.completion.CompletionPublisher; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandleResult; +import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; +import io.github.rosemoe.sora.langs.textmate.theme.TextMateColorScheme; +import io.github.rosemoe.sora.text.CharPosition; +import io.github.rosemoe.sora.text.ContentReference; +import io.github.rosemoe.sora.text.TextUtils; +import io.github.rosemoe.sora.util.MyCharacter; +import io.github.rosemoe.sora.widget.SymbolPairMatch; + +public class LanguageXML implements Language { + + private final Editor mEditor; + + private final BaseTextmateAnalyzer mAnalyzer; + + public LanguageXML(Editor editor) { + mEditor = editor; + + try { + AssetManager assetManager = ApplicationLoader.applicationContext.getAssets(); + mAnalyzer = new XMLAnalyzer(editor, "xml.tmLanguage.json", + assetManager.open( + "textmate/xml" + + "/syntaxes/xml" + + ".tmLanguage.json"), + new InputStreamReader( + assetManager.open( + "textmate/java/language-configuration.json")), + ((TextMateColorScheme) ((CodeEditorView) editor).getColorScheme()).getRawTheme()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public boolean isAutoCompleteChar(char ch) { + return MyCharacter.isJavaIdentifierPart(ch) || + ch == '<' || + ch == '/' || + ch == ':' || + ch == '.'; + } + + @Override + public boolean useTab() { + return true; + } + + @Override + public CharSequence format(CharSequence text) { + XmlFormatPreferences preferences = XmlFormatPreferences.defaults(); + File file = mEditor.getCurrentFile(); + CharSequence formatted = null; + if ("AndroidManifest.xml".equals(file.getName())) { + formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), preferences, + XmlFormatStyle.MANIFEST, "\n"); + } else { + if (ProjectUtils.isLayoutXMLFile(file)) { + formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), preferences, + XmlFormatStyle.LAYOUT, "\n"); + } else if (ProjectUtils.isResourceXMLFile(file)) { + formatted = XmlPrettyPrinter.prettyPrint(String.valueOf(text), preferences, + XmlFormatStyle.RESOURCE, "\n"); + } + } + if (formatted == null) { + formatted = text; + } + return formatted; + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return new SymbolPairMatch.DefaultSymbolPairs(); + } + + @Override + public NewlineHandler[] getNewlineHandlers() { + return new NewlineHandler[]{new StartTagHandler(), new EndTagHandler(), + new EndTagAttributeHandler()}; + } + + @Override + public void destroy() { + + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return mAnalyzer; + } + + @Override + public int getInterruptionLevel() { + return INTERRUPTION_LEVEL_SLIGHT; + } + + @Override + public void requireAutoComplete(@NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) throws CompletionCancelledException { + String prefix = CompletionHelper.computePrefix(content, position, this::isAutoCompleteChar); + List items = + new XMLAutoCompleteProvider(mEditor).getAutoCompleteItems(prefix, + position.getLine(), + position.getColumn()); + if (items == null) { + return; + } + for (CompletionItem item : items) { + publisher.addItem(item); + } + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + String text = content.getLine(line) + .substring(0, column); + return getIndentAdvance(text); + } + + public int getIndentAdvance(String content) { + return getIndentAdvance(content, XMLLexer.DEFAULT_MODE, true); + } + + public int getIndentAdvance(String content, int mode, boolean ignore) { + return 0; +// XMLLexer lexer = new XMLLexer(CharStreams.fromString(content)); +// lexer.pushMode(mode); +// +// int advance = 0; +// while (lexer.nextToken() +// .getType() != Lexer.EOF) { +// switch (lexer.getToken() +// .getType()) { +// case XMLLexer.OPEN: +// advance++; +// break; +// case XMLLexer.CLOSE: +// case XMLLexer.SLASH_CLOSE: +// advance--; +// break; +// } +// } +// +// if (advance == 0 && mode != XMLLexer.INSIDE) { +// return getIndentAdvance(content, XMLLexer.INSIDE, ignore); +// } +// +// return advance * mEditor.getTabCount(); + } + + public int getFormatIndent(String line) { + return getIndentAdvance(line, XMLLexer.DEFAULT_MODE, false); + } + + private class EndTagHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + String trim = beforeText.trim(); + if (!trim.startsWith("<")) { + return false; + } + if (!trim.endsWith(">")) { + return false; + } + return afterText.trim() + .startsWith("") && + afterText.trim() + .startsWith(""); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + String text; + StringBuilder sb = new StringBuilder().append("\n") + .append(TextUtils.createIndent(count + tabSize, tabSize, useTab())); + return new NewlineHandleResult(sb, 0); + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/xml/XMLAnalyzer.java b/app/src/main/java/com/tyron/code/language/xml/XMLAnalyzer.java new file mode 100644 index 00000000..2e1ca265 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/xml/XMLAnalyzer.java @@ -0,0 +1,220 @@ +package com.tyron.code.language.xml; + +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import com.tyron.builder.compiler.BuildType; +import com.tyron.builder.compiler.incremental.resource.IncrementalAapt2Task; +import com.tyron.builder.exception.CompilationFailedException; +import com.tyron.builder.log.ILogger; +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.BuildConfig; +import com.tyron.code.analyzer.DiagnosticTextmateAnalyzer; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.code.util.ProjectUtils; +import com.tyron.common.util.Debouncer; +import com.tyron.completion.index.CompilerService; +import com.tyron.completion.java.JavaCompilerProvider; +import com.tyron.completion.java.compiler.CompilerContainer; +import com.tyron.completion.java.compiler.JavaCompilerService; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.completion.xml.task.InjectResourcesTask; +import com.tyron.editor.Editor; +import com.tyron.viewbinding.task.InjectViewBindingTask; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.lang.ref.WeakReference; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.stream.Collectors; + +import io.github.rosemoe.sora.textmate.core.theme.IRawTheme; +import kotlin.Unit; + +public class XMLAnalyzer extends DiagnosticTextmateAnalyzer { + + private boolean mAnalyzerEnabled = false; + + private static final Debouncer sDebouncer = new Debouncer(Duration.ofMillis(900L), Executors.newScheduledThreadPool( + 1, new ThreadFactory() { + @Override + public Thread newThread(Runnable runnable) { + ThreadGroup threadGroup = Looper.getMainLooper().getThread().getThreadGroup(); + return new Thread(threadGroup, runnable, "XmlAnalyzer"); + } + })); + + private final WeakReference mEditorReference; + + public XMLAnalyzer(Editor editor, + String grammarName, + InputStream grammarIns, + Reader languageConfiguration, + IRawTheme theme) throws Exception { + super(editor, grammarName, grammarIns, languageConfiguration, theme); + + mEditorReference = new WeakReference<>(editor); + } + + @Override + public void analyzeInBackground(CharSequence contents) { + Editor editor = mEditorReference.get(); + if (editor == null) { + return; + } + + if (!mAnalyzerEnabled) { + Project project = editor.getProject(); + if (project == null) { + return; + } + + ProgressManager.getInstance().runLater(() -> editor.setAnalyzing(true)); + + sDebouncer.cancel(); + sDebouncer.schedule(cancel -> { + AndroidModule mainModule = (AndroidModule) project.getMainModule(); + try { + InjectResourcesTask.inject(project, mainModule); + InjectViewBindingTask.inject(project, mainModule); + ProgressManager.getInstance().runLater(() -> editor.setAnalyzing(false), 300); + } catch (IOException e) { + e.printStackTrace(); + } + return Unit.INSTANCE; + }); + return; + } + + File currentFile = editor.getCurrentFile(); + if (currentFile == null) { + return; + } + + List diagnosticWrappers = new ArrayList<>(); + + sDebouncer.cancel(); + sDebouncer.schedule(cancel -> { + compile(currentFile, contents.toString(), new ILogger() { + @Override + public void info(DiagnosticWrapper wrapper) { + addMaybe(wrapper); + } + + @Override + public void debug(DiagnosticWrapper wrapper) { + addMaybe(wrapper); + } + + @Override + public void warning(DiagnosticWrapper wrapper) { + addMaybe(wrapper); + } + + @Override + public void error(DiagnosticWrapper wrapper) { + addMaybe(wrapper); + } + + private void addMaybe(DiagnosticWrapper wrapper) { + if (currentFile.equals(wrapper.getSource())) { + diagnosticWrappers.add(wrapper); + } + } + }); + + if (!cancel.invoke()) { + ProgressManager.getInstance().runLater(() -> { + editor.setDiagnostics( + diagnosticWrappers.stream().filter(it -> it.getLineNumber() > 0) + .collect(Collectors.toList())); + }); + } + return Unit.INSTANCE; + }); + + } + + private final Handler handler = new Handler(); + long delay = 1000L; + long lastTime; + + private void compile(File file, String contents, ILogger logger) { + boolean isResource = ProjectUtils.isResourceXMLFile(file); + + if (isResource) { + Project project = ProjectManager.getInstance().getCurrentProject(); + if (project != null) { + Module module = project.getModule(file); + if (module instanceof AndroidModule) { + try { + doGenerate(project, (AndroidModule) module, file, contents, logger); + } catch (IOException | CompilationFailedException e) { + if (BuildConfig.DEBUG) { + Log.e("XMLAnalyzer", "Failed compiling", e); + } + } + } + } + } + } + + private void doGenerate(Project project, + AndroidModule module, + File file, + String contents, + ILogger logger) throws IOException, CompilationFailedException { + if (!file.canWrite() || !file.canRead()) { + return; + } + + if (!module.getFileManager().isOpened(file)) { + Log.e("XMLAnalyzer", "File is not yet opened!"); + return; + } + + Optional fileContent = module.getFileManager().getFileContent(file); + if (!fileContent.isPresent()) { + Log.e("XMLAnalyzer", "No snapshot for file found."); + return; + } + + contents = fileContent.get().toString(); + FileUtils.writeStringToFile(file, contents, StandardCharsets.UTF_8); + IncrementalAapt2Task task = new IncrementalAapt2Task(project, module, logger, false); + + try { + task.prepare(BuildType.DEBUG); + task.run(); + } catch (CompilationFailedException e) { + throw e; + } + + // work around to refresh R.java file + File resourceClass = module.getJavaFile(module.getPackageName() + ".R"); + if (resourceClass != null) { + JavaCompilerProvider provider = + CompilerService.getInstance().getIndex(JavaCompilerProvider.KEY); + JavaCompilerService service = provider.getCompiler(project, module); + + CompilerContainer container = service.compile(resourceClass.toPath()); + container.run(__ -> { + + }); + } + } +} diff --git a/app/src/main/java/com/tyron/code/language/xml/XMLAutoCompleteProvider.java b/app/src/main/java/com/tyron/code/language/xml/XMLAutoCompleteProvider.java new file mode 100644 index 00000000..04716cf4 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/xml/XMLAutoCompleteProvider.java @@ -0,0 +1,41 @@ +package com.tyron.code.language.xml; + +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.language.AbstractAutoCompleteProvider; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.completion.main.CompletionEngine; +import com.tyron.completion.model.CompletionList; +import com.tyron.editor.Editor; + +import java.io.File; + +public class XMLAutoCompleteProvider extends AbstractAutoCompleteProvider { + + private final Editor mEditor; + + public XMLAutoCompleteProvider(Editor editor) { + mEditor = editor; + } + + @Override + public CompletionList getCompletionList(String prefix, int line, int column) { + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + if (currentProject == null) { + return null; + } + Module module = currentProject.getModule(mEditor.getCurrentFile()); + if (!(module instanceof AndroidModule)) { + return null; + } + + File currentFile = mEditor.getCurrentFile(); + if (currentFile == null) { + return null; + } + return CompletionEngine.getInstance().complete(currentProject, module, mEditor, + currentFile, mEditor.getContent().toString(), prefix, line, column, + mEditor.getCaret().getStart()); + } +} diff --git a/app/src/main/java/com/tyron/code/language/xml/XMLColorScheme.java b/app/src/main/java/com/tyron/code/language/xml/XMLColorScheme.java new file mode 100644 index 00000000..d62a6d3c --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/xml/XMLColorScheme.java @@ -0,0 +1,21 @@ +package com.tyron.code.language.xml; + +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; + +public class XMLColorScheme extends EditorColorScheme { + + public XMLColorScheme() { + super(); + } + + @Override + public void applyDefault() { + for (int i = START_COLOR_ID; i <= END_COLOR_ID; i++) { + applyDefault(i); + } + } + + private void applyDefault(int color) { + super.applyDefault(); + } +} diff --git a/app/src/main/java/com/tyron/code/language/xml/Xml.java b/app/src/main/java/com/tyron/code/language/xml/Xml.java new file mode 100644 index 00000000..1f758370 --- /dev/null +++ b/app/src/main/java/com/tyron/code/language/xml/Xml.java @@ -0,0 +1,20 @@ +package com.tyron.code.language.xml; + +import com.tyron.code.language.Language; +import com.tyron.editor.Editor; + +import java.io.File; + + +public class Xml implements Language { + + @Override + public boolean isApplicable(File file) { + return file.getName().endsWith(".xml"); + } + + @Override + public io.github.rosemoe.sora.lang.Language get(Editor editor) { + return new LanguageXML(editor); + } +} diff --git a/app/src/main/java/com/tyron/code/lint/DefaultLintClient.java b/app/src/main/java/com/tyron/code/lint/DefaultLintClient.java new file mode 100644 index 00000000..384295de --- /dev/null +++ b/app/src/main/java/com/tyron/code/lint/DefaultLintClient.java @@ -0,0 +1,52 @@ +package com.tyron.code.lint; + +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.builder.project.api.JavaModule; +import com.tyron.completion.index.CompilerService; +import com.tyron.completion.java.JavaCompilerProvider; +import com.tyron.completion.java.compiler.JavaCompilerService; +import com.tyron.lint.api.Context; +import com.tyron.lint.api.Issue; +import com.tyron.lint.api.Lint; +import com.tyron.lint.api.Location; +import com.tyron.lint.api.Severity; +import com.tyron.lint.api.TextFormat; +import com.tyron.lint.client.LintClient; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class DefaultLintClient extends LintClient { + + private final List mIssues = new ArrayList<>(); + private final Lint mLint; + private final JavaCompilerService mCompiler; + + public DefaultLintClient(JavaModule project) { + mCompiler = CompilerService.getInstance() + .getIndex(JavaCompilerProvider.KEY); + mLint = new Lint(mCompiler, project, this); + } + + public void scan(File file) { + mIssues.clear(); + mLint.scanFile(file); + } + + @Override + public void report(@NonNull Context context, @NonNull Issue issue, @NonNull Severity severity, @Nullable Location location, @NonNull String message, @NonNull TextFormat format) { + if (location != null) { + Log.d("default lint client", "adding issue: " + issue.getId()); + mIssues.add(new LintIssue(issue, severity, location)); + } + } + + public List getReportedIssues() { + return mIssues; + } +} diff --git a/app/src/main/java/com/tyron/code/lint/LintIssue.java b/app/src/main/java/com/tyron/code/lint/LintIssue.java new file mode 100644 index 00000000..492bcc19 --- /dev/null +++ b/app/src/main/java/com/tyron/code/lint/LintIssue.java @@ -0,0 +1,32 @@ +package com.tyron.code.lint; + +import com.tyron.lint.api.Issue; +import com.tyron.lint.api.Location; +import com.tyron.lint.api.Severity; + +public class LintIssue { + + private final Issue mIssue; + + private final Severity mSeverity; + + private final Location mLocation; + + public LintIssue(Issue mIssue, Severity mSeverity, Location mLocation) { + this.mIssue = mIssue; + this.mSeverity = mSeverity; + this.mLocation = mLocation; + } + + public Issue getIssue() { + return mIssue; + } + + public Severity getSeverity() { + return mSeverity; + } + + public Location getLocation() { + return mLocation; + } +} diff --git a/app/src/main/java/com/tyron/code/service/CompilerService.java b/app/src/main/java/com/tyron/code/service/CompilerService.java new file mode 100644 index 00000000..ca9e93cd --- /dev/null +++ b/app/src/main/java/com/tyron/code/service/CompilerService.java @@ -0,0 +1,296 @@ +package com.tyron.code.service; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.core.app.NotificationChannelCompat; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import com.tyron.builder.compiler.AndroidAppBuilder; +import com.tyron.builder.compiler.AndroidAppBundleBuilder; +import com.tyron.builder.compiler.ApkBuilder; +import com.tyron.builder.compiler.BuildType; +import com.tyron.builder.compiler.Builder; +import com.tyron.builder.compiler.ProjectBuilder; +import com.tyron.builder.log.ILogger; +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.BuildConfig; +import com.tyron.code.R; +import com.tyron.code.util.ApkInstaller; +import com.tyron.completion.progress.ProgressIndicator; +import com.tyron.completion.progress.ProgressManager; + +import java.io.File; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.concurrent.Executors; + +public class CompilerService extends Service { + + private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + private final CompilerBinder mBinder = new CompilerBinder(this); + + public static class CompilerBinder extends Binder { + + private final WeakReference mServiceReference; + + public CompilerBinder(CompilerService service) { + mServiceReference = new WeakReference<>(service); + } + + public CompilerService getCompilerService() { + return mServiceReference.get(); + } + } + + private Project mProject; + private ApkBuilder.OnResultListener onResultListener; + private ILogger external; + /** + * Logger that delegates logs to the external logger set + */ + private final ILogger logger = new ILogger() { + + @Override + public void info(DiagnosticWrapper wrapper) { + if (external != null) { + external.info(wrapper); + } + } + + @Override + public void debug(DiagnosticWrapper wrapper) { + if (external != null) { + external.debug(wrapper); + } + } + + @Override + public void warning(DiagnosticWrapper wrapper) { + if (external != null) { + external.warning(wrapper); + } + } + + @Override + public void error(DiagnosticWrapper wrapper) { + if (external != null) { + external.error(wrapper); + } + } + }; + + private boolean shouldShowNotification = true; + + public void setShouldShowNotification(boolean val) { + shouldShowNotification = val; + } + + public void setLogger(ILogger logger) { + this.external = logger; + } + + public void setOnResultListener(ApkBuilder.OnResultListener listener) { + onResultListener = listener; + } + + @Override + public void onCreate() { + super.onCreate(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + + Notification notification = setupNotification(); + startForeground(201, notification); + + return START_STICKY; + } + + private Notification setupNotification() { + return new NotificationCompat.Builder(this, createNotificationChannel()) + .setContentTitle(getString(R.string.app_name)).setSmallIcon(R.drawable.ic_stat_code) + .setContentText("Preparing").setPriority(NotificationCompat.PRIORITY_HIGH) + .setOngoing(true) + .setProgress(100, 0, true) + .build(); + } + + private void updateNotification(String title, String message, int progress) { + updateNotification(title, message, progress, NotificationCompat.PRIORITY_LOW); + } + + private void updateNotification(String title, String message, int progress, int priority) { + new Handler(Looper.getMainLooper()).post(() -> { + NotificationCompat.Builder builder = + new NotificationCompat.Builder(this, "Compiler") + .setContentTitle(title).setContentText(message) + .setSmallIcon(R.drawable.ic_stat_code) + .setPriority(priority); + if (progress != -1) { + builder.setProgress(100, progress, false); + } + NotificationManagerCompat.from(this).notify(201, builder.build()); + }); + } + + private String createNotificationChannel() { + NotificationChannelCompat channel = new NotificationChannelCompat.Builder("Compiler", + NotificationManagerCompat.IMPORTANCE_HIGH) + .setName("Compiler service") + .setDescription("Foreground notification for the compiler") + .build(); + + NotificationManagerCompat.from(this).createNotificationChannel(channel); + + return "Compiler"; + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + public void compile(Project project, BuildType type) { + mProject = project; + + + if (mProject == null) { + if (onResultListener != null) { + mMainHandler.post(() -> onResultListener.onComplete(false, "Failed to open " + + "project (Have you opened a project?)")); + } + + if (shouldShowNotification) { + updateNotification("Compilation failed", "Unable to open project", -1, + NotificationCompat.PRIORITY_HIGH); + } + return; + } + + project.setCompiling(true); + ProgressIndicator indicator = new ProgressIndicator(); + ProgressManager.getInstance().runAsync(() -> { + try { + if (true) { + buildProject(project, type); + } else { + buildMainModule(project, type); + } + } finally { + project.setCompiling(false); + } + }, i -> { + + }, indicator); + } + + private void buildProject(Project project, BuildType type) { + boolean success = true; + + try { + ProjectBuilder projectBuilder = new ProjectBuilder(project, logger); + projectBuilder.setTaskListener(this::updateNotification); + projectBuilder.build(type); + } catch (Throwable e) { + String message; + if (BuildConfig.DEBUG) { + message = Log.getStackTraceString(e); + } else { + message = e.getMessage(); + } + mMainHandler.post(() -> onResultListener.onComplete(false, message)); + success = false; + } + + report(success, type, project.getMainModule()); + } + + private void buildMainModule(Project project, BuildType type) { + Module module = project.getMainModule(); + Builder projectBuilder = getBuilderForProject(module, type); + + module.clear(); + module.index(); + + boolean success = true; + + projectBuilder.setTaskListener(this::updateNotification); + + try { + projectBuilder.build(type); + } catch (Exception e) { + String message; + if (BuildConfig.DEBUG) { + message = Log.getStackTraceString(e); + } else { + message = e.getMessage(); + } + mMainHandler.post(() -> onResultListener.onComplete(false, message)); + success = false; + } + + report(success, type, module); + } + + private void report(boolean success, BuildType type, Module module) { + if (success) { + mMainHandler.post(() -> onResultListener.onComplete(true, "Success")); + } + + + String projectName = "Project"; + if (!success) { + updateNotification(projectName, getString(R.string.compilation_result_failed), -1 + , NotificationCompat.PRIORITY_HIGH); + } else { + if (shouldShowNotification) { + mMainHandler.post(() -> { + NotificationCompat.Builder builder = new NotificationCompat.Builder(this, + "Compiler").setSmallIcon(R.drawable.ic_stat_code).setContentTitle(projectName).setContentText(getString(R.string.compilation_result_success)); + + if (type != BuildType.AAB) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(ApkInstaller.uriFromFile(this, + new File(module.getBuildDirectory(), "bin/signed.apk")), + "application/vnd.android.package-archive"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + PendingIntent pending = PendingIntent.getActivity(this, 0, intent, + PendingIntent.FLAG_IMMUTABLE); + builder.addAction(new NotificationCompat.Action(0, + getString(R.string.compilation_button_install), pending)); + } + NotificationManagerCompat.from(this).notify(201, builder.build()); + }); + } + } + + stopSelf(); + stopForeground(true); + } + + private Builder getBuilderForProject(Module module, BuildType type) { + if (module instanceof AndroidModule) { + if (type == BuildType.AAB) { + return new AndroidAppBundleBuilder(mProject, (AndroidModule) module, logger); + } + return new AndroidAppBuilder(mProject, (AndroidModule) module, logger); + } + return null; + } +} diff --git a/app/src/main/java/com/tyron/code/service/CompilerServiceConnection.java b/app/src/main/java/com/tyron/code/service/CompilerServiceConnection.java new file mode 100644 index 00000000..970ad3c3 --- /dev/null +++ b/app/src/main/java/com/tyron/code/service/CompilerServiceConnection.java @@ -0,0 +1,116 @@ +package com.tyron.code.service; + +import android.content.ComponentName; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.content.SharedPreferences; + +import androidx.preference.PreferenceManager; + +import com.tyron.code.ApplicationLoader; +import com.tyron.common.SharedPreferenceKeys; +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.builder.compiler.BuildType; +import com.tyron.builder.log.ILogger; +import com.tyron.builder.log.LogViewModel; +import com.tyron.code.ui.main.MainViewModel; +import com.tyron.code.util.ApkInstaller; + +import javax.tools.Diagnostic; + +import java.io.File; +import java.util.Objects; + +public class CompilerServiceConnection implements ServiceConnection { + + private final MainViewModel mMainViewModel; + private final LogViewModel mLogViewModel; + + private CompilerService mService; + private BuildType mBuildType; + private boolean mCompiling; + + public CompilerServiceConnection(MainViewModel mainViewModel, LogViewModel logViewModel) { + mMainViewModel = mainViewModel; + mLogViewModel = logViewModel; + } + + public void setBuildType(BuildType type) { + mBuildType = type; + } + + public boolean isCompiling() { + return mCompiling; + } + + public void setShouldShowNotification(boolean val) { + if (mService != null) { + mService.setShouldShowNotification(val); + } + } + + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + mService = ((CompilerService.CompilerBinder) binder).getCompilerService(); + if (mService == null) { + mLogViewModel.e(LogViewModel.BUILD_LOG, "CompilerService is null!"); + return; + } + mService.setLogger(ILogger.wrap(mLogViewModel)); + mService.setShouldShowNotification(false); + mService.setOnResultListener((success, message) -> { + mMainViewModel.setCurrentState(null); + mMainViewModel.setIndexing(false); + + if (success) { + mLogViewModel.d(LogViewModel.BUILD_LOG, message); + mLogViewModel.clear(LogViewModel.APP_LOG); + + File file = new File(ProjectManager.getInstance().getCurrentProject() + .getMainModule().getBuildDirectory(), "bin/signed.apk"); + if (file.exists() && mBuildType != BuildType.AAB) { + SharedPreferences preference = ApplicationLoader.getDefaultPreferences(); + if (preference.getBoolean(SharedPreferenceKeys.INSTALL_APK_DIRECTLY, true)) { + ApkInstaller.installApplication(mService,file.getAbsolutePath()); + } else { + mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_HALF_EXPANDED); + } + DiagnosticWrapper wrapper = new DiagnosticWrapper(); + wrapper.setKind(Diagnostic.Kind.NOTE); + wrapper.setMessage("Generated APK has been saved to " + file.getAbsolutePath()); + wrapper.setExtra("INSTALL"); + wrapper.setSource(file); + wrapper.setCode(""); + wrapper.setOnClickListener((view) -> { + if (view == null || view.getContext() == null) { + return; + } + ApkInstaller.installApplication(view.getContext(), file.getAbsolutePath()); + }); + mLogViewModel.d(LogViewModel.BUILD_LOG, wrapper); + } + } else { + mLogViewModel.e(LogViewModel.BUILD_LOG, message); + if (BottomSheetBehavior.STATE_COLLAPSED == + Objects.requireNonNull(mMainViewModel.getBottomSheetState().getValue())) { + mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_HALF_EXPANDED); + } + } + }); + + if (mBuildType != null) { + mCompiling = true; + mService.compile(ProjectManager.getInstance().getCurrentProject(), mBuildType); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mService = null; + mMainViewModel.setCurrentState(null); + mMainViewModel.setIndexing(false); + mCompiling = false; + } +} diff --git a/app/src/main/java/com/tyron/code/service/IndexService.java b/app/src/main/java/com/tyron/code/service/IndexService.java new file mode 100644 index 00000000..e9d84e77 --- /dev/null +++ b/app/src/main/java/com/tyron/code/service/IndexService.java @@ -0,0 +1,127 @@ +package com.tyron.code.service; + +import android.app.Notification; +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; + +import androidx.core.app.NotificationChannelCompat; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.builder.log.ILogger; +import com.tyron.builder.project.Project; +import com.tyron.code.R; + +import java.lang.ref.WeakReference; + +public class IndexService extends Service { + + private static final int NOTIFICATION_ID = 23; + + private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + private final IndexBinder mBinder = new IndexBinder(this); + + public IndexService() { + } + + public static class IndexBinder extends Binder { + + public final WeakReference mIndexServiceReference; + + public IndexBinder(IndexService service) { + mIndexServiceReference = new WeakReference<>(service); + } + + public void index(Project project, ProjectManager.TaskListener listener, ILogger logger) { + IndexService service = mIndexServiceReference.get(); + if (service == null) { + listener.onComplete(project, false, "Index service is null!"); + } else { + service.index(project, listener, logger); + } + } + } + + + public IBinder onBind(Intent intent) { + return mBinder; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Notification notification = new NotificationCompat.Builder(this, createNotificationChannel()) + .setProgress(100, 0, true) + .setSmallIcon(R.drawable.ic_stat_code) + .setContentTitle("Indexing") + .setContentText("Preparing") + .build(); + updateNotification(notification); + startForeground(NOTIFICATION_ID, notification); + return START_STICKY; + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + private void index(Project project, ProjectManager.TaskListener listener, ILogger logger) { + ProjectManager.TaskListener delegate = new ProjectManager.TaskListener() { + @Override + public void onTaskStarted(String message) { + Notification notification = new NotificationCompat.Builder(IndexService.this, "Index") + .setProgress(100, 0, true) + .setSmallIcon(R.drawable.ic_stat_code) + .setContentTitle("Indexing") + .setContentText(message) + .build(); + updateNotification(notification); + mMainHandler.post(() -> listener.onTaskStarted(message)); + } + + @Override + public void onComplete(Project project, boolean success, String message) { + mMainHandler.post(() -> listener.onComplete(project, success, message)); + stopForeground(true); + stopSelf(); + } + }; + + try { + ProjectManager.getInstance() + .openProject(project, true, delegate, logger); + } catch (Throwable e) { + stopForeground(true); + Notification notification = new NotificationCompat.Builder(IndexService.this, "Index") + .setProgress(100, 0, true) + .setSmallIcon(R.drawable.ic_stat_code) + .setContentTitle("Indexing error") + .setContentText("Unknown error: " + e.getMessage()) + .build(); + updateNotification(notification); + stopSelf(); + throw e; + } + } + + private String createNotificationChannel() { + NotificationChannelCompat channel = new NotificationChannelCompat.Builder("Index", + NotificationManagerCompat.IMPORTANCE_NONE) + .setName("Index Service") + .setDescription("Service that downloads libraries in the foreground") + .build(); + NotificationManagerCompat.from(this) + .createNotificationChannel(channel); + return "Index"; + } + + private void updateNotification(Notification notification) { + NotificationManagerCompat.from(this) + .notify(NOTIFICATION_ID, notification); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/service/IndexServiceConnection.java b/app/src/main/java/com/tyron/code/service/IndexServiceConnection.java new file mode 100644 index 00000000..82e8e958 --- /dev/null +++ b/app/src/main/java/com/tyron/code/service/IndexServiceConnection.java @@ -0,0 +1,128 @@ +package com.tyron.code.service; + +import android.content.ComponentName; +import android.content.ServiceConnection; +import android.os.IBinder; + +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.tyron.builder.model.ProjectSettings; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; +import com.tyron.fileeditor.api.FileEditor; +import com.tyron.fileeditor.api.FileEditorSavedState; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.builder.log.ILogger; +import com.tyron.builder.log.LogViewModel; +import com.tyron.builder.project.Project; +import com.tyron.code.ui.main.MainViewModel; + +import java.io.File; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Handles the communication between the Index service and the main fragment + */ +public class IndexServiceConnection implements ServiceConnection { + + private final MainViewModel mMainViewModel; + private final LogViewModel mLogViewModel; + private final ILogger mLogger; + private Project mProject; + + public IndexServiceConnection(MainViewModel mainViewModel, LogViewModel logViewModel) { + mMainViewModel = mainViewModel; + mLogViewModel = logViewModel; + mLogger = ILogger.wrap(logViewModel); + } + + public void setProject(Project project) { + mProject = project; + } + + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + IndexService.IndexBinder binder = (IndexService.IndexBinder) iBinder; + try { + mProject.setCompiling(true); + binder.index(mProject, new TaskListener(), mLogger); + } finally { + mProject.setCompiling(false); + } + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + mMainViewModel.setIndexing(false); + mMainViewModel.setCurrentState(null); + } + + private class TaskListener implements ProjectManager.TaskListener { + + @Override + public void onTaskStarted(String message) { + mMainViewModel.setCurrentState(message); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void onComplete(Project project, boolean success, String message) { + mMainViewModel.setIndexing(false); + mMainViewModel.setCurrentState(null); + if (success) { + Project currentProject = ProjectManager.getInstance() + .getCurrentProject(); + if (project.equals(currentProject)) { + mMainViewModel.setToolbarTitle(project.getRootFile() + .getName()); + } + } else { + if (mMainViewModel.getBottomSheetState() + .getValue() != BottomSheetBehavior.STATE_EXPANDED) { + mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_HALF_EXPANDED); + } + mLogViewModel.e(LogViewModel.BUILD_LOG, message); + } + } + } + + public static List getOpenedFiles(ProjectSettings settings) { + String openedFilesString = settings.getString(ProjectSettings.SAVED_EDITOR_FILES, null); + if (openedFilesString != null) { + try { + Type type = new TypeToken>() { + }.getType(); + List savedStates = + new Gson().fromJson(openedFilesString, type); + return savedStates.stream() + .filter(it -> it.getFile() + .exists()) + .map(FileEditorManagerImpl.getInstance()::openFile) + .collect(Collectors.toList()); + } catch (Throwable e) { + // ignored, users may have edited the file manually and is corrupt + // just return an empty editor list + } + } + return new ArrayList<>(); + } + + public static void restoreFileEditors(Project currentProject, MainViewModel viewModel) { + List openedFiles = getOpenedFiles(currentProject.getSettings()); + + List value = viewModel.getFiles() + .getValue(); + if (value != null) { + List toClose = value.stream() + .map(FileEditor::getFile) + .filter(file -> openedFiles.stream() + .noneMatch(editor -> file.equals(editor.getFile()))) + .collect(Collectors.toList()); + toClose.forEach(FileEditorManagerImpl.getInstance()::closeFile); + } + viewModel.setFiles(openedFiles); + } +} diff --git a/app/src/main/java/com/tyron/code/template/CodeTemplate.java b/app/src/main/java/com/tyron/code/template/CodeTemplate.java new file mode 100644 index 00000000..7d59b9cb --- /dev/null +++ b/app/src/main/java/com/tyron/code/template/CodeTemplate.java @@ -0,0 +1,85 @@ +package com.tyron.code.template; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.CallSuper; +import androidx.annotation.NonNull; + +/** + * Class for creating different templates for classes such as an interface, + * abstract or regular classes + */ +public class CodeTemplate implements Parcelable { + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public CodeTemplate createFromParcel(Parcel parcel) { + return new CodeTemplate(parcel); + } + + @Override + public CodeTemplate[] newArray(int i) { + return new CodeTemplate[i]; + } + }; + + /** + * Used to replace the template package name with the app's package name + */ + public static final String PACKAGE_NAME = "${packageName}"; + + public static final String CLASS_NAME = "${className}"; + + protected String mContents; + + public CodeTemplate() { + + } + + public CodeTemplate(Parcel in) { + mContents = in.readString(); + } + + public final String get() { + setup(); + return mContents; + } + + public final void setContents(String contents) { + mContents = contents; + } + + /** + * Subclasses must call setContents(); + */ + public void setup() { + + } + + public String getName() { + throw new IllegalStateException("getName() is not subclassed"); + } + + public String getExtension() { + throw new IllegalStateException("getExtension() is not subclassed"); + } + + + + @NonNull + @Override + public String toString() { + return getName(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeString(mContents); + } +} diff --git a/app/src/main/java/com/tyron/code/template/android/ActivityTemplate.java b/app/src/main/java/com/tyron/code/template/android/ActivityTemplate.java new file mode 100644 index 00000000..eb2e0696 --- /dev/null +++ b/app/src/main/java/com/tyron/code/template/android/ActivityTemplate.java @@ -0,0 +1,35 @@ +package com.tyron.code.template.android; + +import android.os.Parcel; + +import com.tyron.code.template.CodeTemplate; +import com.tyron.code.template.java.JavaClassTemplate; + +public class ActivityTemplate extends JavaClassTemplate { + + public ActivityTemplate() { + super(); + } + + public ActivityTemplate(Parcel in) { + super(in); + } + + @Override + public String getName() { + return "Activity"; + } + + @Override + public void setup() { + setContents("package " + CodeTemplate.PACKAGE_NAME + ";\n\n" + + "import android.app.Activity;\n" + + "import android.os.Bundle;\n\n" + + "public class " + CodeTemplate.CLASS_NAME + " extends Activity {\n\n" + + " @Override\n" + + " public void onCreate(Bundle savedInstanceState) {\n" + + " super.onCreate(savedInstanceState);\n" + + " }\n" + + "}"); + } +} diff --git a/app/src/main/java/com/tyron/code/template/java/AbstractTemplate.java b/app/src/main/java/com/tyron/code/template/java/AbstractTemplate.java new file mode 100644 index 00000000..05e3bde3 --- /dev/null +++ b/app/src/main/java/com/tyron/code/template/java/AbstractTemplate.java @@ -0,0 +1,32 @@ +package com.tyron.code.template.java; + +import android.os.Parcel; + +import com.tyron.code.template.CodeTemplate; + +public class AbstractTemplate extends JavaClassTemplate { + + public AbstractTemplate() { + + } + + public AbstractTemplate(Parcel in) { + super(in); + } + + @Override + public String getName() { + return "Abstract"; + } + + @Override + public void setup() { + setContents("package " + + CodeTemplate.PACKAGE_NAME + + ";\n" + + "\npublic abstract" + + " class " + + CodeTemplate.CLASS_NAME + + " {\n\t\n}"); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/template/java/InterfaceTemplate.java b/app/src/main/java/com/tyron/code/template/java/InterfaceTemplate.java new file mode 100644 index 00000000..6a051fbd --- /dev/null +++ b/app/src/main/java/com/tyron/code/template/java/InterfaceTemplate.java @@ -0,0 +1,30 @@ +package com.tyron.code.template.java; + +import android.os.Parcel; + +import com.tyron.code.template.CodeTemplate; + +public class InterfaceTemplate extends JavaClassTemplate { + + public InterfaceTemplate() { + + } + + public InterfaceTemplate(Parcel in) { + super(in); + } + + @Override + public String getName() { + return "Interface"; + } + + @Override + public void setup() { + setContents("package " + + CodeTemplate.PACKAGE_NAME + + ";\n" + "\npublic interface " + + CodeTemplate.CLASS_NAME + + " {\n\t\n}"); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/template/java/JavaClassTemplate.java b/app/src/main/java/com/tyron/code/template/java/JavaClassTemplate.java new file mode 100644 index 00000000..b8af2fbc --- /dev/null +++ b/app/src/main/java/com/tyron/code/template/java/JavaClassTemplate.java @@ -0,0 +1,35 @@ +package com.tyron.code.template.java; + +import android.os.Parcel; + +import com.tyron.code.template.CodeTemplate; + +public class JavaClassTemplate extends CodeTemplate { + + public JavaClassTemplate() { + + } + + public JavaClassTemplate(Parcel in) { + super(in); + } + + @Override + public String getName() { + return "Java class"; + } + + @Override + public void setup() { + setContents("package " + + CodeTemplate.PACKAGE_NAME + + ";\n" + "\npublic class " + + CodeTemplate.CLASS_NAME + + " {\n\t\n}"); + } + + @Override + public String getExtension() { + return ".java"; + } +} diff --git a/app/src/main/java/com/tyron/code/template/kotlin/KotlinAbstractClassTemplate.java b/app/src/main/java/com/tyron/code/template/kotlin/KotlinAbstractClassTemplate.java new file mode 100644 index 00000000..82fc376a --- /dev/null +++ b/app/src/main/java/com/tyron/code/template/kotlin/KotlinAbstractClassTemplate.java @@ -0,0 +1,29 @@ +package com.tyron.code.template.kotlin; + +import android.os.Parcel; + +import com.tyron.code.template.CodeTemplate; + +public class KotlinAbstractClassTemplate extends KotlinClassTemplate { + + public KotlinAbstractClassTemplate() { + + } + + public KotlinAbstractClassTemplate(Parcel in) { + super(in); + } + + @Override + public String getName() { + return "Kotlin Abstract Class"; + } + + @Override + public void setup() { + setContents("package " + CodeTemplate.PACKAGE_NAME + "\n\n" + + "abstract class " + CodeTemplate.CLASS_NAME + " {\n" + + "\t" + "\n" + + "}"); + } +} diff --git a/app/src/main/java/com/tyron/code/template/kotlin/KotlinClassTemplate.java b/app/src/main/java/com/tyron/code/template/kotlin/KotlinClassTemplate.java new file mode 100644 index 00000000..05fbfa4e --- /dev/null +++ b/app/src/main/java/com/tyron/code/template/kotlin/KotlinClassTemplate.java @@ -0,0 +1,34 @@ +package com.tyron.code.template.kotlin; + +import android.os.Parcel; + +import com.tyron.code.template.CodeTemplate; + +public class KotlinClassTemplate extends CodeTemplate { + + public KotlinClassTemplate() { + + } + + public KotlinClassTemplate(Parcel in) { + super(in); + } + + @Override + public String getName() { + return "Kotlin class"; + } + + @Override + public void setup() { + setContents("package " + CodeTemplate.PACKAGE_NAME + "\n\n" + + "class " + CodeTemplate.CLASS_NAME + " {\n" + + "\t" + "\n" + + "}"); + } + + @Override + public String getExtension() { + return ".kt"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/template/kotlin/KotlinInterfaceTemplate.java b/app/src/main/java/com/tyron/code/template/kotlin/KotlinInterfaceTemplate.java new file mode 100644 index 00000000..6bac16ff --- /dev/null +++ b/app/src/main/java/com/tyron/code/template/kotlin/KotlinInterfaceTemplate.java @@ -0,0 +1,29 @@ +package com.tyron.code.template.kotlin; + +import android.os.Parcel; + +import com.tyron.code.template.CodeTemplate; + +public class KotlinInterfaceTemplate extends KotlinClassTemplate { + + public KotlinInterfaceTemplate() { + + } + + public KotlinInterfaceTemplate(Parcel in) { + super(in); + } + + @Override + public String getName() { + return "Kotlin Interface"; + } + + @Override + public void setup() { + setContents("package " + CodeTemplate.PACKAGE_NAME + "\n\n" + + "interface " + CodeTemplate.CLASS_NAME + " {\n" + + "\t" + "\n" + + "}"); + } +} diff --git a/app/src/main/java/com/tyron/code/template/xml/LayoutTemplate.java b/app/src/main/java/com/tyron/code/template/xml/LayoutTemplate.java new file mode 100644 index 00000000..a447020a --- /dev/null +++ b/app/src/main/java/com/tyron/code/template/xml/LayoutTemplate.java @@ -0,0 +1,36 @@ +package com.tyron.code.template.xml; + +import android.os.Parcel; + +import com.tyron.code.template.CodeTemplate; + +public class LayoutTemplate extends CodeTemplate { + public LayoutTemplate() { + + } + + public LayoutTemplate(Parcel in) { + super(in); + } + + @Override + public String getName() { + return "Layout XML"; + } + + @Override + public void setup() { + setContents("\n" + + "\n" + + ""); + } + + @Override + public String getExtension() { + return ".xml"; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/BottomEditorFragment.java b/app/src/main/java/com/tyron/code/ui/editor/BottomEditorFragment.java new file mode 100644 index 00000000..8696f211 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/BottomEditorFragment.java @@ -0,0 +1,216 @@ +package com.tyron.code.ui.editor; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; + +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; +import com.google.common.base.Strings; +import com.tyron.builder.log.LogViewModel; +import com.tyron.code.R; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorFragment; +import com.tyron.code.ui.editor.log.AppLogFragment; +import com.tyron.code.ui.editor.shortcuts.ShortcutAction; +import com.tyron.code.ui.editor.shortcuts.ShortcutItem; +import com.tyron.code.ui.editor.shortcuts.ShortcutsAdapter; +import com.tyron.code.ui.editor.shortcuts.action.CursorMoveAction; +import com.tyron.code.ui.editor.shortcuts.action.RedoAction; +import com.tyron.code.ui.editor.shortcuts.action.TextInsertAction; +import com.tyron.code.ui.editor.shortcuts.action.UndoAction; +import com.tyron.code.ui.main.MainViewModel; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.editor.Caret; +import com.tyron.editor.Editor; +import com.tyron.fileeditor.api.FileEditor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@SuppressWarnings("FieldCanBeLocal") +public class BottomEditorFragment extends Fragment { + + public static final String OFFSET_KEY = "offsetKey"; + + public static BottomEditorFragment newInstance() { + return new BottomEditorFragment(); + } + + private View mRoot; + + private TabLayout mTabLayout; + private LinearLayout mRowLayout; + private ViewPager2 mPager; + private PageAdapter mAdapter; + + private RecyclerView mShortcutsRecyclerView; + + private MainViewModel mFilesViewModel; + + public BottomEditorFragment() { + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + mRoot = inflater.inflate(R.layout.bottom_editor_fragment, container, false); + mRowLayout = mRoot.findViewById(R.id.row_layout); + mShortcutsRecyclerView = mRoot.findViewById(R.id.recyclerview_shortcuts); + mPager = mRoot.findViewById(R.id.viewpager); + mTabLayout = mRoot.findViewById(R.id.tablayout); + + mFilesViewModel = new ViewModelProvider(requireActivity()) + .get(MainViewModel.class); + return mRoot; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + mAdapter = new PageAdapter(this); + mPager.setAdapter(mAdapter); + mPager.setUserInputEnabled(false); + new TabLayoutMediator(mTabLayout, mPager, (tab, position) -> { + switch (position) { + case 0: + tab.setText(R.string.tab_build_logs_title); + break; + default: + case 1: + tab.setText(R.string.tab_app_logs_title); + break; + case 2: + tab.setText(R.string.tab_diagnostics_title); + break; + case 3: + tab.setText(R.string.tab_ide_logs_title); + break; + } + }).attach(); + + ShortcutsAdapter adapter = new ShortcutsAdapter(getShortcuts()); + LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext(), + LinearLayoutManager.HORIZONTAL, false); + mShortcutsRecyclerView.setLayoutManager(layoutManager); + mShortcutsRecyclerView.setAdapter(adapter); + + adapter.setOnShortcutSelectedListener((item, pos) -> { + FileEditor currentFile = mFilesViewModel.getCurrentFileEditor(); + if (currentFile != null) { + if (currentFile.getFragment() instanceof CodeEditorFragment) { + ((CodeEditorFragment) currentFile.getFragment()).performShortcut(item); + } + } + }); + + getParentFragmentManager().setFragmentResultListener(OFFSET_KEY, getViewLifecycleOwner(), + ((requestKey, result) -> { + setOffset(result.getFloat("offset", 0f)); + })); + } + + private void setOffset(float offset) { + if (mRowLayout == null) { + return; + } + + if (offset >= 0.50f) { + float invertedOffset = 0.5f - offset; + setRowOffset(((invertedOffset + 0.5f) * 2f)); + } else { + if (mRowLayout.getHeight() != AndroidUtilities.dp(30)) { + setRowOffset(1f); + } + } + } + + private void setRowOffset(float offset) { + mRowLayout.getLayoutParams() + .height = Math.round(AndroidUtilities.dp(38) * offset); + mRowLayout.requestLayout(); + } + + private List getShortcuts() { + List strings = Arrays.asList("<", ">", ";", "{", "}", ":"); + List items = new ArrayList<>(); + items.add(new ShortcutItem(Collections.singletonList(new ShortcutAction() { + + @Override + public boolean isApplicable(String kind) { + return "tab".equals(kind); + } + + @Override + public void apply(Editor editor, ShortcutItem item) { + Caret cursor = editor.getCaret(); + if (editor.useTab()) { + editor.insert(cursor.getStartLine(), cursor.getStartColumn(), "\t"); + } else { + editor.insert(cursor.getStartLine(), cursor.getStartColumn(), + Strings.repeat(" ", editor.getTabCount())); + } + } + }), "->", "tab")); + items.addAll(strings.stream() + .map(item -> { + ShortcutItem it = new ShortcutItem(); + it.label = item; + it.kind = TextInsertAction.KIND; + it.actions = Collections.singletonList(new TextInsertAction()); + return it; + }).collect(Collectors.toList())); + Collections.addAll(items, + new ShortcutItem(Collections.singletonList(new UndoAction()), "⬿", UndoAction.KIND), + new ShortcutItem(Collections.singletonList(new RedoAction()), "⤳", RedoAction.KIND), + new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.UP, 1)), "↑", CursorMoveAction.KIND), + new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.DOWN, 1)), "↓", CursorMoveAction.KIND), + new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.LEFT, 1)), "â†", CursorMoveAction.KIND), + new ShortcutItem(Collections.singletonList(new CursorMoveAction(CursorMoveAction.Direction.RIGHT, 1)), "→", CursorMoveAction.KIND) + ); + + return items; + } + + @SuppressWarnings("deprecation") + private static class PageAdapter extends FragmentStateAdapter { + + public PageAdapter(@NonNull Fragment fragment) { + super(fragment); + } + + @NonNull + @Override + public Fragment createFragment(int position) { + switch (position) { + case 0: + return AppLogFragment.newInstance(LogViewModel.BUILD_LOG); + default: + case 1: + return AppLogFragment.newInstance(LogViewModel.APP_LOG); + case 2: + return AppLogFragment.newInstance(LogViewModel.DEBUG); + case 3: + return AppLogFragment.newInstance(LogViewModel.IDE); + } + } + + @Override + public int getItemCount() { + return 4; + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/EditorContainerFragment.java b/app/src/main/java/com/tyron/code/ui/editor/EditorContainerFragment.java new file mode 100644 index 00000000..ca4c3a1f --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/EditorContainerFragment.java @@ -0,0 +1,355 @@ +package com.tyron.code.ui.editor; + +import android.annotation.SuppressLint; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.PopupMenu; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.viewpager2.widget.ViewPager2; + +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; +import com.tyron.actions.ActionManager; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.util.DataContextUtils; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.FileManager; +import com.tyron.builder.project.api.Module; +import com.tyron.builder.project.listener.FileListener; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.R; +import com.tyron.code.ui.editor.adapter.PageAdapter; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorFragment; +import com.tyron.code.ui.editor.impl.xml.LayoutTextEditorFragment; +import com.tyron.code.ui.main.MainFragment; +import com.tyron.code.ui.main.MainViewModel; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.common.util.UniqueNameBuilder; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.fileeditor.api.FileEditor; +import com.tyron.fileeditor.api.FileEditorManager; + +import java.io.File; +import java.util.List; +import java.util.Objects; + +public class EditorContainerFragment extends Fragment implements FileListener, + ProjectManager.OnProjectOpenListener, SharedPreferences.OnSharedPreferenceChangeListener { + + public static final String SAVE_ALL_KEY = "saveAllEditors"; + public static final String PREVIEW_KEY = "previewEditor"; + public static final String FORMAT_KEY = "formatEditor"; + + private TabLayout mTabLayout; + private ViewPager2 mPager; + private PageAdapter mAdapter; + private BottomSheetBehavior mBehavior; + + private MainViewModel mMainViewModel; + + private FileEditorManager mFileEditorManager; + private SharedPreferences pref; + + private final OnBackPressedCallback mOnBackPressedCallback = new OnBackPressedCallback(false) { + @Override + public void handleOnBackPressed() { + mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_COLLAPSED); + } + }; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + pref = ApplicationLoader.getDefaultPreferences(); + mMainViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class); + requireActivity().getOnBackPressedDispatcher() + .addCallback(this, mOnBackPressedCallback); + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putInt("bottom_sheet_state", mBehavior.getState()); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + CoordinatorLayout root = + (CoordinatorLayout) inflater.inflate(R.layout.editor_container_fragment, container, + false); + + ((FileEditorManagerImpl) FileEditorManagerImpl.getInstance()).attach(mMainViewModel, + getChildFragmentManager()); + + mAdapter = new PageAdapter(getChildFragmentManager(), getLifecycle()); + mPager = root.findViewById(R.id.viewpager); + mPager.setAdapter(mAdapter); + mPager.setUserInputEnabled(false); + + mTabLayout = root.findViewById(R.id.tablayout); + mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { + @Override + public void onTabUnselected(TabLayout.Tab p1) { + Fragment fragment = getChildFragmentManager().findFragmentByTag( + "f" + mAdapter.getItemId(p1.getPosition())); + if (fragment instanceof CodeEditorFragment) { + ((CodeEditorFragment) fragment).save(true); + } + } + + @Override + public void onTabReselected(TabLayout.Tab p1) { + PopupMenu popup = new PopupMenu(requireActivity(), p1.view); + + DataContext dataContext = DataContextUtils.getDataContext(mTabLayout); + dataContext.putData(CommonDataKeys.PROJECT, ProjectManager.getInstance() + .getCurrentProject()); + dataContext.putData(CommonDataKeys.FRAGMENT, EditorContainerFragment.this); + dataContext.putData(MainFragment.MAIN_VIEW_MODEL_KEY, mMainViewModel); + dataContext.putData(CommonDataKeys.FILE_EDITOR_KEY, + mMainViewModel.getCurrentFileEditor()); + + ActionManager.getInstance() + .fillMenu(dataContext, popup.getMenu(), ActionPlaces.EDITOR_TAB, true, + false); + popup.show(); + } + + @Override + public void onTabSelected(TabLayout.Tab p1) { + updateTab(p1, p1.getPosition()); + mMainViewModel.setCurrentPosition(p1.getPosition(), false); + + ProgressManager.getInstance().runLater(() -> getParentFragmentManager().setFragmentResult(MainFragment.REFRESH_TOOLBAR_KEY, + Bundle.EMPTY)); + } + }); + new TabLayoutMediator(mTabLayout, mPager, true, false, this::updateTab).attach(); + + View persistentSheet = root.findViewById(R.id.persistent_sheet); + mBehavior = BottomSheetBehavior.from(persistentSheet); + mBehavior.setGestureInsetBottomIgnored(true); + + mBehavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { + @Override + public void onStateChanged(@NonNull View p1, int state) { + mMainViewModel.setBottomSheetState(state); + } + + @Override + public void onSlide(@NonNull View bottomSheet, float slideOffset) { + if (isAdded()) { + Bundle bundle = new Bundle(); + bundle.putFloat("offset", slideOffset); + getChildFragmentManager().setFragmentResult(BottomEditorFragment.OFFSET_KEY, bundle); + } + } + }); + mBehavior.setHalfExpandedRatio(0.3f); + mBehavior.setFitToContents(false); + + ProjectManager.getInstance() + .addOnProjectOpenListener(this); + + if (savedInstanceState != null) { + restoreViewState(savedInstanceState); + } + return root; + } + + private void updateTab(TabLayout.Tab tab, int pos) { + FileEditor currentEditor = + Objects.requireNonNull(mMainViewModel.getFiles().getValue()).get(pos); + File current = currentEditor.getFile(); + + String text = current != null ? getUniqueTabTitle(current) : "Unknown"; + if (currentEditor.isModified()) { + text = "*" + text; + } + + tab.setText(text); + } + + private String getUniqueTabTitle(@NonNull File currentFile) { + if (!pref.getBoolean(SharedPreferenceKeys.EDITOR_TAB_UNIQUE_FILE_NAME, true)) { + return currentFile.getName(); + } + + int sameFileNameCount = 0; + UniqueNameBuilder builder = new UniqueNameBuilder<>("", "/"); + + for (FileEditor fileEditor : Objects.requireNonNull(mMainViewModel.getFiles().getValue())) { + File openFile = fileEditor.getFile(); + if (openFile.getName().equals(currentFile.getName())) { + sameFileNameCount++; + } + builder.addPath(openFile, openFile.getPath()); + } + + if (sameFileNameCount > 1) { + return builder.getShortPath(currentFile); + } else { + return currentFile.getName(); + } + } + + @Override + public void onProjectOpen(Project project) { + for (Module module : project.getModules()) { + FileManager fileManager = module.getFileManager(); + fileManager.addSnapshotListener(this); + } + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + ApplicationLoader.getDefaultPreferences().registerOnSharedPreferenceChangeListener(this); + + mMainViewModel.getFiles() + .observe(getViewLifecycleOwner(), files -> { + mAdapter.submitList(files); + mTabLayout.setVisibility(files.isEmpty() ? View.GONE : View.VISIBLE); + }); + mMainViewModel.getCurrentPosition() + .observe(getViewLifecycleOwner(), pos -> { + mPager.setCurrentItem(pos, false); + TabLayout.Tab tab = mTabLayout.getTabAt(pos); + if (tab != null) { + updateTab(tab, pos); + } + if (mBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { + mMainViewModel.setBottomSheetState(BottomSheetBehavior.STATE_COLLAPSED); + } + }); + mMainViewModel.getBottomSheetState() + .observe(getViewLifecycleOwner(), state -> { + mBehavior.setState(state); + mOnBackPressedCallback.setEnabled(state == BottomSheetBehavior.STATE_EXPANDED); + }); + + getParentFragmentManager().setFragmentResultListener(SAVE_ALL_KEY, getViewLifecycleOwner(), + (requestKey, result) -> saveAll()); + getParentFragmentManager().setFragmentResultListener(PREVIEW_KEY, getViewLifecycleOwner(), + ((requestKey, result) -> previewCurrent())); + getParentFragmentManager().setFragmentResultListener(FORMAT_KEY, getViewLifecycleOwner(), + (((requestKey, result) -> formatCurrent()))); + } + + private void restoreViewState(@NonNull Bundle state) { + int behaviorState = state.getInt("bottom_sheet_state", BottomSheetBehavior.STATE_COLLAPSED); + mMainViewModel.setBottomSheetState(behaviorState); + Bundle floatOffset = new Bundle(); + floatOffset.putFloat("offset", + behaviorState == BottomSheetBehavior.STATE_EXPANDED ? 1 : 0f); + getChildFragmentManager().setFragmentResult(BottomEditorFragment.OFFSET_KEY, floatOffset); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + ProjectManager projectManager = ProjectManager.getInstance(); + Project currentProject = projectManager.getCurrentProject(); + if (currentProject != null) { + for (Module module : currentProject.getModules()) { + FileManager fileManager = module.getFileManager(); + fileManager.removeSnapshotListener(this); + } + } + } + + private void formatCurrent() { + String tag = "f" + mAdapter.getItemId(mPager.getCurrentItem()); + Fragment fragment = getChildFragmentManager().findFragmentByTag(tag); + if (fragment instanceof CodeEditorFragment) { + ((CodeEditorFragment) fragment).format(); + } + } + + private void previewCurrent() { + String tag = "f" + mAdapter.getItemId(mPager.getCurrentItem()); + Fragment fragment = getChildFragmentManager().findFragmentByTag(tag); + if (fragment instanceof LayoutTextEditorFragment) { + ((LayoutTextEditorFragment) fragment).preview(); + } + } + + private void saveAll() { + for (int i = 0; i < mAdapter.getItemCount(); i++) { + String tag = "f" + mAdapter.getItemId(i); + Fragment fragment = getChildFragmentManager().findFragmentByTag(tag); + if (fragment instanceof Savable) { + ((Savable) fragment).save(true); + } + } + } + + @Override + public void onSnapshotChanged(File file, CharSequence contents) { + if (mTabLayout == null) { + return; + } + List editors = mMainViewModel.getFiles() + .getValue(); + if (editors == null) { + return; + } + + int found = -1; + for (int i = 0; i < editors.size(); i++) { + FileEditor editor = editors.get(i); + if (file.equals(editor.getFile())) { + found = i; + break; + } + } + + if (found == -1) { + return; + } + + TabLayout.Tab tab = mTabLayout.getTabAt(found); + if (tab == null) { + return; + } + updateTab(tab, found); + } + + @SuppressLint("NotifyDataSetChanged") + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + switch (key) { + case SharedPreferenceKeys.EDITOR_TAB_UNIQUE_FILE_NAME: + if (mAdapter != null) { + mAdapter.notifyDataSetChanged(); + } + break; + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + + ApplicationLoader.getDefaultPreferences().unregisterOnSharedPreferenceChangeListener(this); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/EditorViewModel.java b/app/src/main/java/com/tyron/code/ui/editor/EditorViewModel.java new file mode 100644 index 00000000..4ca48dc5 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/EditorViewModel.java @@ -0,0 +1,18 @@ +package com.tyron.code.ui.editor; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +public class EditorViewModel extends ViewModel { + + private final MutableLiveData mAnalyzeState = new MutableLiveData<>(false); + + public void setAnalyzeState(boolean analyzing) { + mAnalyzeState.setValue(analyzing); + } + + public LiveData getAnalyzeState() { + return mAnalyzeState; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/Savable.java b/app/src/main/java/com/tyron/code/ui/editor/Savable.java new file mode 100644 index 00000000..fdbfec01 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/Savable.java @@ -0,0 +1,19 @@ +package com.tyron.code.ui.editor; + +/** + * Fragments may implement this class to indicate that its contents + * can be saved. + */ +public interface Savable { + + /** + * @return Whether the content can be saved + */ + boolean canSave(); + + /** + * Saves the contents of this class + * @param toDisk whether the contents will be written to file or stored in memory + */ + void save(boolean toDisk); +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/CloseAllEditorAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/CloseAllEditorAction.java new file mode 100644 index 00000000..7d4b18c6 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/CloseAllEditorAction.java @@ -0,0 +1,37 @@ +package com.tyron.code.ui.editor.action; + +import androidx.annotation.NonNull; + +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.code.R; +import com.tyron.code.ui.main.MainFragment; +import com.tyron.code.ui.main.MainViewModel; + +public class CloseAllEditorAction extends AnAction { + + public static final String ID = "editorTabCloseAll"; + + @Override + public void update(@NonNull AnActionEvent event) { + MainViewModel mainViewModel = event.getData(MainFragment.MAIN_VIEW_MODEL_KEY); + + event.getPresentation().setVisible(false); + if (!ActionPlaces.EDITOR_TAB.equals(event.getPlace())) { + return; + } + if (mainViewModel == null) { + return; + } + + event.getPresentation().setVisible(true); + event.getPresentation().setText(event.getDataContext().getString(R.string.menu_close_all)); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + MainViewModel mainViewModel = e.getData(MainFragment.MAIN_VIEW_MODEL_KEY); + mainViewModel.clear(); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/CloseFileEditorAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/CloseFileEditorAction.java new file mode 100644 index 00000000..7ca64cd8 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/CloseFileEditorAction.java @@ -0,0 +1,46 @@ +package com.tyron.code.ui.editor.action; + +import androidx.annotation.NonNull; + +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; +import com.tyron.fileeditor.api.FileEditor; +import com.tyron.code.ui.main.MainFragment; +import com.tyron.code.ui.main.MainViewModel; + +public class CloseFileEditorAction extends AnAction { + + public static final String ID = "editorTabCloseFile"; + + @Override + public void update(@NonNull AnActionEvent event) { + MainViewModel mainViewModel = event.getData(MainFragment.MAIN_VIEW_MODEL_KEY); + FileEditor fileEditor = event.getData(CommonDataKeys.FILE_EDITOR_KEY); + + event.getPresentation().setVisible(false); + if (!ActionPlaces.EDITOR_TAB.equals(event.getPlace())) { + return; + } + + if (fileEditor == null) { + return; + } + + if (mainViewModel == null) { + return; + } + + event.getPresentation().setVisible(true); + event.getPresentation().setText(event.getDataContext().getString(R.string.menu_close_file)); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + MainViewModel mainViewModel = e.getRequiredData(MainFragment.MAIN_VIEW_MODEL_KEY); + FileEditor fileEditor = e.getRequiredData(CommonDataKeys.FILE_EDITOR_KEY); + mainViewModel.removeFile(fileEditor.getFile()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/CloseOtherEditorAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/CloseOtherEditorAction.java new file mode 100644 index 00000000..c99501c2 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/CloseOtherEditorAction.java @@ -0,0 +1,46 @@ +package com.tyron.code.ui.editor.action; + +import androidx.annotation.NonNull; + +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; +import com.tyron.fileeditor.api.FileEditor; +import com.tyron.code.ui.main.MainFragment; +import com.tyron.code.ui.main.MainViewModel; + +public class CloseOtherEditorAction extends AnAction { + + public static final String ID = "editorTabCloseOthers"; + + @Override + public void update(@NonNull AnActionEvent event) { + MainViewModel mainViewModel = event.getData(MainFragment.MAIN_VIEW_MODEL_KEY); + FileEditor fileEditor = event.getData(CommonDataKeys.FILE_EDITOR_KEY); + + event.getPresentation().setVisible(false); + if (!ActionPlaces.EDITOR_TAB.equals(event.getPlace())) { + return; + } + + if (fileEditor == null) { + return; + } + + if (mainViewModel == null) { + return; + } + + event.getPresentation().setVisible(true); + event.getPresentation().setText(event.getDataContext().getString(R.string.menu_close_others)); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + MainViewModel mainViewModel = e.getData(MainFragment.MAIN_VIEW_MODEL_KEY); + FileEditor fileEditor = e.getData(CommonDataKeys.FILE_EDITOR_KEY); + mainViewModel.removeOthers(fileEditor.getFile()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/DiagnosticInfoAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/DiagnosticInfoAction.java new file mode 100644 index 00000000..f3d05b14 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/DiagnosticInfoAction.java @@ -0,0 +1,52 @@ +package com.tyron.code.ui.editor.action; + +import android.app.AlertDialog; + +import androidx.annotation.NonNull; + +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.Presentation; + +import javax.tools.Diagnostic; + +import java.util.Locale; + +/** + * An action to display information about the diagnostic in the current cursor + */ +public class DiagnosticInfoAction extends AnAction { + + public static final String ID = "editorDiagnosticInfoAction"; + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + if (!ActionPlaces.EDITOR.equals(event.getPlace())) { + return; + } + + Diagnostic data = event.getData(CommonDataKeys.DIAGNOSTIC); + if (data == null) { + return; + } + + presentation.setVisible(true); + presentation.setText(event.getDataContext().getString(com.tyron.completion.java.R.string.menu_diagnostic_info_title)); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Diagnostic diagnostic = e.getRequiredData(CommonDataKeys.DIAGNOSTIC); + + new AlertDialog.Builder(e.getDataContext()) + .setTitle(com.tyron.completion.java.R.string.menu_diagnostic_info_title) + .setMessage(diagnostic.getMessage(Locale.getDefault())) + .setPositiveButton(android.R.string.ok, null) + .show(); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/ExpandSelectionAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/ExpandSelectionAction.java new file mode 100644 index 00000000..1f2048e0 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/ExpandSelectionAction.java @@ -0,0 +1,89 @@ +package com.tyron.code.ui.editor.action; + +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; + +import com.google.common.collect.Range; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.Presentation; +import com.tyron.builder.model.SourceFileObject; +import com.tyron.builder.project.Project; +import com.tyron.code.R; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.completion.java.action.FindCurrentPath; +import com.tyron.completion.java.compiler.Parser; +import com.tyron.editor.CharPosition; +import com.tyron.editor.Editor; +import com.tyron.editor.selection.ExpandSelectionProvider; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.SourcePositions; +import com.sun.source.util.TreePath; +import com.sun.source.util.Trees; + +import java.io.File; +import java.time.Instant; + +public class ExpandSelectionAction extends AnAction { + + public static final String ID = "expandSelection"; + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + if (!ActionPlaces.EDITOR.equals(event.getPlace())) { + return; + } + + File file = event.getData(CommonDataKeys.FILE); + if (file == null) { + return; + } + + Editor editor = event.getData(CommonDataKeys.EDITOR); + if (editor == null) { + return; + } + + ExpandSelectionProvider provider = ExpandSelectionProvider.forEditor(editor); + if (provider == null) { + return; + } + + if (editor.getProject() == null) { + return; + } + + DataContext context = event.getDataContext(); + presentation.setVisible(true); + presentation.setText(context.getString(R.string.expand_selection)); + presentation.setIcon(ResourcesCompat.getDrawable(context.getResources(), + R.drawable.ic_baseline_code_24, null)); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Editor editor = e.getRequiredData(CommonDataKeys.EDITOR); + ExpandSelectionProvider provider = ExpandSelectionProvider.forEditor(editor); + if (provider == null) { + AndroidUtilities.showSimpleAlert(e.getDataContext(), "No provider", + "No expand selection provider found."); + return; + } + Range range = provider.expandSelection(editor); + if (range == null) { + AndroidUtilities.showSimpleAlert(e.getDataContext(), + "Error", + "Cannot expand selection"); + return; + } + editor.setSelectionRegion(range.lowerEndpoint(), range.upperEndpoint()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/PreviewLayoutAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/PreviewLayoutAction.java new file mode 100644 index 00000000..e8e42f8f --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/PreviewLayoutAction.java @@ -0,0 +1,60 @@ +package com.tyron.code.ui.editor.action; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; + +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.Presentation; +import com.tyron.code.R; +import com.tyron.fileeditor.api.FileEditor; +import com.tyron.code.ui.editor.impl.xml.LayoutEditor; +import com.tyron.code.ui.editor.impl.xml.LayoutTextEditorFragment; +import com.tyron.common.util.AndroidUtilities; + +public class PreviewLayoutAction extends AnAction { + + public static final String ID = "previewLayoutAction"; + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + if (!ActionPlaces.MAIN_TOOLBAR.equals(event.getPlace())) { + return; + } + + FileEditor fileEditor = event.getData(CommonDataKeys.FILE_EDITOR_KEY); + if (!(fileEditor instanceof LayoutEditor)) { + return; + } + + presentation.setVisible(true); + presentation.setText(event.getDataContext().getString(R.string.menu_preview_layout)); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + FileEditor fileEditor = e.getRequiredData(CommonDataKeys.FILE_EDITOR_KEY); + Fragment fragment = fileEditor.getFragment(); + if (fragment == null || fragment.isDetached() || fragment.getActivity() == null) { + return; + } + FragmentActivity activity = fragment.requireActivity(); + View currentFocus = activity.getCurrentFocus(); + if (currentFocus == null) { + currentFocus = new View(activity); + } + AndroidUtilities.hideKeyboard(currentFocus); + + if (fragment instanceof LayoutTextEditorFragment) { + ((LayoutTextEditorFragment) fragment).preview(); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/CopyAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/CopyAction.java new file mode 100644 index 00000000..0faef193 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/CopyAction.java @@ -0,0 +1,55 @@ +package com.tyron.code.ui.editor.action.text; + +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; + +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.Presentation; +import com.tyron.code.R; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.editor.Caret; +import com.tyron.editor.Editor; + +public class CopyAction extends AnAction { + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + if (!ActionPlaces.EDITOR.equals(event.getPlace())) { + return; + } + + Editor editor = event.getData(CommonDataKeys.EDITOR); + if (editor == null) { + return; + } + + Caret caret = editor.getCaret(); + if (caret.getStartLine() == caret.getEndLine() && + caret.getStartColumn() == caret.getEndColumn()) { + return; + } + + DataContext context = event.getDataContext(); + presentation.setVisible(true); + presentation.setText(context.getString(io.github.rosemoe.sora2.R.string.copy)); + presentation.setIcon(ResourcesCompat.getDrawable(context.getResources(), + io.github.rosemoe.sora2.R.drawable.round_content_copy_20, context.getTheme())); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Editor editor = e.getRequiredData(CommonDataKeys.EDITOR); + Caret caret = editor.getCaret(); + CharSequence textToCopy = editor.getContent().subSequence(caret.getStart(), + caret.getEnd()); + AndroidUtilities.copyToClipboard(textToCopy.toString(), false); + AndroidUtilities.showToast(R.string.copied_to_clipoard); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/CutAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/CutAction.java new file mode 100644 index 00000000..fef5eaf4 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/CutAction.java @@ -0,0 +1,36 @@ +package com.tyron.code.ui.editor.action.text; + +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; + +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.editor.Caret; +import com.tyron.editor.Editor; + +public class CutAction extends CopyAction { + + @Override + public void update(@NonNull AnActionEvent event) { + super.update(event); + + if (event.getPresentation().isVisible()) { + DataContext context = event.getDataContext(); + event.getPresentation().setText(context.getString(io.github.rosemoe.sora2.R.string.cut)); + event.getPresentation().setIcon(ResourcesCompat.getDrawable(context.getResources(), + io.github.rosemoe.sora2.R.drawable.round_content_cut_20, context.getTheme())); + } + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Editor editor = e.getRequiredData(CommonDataKeys.EDITOR); + Caret caret = editor.getCaret(); + int startIndex = caret.getStart(); + int endIndex = caret.getEnd(); + super.actionPerformed(e); + + editor.delete(startIndex, endIndex); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/PasteAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/PasteAction.java new file mode 100644 index 00000000..349c185e --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/PasteAction.java @@ -0,0 +1,78 @@ +package com.tyron.code.ui.editor.action.text; + +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; + +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.Presentation; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.editor.Caret; +import com.tyron.editor.Editor; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import io.github.rosemoe.sora.text.TextUtils; +import io.github.rosemoe.sora2.text.EditorUtil; + +public class PasteAction extends CopyAction { + + @Override + public void update(@NonNull AnActionEvent event) { + super.update(event); + + Presentation presentation = event.getPresentation(); + if (!presentation.isVisible() && AndroidUtilities.getPrimaryClip() != null) { + presentation.setVisible(true); + } + + if (presentation.isVisible()) { + DataContext context = event.getDataContext(); + presentation.setText(context.getString(io.github.rosemoe.sora2.R.string.paste)); + presentation.setIcon(ResourcesCompat.getDrawable(context.getResources(), + io.github.rosemoe.sora2.R.drawable.round_content_paste_20, context.getTheme())); + } + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Editor editor = e.getRequiredData(CommonDataKeys.EDITOR); + Caret caret = editor.getCaret(); + + if (caret.isSelected()) { + editor.delete(caret.getStart(), caret.getEnd()); + } + + String clip = String.valueOf(AndroidUtilities.getPrimaryClip()); + String[] lines = clip.split("\n"); + if (lines.length == 0) { + lines = new String[]{clip}; + } + + int count = TextUtils.countLeadingSpaceCount(lines[0], editor.getTabCount()); + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + if (count < line.length()) { + String whitespace = line.substring(0, count); + if (EditorUtil.isWhitespace(whitespace)) { + line = line.substring(count); + } else { + line = line.trim(); + } + } else { + line = line.trim(); + } + lines[i] = line; + } + + String textToCopy = String.join("\n", lines); + editor.insertMultilineString( + caret.getStartLine(), + caret.getStartColumn(), + textToCopy + ); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/SelectAllAction.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/SelectAllAction.java new file mode 100644 index 00000000..4824d812 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/SelectAllAction.java @@ -0,0 +1,30 @@ +package com.tyron.code.ui.editor.action.text; + +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; + +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.editor.Editor; + +public class SelectAllAction extends CopyAction { + + @Override + public void update(@NonNull AnActionEvent event) { + super.update(event); + + if (event.getPresentation().isVisible()) { + DataContext context = event.getDataContext(); + event.getPresentation().setText(context.getString(io.github.rosemoe.sora2.R.string.selectAll)); + event.getPresentation().setIcon(ResourcesCompat.getDrawable(context.getResources(), + io.github.rosemoe.sora2.R.drawable.round_select_all_20, context.getTheme())); + } + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Editor editor = e.getRequiredData(CommonDataKeys.EDITOR); + editor.setSelectionRegion(0, editor.getContent().length()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/action/text/TextActionGroup.java b/app/src/main/java/com/tyron/code/ui/editor/action/text/TextActionGroup.java new file mode 100644 index 00000000..77d2ca77 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/action/text/TextActionGroup.java @@ -0,0 +1,46 @@ +package com.tyron.code.ui.editor.action.text; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.actions.ActionGroup; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.Presentation; +import com.tyron.code.R; +import com.tyron.code.ui.editor.action.ExpandSelectionAction; + +public class TextActionGroup extends ActionGroup { + + public static final String ID = "textActionGroup"; + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + if (!ActionPlaces.EDITOR.equals(event.getPlace())) { + return; + } + + presentation.setVisible(true); + presentation.setText(event.getDataContext().getString(R.string.text_actions)); + } + + @Override + public boolean isPopup() { + return true; + } + + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return new AnAction[] { + new ExpandSelectionAction(), + new SelectAllAction(), + new CutAction(), + new CopyAction(), + new PasteAction() + }; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/adapter/PageAdapter.java b/app/src/main/java/com/tyron/code/ui/editor/adapter/PageAdapter.java new file mode 100644 index 00000000..f20741a2 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/adapter/PageAdapter.java @@ -0,0 +1,79 @@ +package com.tyron.code.ui.editor.adapter; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.Lifecycle; +import androidx.recyclerview.widget.DiffUtil; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import com.tyron.fileeditor.api.FileEditor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class PageAdapter extends FragmentStateAdapter { + + private final List data = new ArrayList<>(); + + public PageAdapter(FragmentManager fm, Lifecycle lifecycle) { + super(fm, lifecycle); + } + + public void submitList(List files) { + DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return data.size(); + } + + @Override + public int getNewListSize() { + return files.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return Objects.equals(data.get(oldItemPosition), files.get(newItemPosition)); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + return Objects.equals(data.get(oldItemPosition), files.get(newItemPosition)); + } + }); + data.clear(); + data.addAll(files); + result.dispatchUpdatesTo(this); + } + + @Override + public int getItemCount() { + return data.size(); + } + + @Override + public long getItemId(int position) { + if (data.isEmpty()) { + return -1; + } + return data.get(position).hashCode(); + } + + @Override + public boolean containsItem(long itemId) { + for (FileEditor d : data) { + if (d.hashCode() == itemId) { + return true; + } + } + return false; + } + + @NonNull + @Override + public Fragment createFragment(int p1) { + return data.get(p1).getFragment(); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorManagerImpl.java b/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorManagerImpl.java new file mode 100644 index 00000000..16b87c7e --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorManagerImpl.java @@ -0,0 +1,113 @@ +package com.tyron.code.ui.editor.impl; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentManager; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.tyron.code.R; +import com.tyron.code.ui.main.MainViewModel; +import com.tyron.common.ApplicationProvider; +import com.tyron.fileeditor.api.FileEditor; +import com.tyron.fileeditor.api.FileEditorManager; +import com.tyron.fileeditor.api.FileEditorProvider; + +import java.io.File; +import java.util.Arrays; +import java.util.function.Consumer; + +public class FileEditorManagerImpl extends FileEditorManager { + + private static volatile FileEditorManager sInstance = null; + + public static synchronized FileEditorManager getInstance() { + if (sInstance == null) { + sInstance = new FileEditorManagerImpl(); + } + return sInstance; + } + + private MainViewModel mViewModel; + private FragmentManager mFragmentManager; + + FileEditorManagerImpl() { + + } + + public void attach(MainViewModel mainViewModel, FragmentManager fragmentManager) { + mViewModel = mainViewModel; + mFragmentManager = fragmentManager; + } + + @Override + public void openFile(@NonNull Context context, File file, Consumer callback) { + checkAttached(); + + FileEditor[] fileEditors = getFileEditors(file); + openChooser(context, fileEditors, callback); + } + + @NonNull + @Override + public FileEditor[] openFile(@NonNull Context context, @NonNull File file, boolean focus) { + checkAttached(); + + FileEditor[] editors = getFileEditors(file); + openChooser(context, editors, this::openFileEditor); + return editors; + } + + @Override + public FileEditor[] getFileEditors(@NonNull File file) { + FileEditor[] editors; + FileEditorProvider[] providers = FileEditorProviderManagerImpl.getInstance().getProviders(file); + editors = new FileEditor[providers.length]; + for (int i = 0; i < providers.length; i++) { + FileEditor editor = providers[i].createEditor(file); + editors[i] = editor; + } + return editors; + } + + @Override + public void openFileEditor(@NonNull FileEditor fileEditor) { + mViewModel.openFile(fileEditor); + } + + @Override + public void closeFile(@NonNull File file) { + mViewModel.removeFile(file); + } + + @Override + public void openChooser(Context context, FileEditor[] fileEditors, Consumer callback) { + if (fileEditors.length == 0) { + return; + } + if (fileEditors.length > 1) { + CharSequence[] items = Arrays.stream(fileEditors) + .map(FileEditor::getName) + .toArray(String[]::new); + new MaterialAlertDialogBuilder(context) + .setTitle(R.string.file_editor_selection_title) + .setItems(items, (__, which) -> + callback.accept(fileEditors[which])) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } else { + callback.accept(fileEditors[0]); + } + } + + public FragmentManager getFragmentManager() { + checkAttached(); + return this.mFragmentManager; + } + + private void checkAttached() { + if (mViewModel == null || mFragmentManager == null) { + throw new IllegalStateException("File editor manager is not yet attached to a ViewModel"); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorProviderManagerImpl.java b/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorProviderManagerImpl.java new file mode 100644 index 00000000..053af5e2 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/FileEditorProviderManagerImpl.java @@ -0,0 +1,72 @@ +package com.tyron.code.ui.editor.impl; + +import androidx.annotation.NonNull; + +import com.tyron.code.ui.editor.impl.image.ImageEditorProvider; +import com.tyron.code.ui.editor.impl.text.rosemoe.RosemoeEditorProvider; +import com.tyron.code.ui.editor.impl.xml.LayoutTextEditorProvider; +import com.tyron.fileeditor.api.FileEditorProvider; +import com.tyron.fileeditor.api.FileEditorProviderManager; + +import java.io.File; +import java.util.ArrayList; + +public class FileEditorProviderManagerImpl implements FileEditorProviderManager { + + private static FileEditorProviderManager sInstance = null; + + public static FileEditorProviderManager getInstance() { + if (sInstance == null) { + sInstance = new FileEditorProviderManagerImpl(); + } + return sInstance; + } + + private final ArrayList mProviders; + private final ArrayList mSharedProviderList; + + public FileEditorProviderManagerImpl() { + mProviders = new ArrayList<>(); + mSharedProviderList = new ArrayList<>(); + + registerBuiltInProviders(); + } + + private void registerBuiltInProviders() { + registerProvider(new RosemoeEditorProvider()); + registerProvider(new LayoutTextEditorProvider()); + registerProvider(new ImageEditorProvider()); + } + + @Override + public FileEditorProvider[] getProviders(@NonNull File file) { + mSharedProviderList.clear(); + for(int i = mProviders.size() - 1; i >= 0; i--){ + FileEditorProvider provider = mProviders.get(i); + if(provider.accept(file)){ + mSharedProviderList.add(provider); + } + } + return mSharedProviderList.toArray(new FileEditorProvider[0]); + } + + @Override + public FileEditorProvider getProvider(@NonNull String typeId) { + return null; + } + + public void registerProvider(FileEditorProvider provider) { + String editorTypeId = provider.getEditorTypeId(); + for (int i = mProviders.size() - 1; i >= 0; i--) { + FileEditorProvider _provider = mProviders.get(i); + if (editorTypeId.equals(_provider.getEditorTypeId())) { + throw new IllegalArgumentException("Attempt to register non unique editor id: " + editorTypeId); + } + } + mProviders.add(provider); + } + + private void unregisterProvider(FileEditorProvider provider) { + mProviders.remove(provider); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditor.java b/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditor.java new file mode 100644 index 00000000..bc45120b --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditor.java @@ -0,0 +1,72 @@ +package com.tyron.code.ui.editor.impl.image; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import com.tyron.fileeditor.api.FileEditor; + +import java.io.File; +import java.util.Objects; + +public class ImageEditor implements FileEditor { + + private final File mFile; + private final ImageEditorProvider mProvider; + private ImageEditorFragment mFragment; + + public ImageEditor(@NonNull File file, ImageEditorProvider provider) { + mFile = file; + mProvider = provider; + mFragment = createFragment(file); + } + + protected ImageEditorFragment createFragment(@NonNull File file) { + return ImageEditorFragment.newInstance(file); + } + + @Override + public Fragment getFragment() { + return mFragment; + } + + @Override + public View getPreferredFocusedView() { + return mFragment.getView(); + } + + @NonNull + @Override + public String getName() { + return "Image Editor"; + } + + @Override + public boolean isModified() { + return false; + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public File getFile() { + return mFile; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ImageEditor that = (ImageEditor) o; + return Objects.equals(mFile, that.mFile); + } + + @Override + public int hashCode() { + return Objects.hash(mFile); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditorFragment.java b/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditorFragment.java new file mode 100644 index 00000000..0a8d2529 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditorFragment.java @@ -0,0 +1,44 @@ +package com.tyron.code.ui.editor.impl.image; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.bumptech.glide.Glide; +import com.tyron.code.R; + +import java.io.File; + +public class ImageEditorFragment extends Fragment { + + public static ImageEditorFragment newInstance(File file) { + ImageEditorFragment fragment = new ImageEditorFragment(); + Bundle bundle = new Bundle(); + bundle.putString("file", file.getAbsolutePath()); + fragment.setArguments(bundle); + return fragment; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + ImageView imageView = new ImageView(requireContext()); + if (getArguments() != null) { + String file = requireArguments().getString("file", ""); + File imageFile = new File(file); + if (imageFile.exists()) { + Glide.with(imageView) + .load(imageFile) + .into(imageView); + } + } + return imageView; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditorProvider.java b/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditorProvider.java new file mode 100644 index 00000000..db00b3e3 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/image/ImageEditorProvider.java @@ -0,0 +1,44 @@ +package com.tyron.code.ui.editor.impl.image; + +import androidx.annotation.NonNull; + +import com.tyron.fileeditor.api.FileEditor; +import com.tyron.fileeditor.api.FileEditorProvider; + +import java.io.File; + +public class ImageEditorProvider implements FileEditorProvider { + + private static final String TYPE_ID = "image-editor"; + + @Override + public boolean accept(@NonNull File file) { + if (file.isDirectory()) { + return false; + } + String name = file.getName(); + if (!name.contains(".")) { + return false; + } + switch (name.substring(name.lastIndexOf('.') + 1)) { + case "png": + case "jpg": + case "jpeg": + case "bmp": + return true; + } + return false; + } + + @NonNull + @Override + public FileEditor createEditor(@NonNull File file) { + return new ImageEditor(file, this); + } + + @NonNull + @Override + public String getEditorTypeId() { + return TYPE_ID; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorFragment.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorFragment.java new file mode 100644 index 00000000..49475eef --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorFragment.java @@ -0,0 +1,802 @@ +package com.tyron.code.ui.editor.impl.text.rosemoe; + + +import static io.github.rosemoe.sora2.text.EditorUtil.getDefaultColorScheme; + +import android.annotation.SuppressLint; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.os.Bundle; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.ForwardingListener; +import androidx.core.content.ContextCompat; +import androidx.core.content.res.ResourcesCompat; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelStoreOwner; + +import com.android.tools.r8.graph.V; +import com.google.android.material.snackbar.Snackbar; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.tyron.actions.ActionManager; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.util.DataContextUtils; +import com.tyron.builder.log.LogViewModel; +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.FileManager; +import com.tyron.builder.project.api.Module; +import com.tyron.builder.project.listener.FileListener; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.R; +import com.tyron.code.ui.editor.CodeAssistCompletionAdapter; +import com.tyron.code.ui.editor.CodeAssistCompletionLayout; +import com.tyron.code.ui.editor.EditorViewModel; +import com.tyron.code.ui.editor.Savable; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; +import com.tyron.code.language.LanguageManager; +import com.tyron.code.language.java.JavaLanguage; +import com.tyron.code.analyzer.BaseTextmateAnalyzer; +import com.tyron.code.language.textmate.EmptyTextMateLanguage; +import com.tyron.code.language.xml.LanguageXML; +import com.tyron.code.ui.editor.scheme.CompiledEditorScheme; +import com.tyron.code.ui.editor.shortcuts.ShortcutAction; +import com.tyron.code.ui.editor.shortcuts.ShortcutItem; +import com.tyron.code.ui.main.MainViewModel; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.code.ui.settings.EditorSettingsFragment; +import com.tyron.code.ui.theme.ThemeRepository; +import com.tyron.code.util.CoordinatePopupMenu; +import com.tyron.code.util.PopupMenuHelper; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.common.logging.IdeLog; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.completion.java.util.DiagnosticUtil; +import com.tyron.completion.java.util.JavaDataContextUtil; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.editor.CharPosition; +import com.tyron.kotlin_completion.CompletionEngine; + +import org.apache.commons.io.FileUtils; +import org.jetbrains.kotlin.backend.common.psi.PsiSourceManager; +import org.jetbrains.kotlin.com.intellij.openapi.components.ServiceManager; +import org.jetbrains.kotlin.com.intellij.openapi.editor.event.DocumentEvent; +import org.jetbrains.kotlin.com.intellij.openapi.editor.impl.event.DocumentEventImpl; +import org.jetbrains.kotlin.com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.kotlin.com.intellij.openapi.vfs.local.CoreLocalFileSystem; +import org.jetbrains.kotlin.com.intellij.psi.AbstractFileViewProvider; +import org.jetbrains.kotlin.com.intellij.psi.FileViewProvider; +import org.jetbrains.kotlin.com.intellij.psi.PsiDocumentManager; +import org.jetbrains.kotlin.com.intellij.psi.PsiManager; +import org.jetbrains.kotlin.com.intellij.util.DocumentEventUtil; +import org.jetbrains.kotlin.com.intellij.util.FileContentUtilCore; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.logging.Logger; + +import io.github.rosemoe.sora.event.ClickEvent; +import io.github.rosemoe.sora.event.ContentChangeEvent; +import io.github.rosemoe.sora.event.LongPressEvent; +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.langs.textmate.theme.TextMateColorScheme; +import io.github.rosemoe.sora.text.Content; +import io.github.rosemoe.sora.text.Cursor; +import io.github.rosemoe.sora.widget.CodeEditor; +import io.github.rosemoe.sora.widget.DirectAccessProps; +import io.github.rosemoe.sora.widget.component.EditorAutoCompletion; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; +import io.github.rosemoe.sora2.text.EditorUtil; + +@SuppressWarnings("FieldCanBeLocal") +public class CodeEditorFragment extends Fragment implements Savable, + SharedPreferences.OnSharedPreferenceChangeListener, FileListener, + ProjectManager.OnProjectOpenListener { + + private static final Logger LOG = IdeLog.getCurrentLogger(CodeEditorFragment.class); + + public static final String KEY_LINE = "line"; + public static final String KEY_COLUMN = "column"; + public static final String KEY_PATH = "path"; + + public static CodeEditorFragment newInstance(File file) { + CodeEditorFragment fragment = new CodeEditorFragment(); + Bundle args = new Bundle(); + args.putString(KEY_PATH, file.getAbsolutePath()); + fragment.setArguments(args); + return fragment; + } + + /** + * Creates a new instance of the editor with the the cursor positioned at the given + * line and column + * + * @param file The file to be r ead + * @param line The 0-based line + * @param column The 0-based column + * @return The editor instance + */ + public static CodeEditorFragment newInstance(File file, int line, int column) { + CodeEditorFragment fragment = new CodeEditorFragment(); + Bundle args = new Bundle(); + args.putInt(KEY_LINE, line); + args.putInt(KEY_COLUMN, column); + args.putString(KEY_PATH, file.getAbsolutePath()); + fragment.setArguments(args); + return fragment; + } + + /** + * Keys for saved states + */ + private static final String EDITOR_LEFT_LINE_KEY = "line"; + private static final String EDITOR_LEFT_COLUMN_KEY = "column"; + private static final String EDITOR_RIGHT_LINE_KEY = "rightLine"; + private static final String EDITOR_RIGHT_COLUMN_KEY = "rightColumn"; + + private CodeEditorView mEditor; + + private Language mLanguage; + private File mCurrentFile = new File(""); + private MainViewModel mMainViewModel; + + private Bundle mSavedInstanceState; + + private boolean mCanSave = false; + private boolean mReading = false; + + private View.OnTouchListener mDragToOpenListener; + + public CodeEditorFragment() { + super(R.layout.code_editor_fragment); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mCurrentFile = new File(requireArguments().getString(KEY_PATH, "")); + mMainViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class); + mSavedInstanceState = savedInstanceState; + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + + Cursor cursor = mEditor.getCursor(); + outState.putInt(EDITOR_LEFT_LINE_KEY, cursor.getLeftLine()); + outState.putInt(EDITOR_LEFT_COLUMN_KEY, cursor.getLeftColumn()); + outState.putInt(EDITOR_RIGHT_LINE_KEY, cursor.getRightLine()); + outState.putInt(EDITOR_RIGHT_COLUMN_KEY, cursor.getRightColumn()); + } + + @Override + public void onResume() { + super.onResume(); + } + + public void hideEditorWindows() { + mEditor.hideAutoCompleteWindow(); + } + + /** + * Return the {@link EditorColorScheme} from the specified path. + * If the path is null or does not exist, the default color scheme is returned + * depending on the state of the device's theme + * + * @param path The file path to color scheme json file + * @return The color scheme instance + */ + @NonNull + private ListenableFuture getScheme(@Nullable String path) { + if (path != null && new File(path).exists()) { + return EditorSettingsFragment.getColorScheme(new File(path)); + } else { + return Futures.immediateFailedFuture(new Throwable()); + } + } + + @SuppressLint("RestrictedApi") + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mCanSave = false; + + mEditor = view.findViewById(R.id.code_editor); + mEditor.setEditable(false); + configureEditor(mEditor); + + View topView = view.findViewById(R.id.top_view); + EditorViewModel viewModel = + new ViewModelProvider((ViewModelStoreOwner) this).get(EditorViewModel.class); + viewModel.getAnalyzeState().observe(getViewLifecycleOwner(), analyzing -> { + if (analyzing) { + topView.setVisibility(View.VISIBLE); + } else { + topView.setVisibility(View.GONE); + } + }); + mEditor.setViewModel(viewModel); + ApplicationLoader.getDefaultPreferences().registerOnSharedPreferenceChangeListener(this); + + postConfigureEditor(); + + String schemeValue = ApplicationLoader.getDefaultPreferences() + .getString(SharedPreferenceKeys.SCHEME, null); + if (schemeValue != null && + new File(schemeValue).exists() && + ThemeRepository.getColorScheme(schemeValue) != null) { + TextMateColorScheme scheme = ThemeRepository.getColorScheme(schemeValue); + if (scheme != null) { + mEditor.setColorScheme(scheme); + initializeLanguage(); + mEditor.openFile(mCurrentFile); + readOrWait(); + } + } else { + ListenableFuture scheme = getScheme(schemeValue); + Futures.addCallback(scheme, new FutureCallback() { + @Override + public void onSuccess(@Nullable TextMateColorScheme result) { + if (getContext() == null) { + return; + } + assert result != null; + ThemeRepository.putColorScheme(schemeValue, result); + mEditor.setColorScheme(result); + + initializeLanguage(); + mEditor.openFile(mCurrentFile); + readOrWait(); + } + + @Override + public void onFailure(@NonNull Throwable t) { + if (getContext() == null) { + return; + } + String key = EditorUtil.isDarkMode( + requireContext()) ? ThemeRepository.DEFAULT_NIGHT : + ThemeRepository.DEFAULT_LIGHT; + TextMateColorScheme scheme = ThemeRepository.getColorScheme(key); + if (scheme == null) { + scheme = getDefaultColorScheme(requireContext()); + ThemeRepository.putColorScheme(key, scheme); + } + mEditor.setColorScheme(scheme); + initializeLanguage(); + mEditor.openFile(mCurrentFile); + readOrWait(); + } + }, ContextCompat.getMainExecutor(requireContext())); + } + } + + private void initializeLanguage() { + mLanguage = LanguageManager.getInstance().get(mEditor, mCurrentFile); + if (mLanguage == null) { + mLanguage = new EmptyTextMateLanguage(); + } + mEditor.setEditorLanguage(mLanguage); + } + + private void configureEditor(@NonNull CodeEditorView editor) { + // do not allow the user to edit, since at the time this is called + // the contents may still be loading. + editor.setEditable(false); + editor.setColorScheme(new CompiledEditorScheme(requireContext())); + editor.setBackgroundAnalysisEnabled(false); + editor.setTypefaceText( + ResourcesCompat.getFont(requireContext(), R.font.jetbrains_mono_regular)); + editor.getComponent(EditorAutoCompletion.class).setLayout(new CodeAssistCompletionLayout()); + editor.setLigatureEnabled(true); + editor.setHighlightCurrentBlock(true); + editor.setEdgeEffectColor(Color.TRANSPARENT); + editor.openFile(mCurrentFile); + editor.setAutoCompletionItemAdapter(new CodeAssistCompletionAdapter()); + editor.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO); + editor.setInputType(EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS | + EditorInfo.TYPE_CLASS_TEXT | + EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | + EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + + SharedPreferences pref = ApplicationLoader.getDefaultPreferences(); + editor.setWordwrap(pref.getBoolean(SharedPreferenceKeys.EDITOR_WORDWRAP, false)); + editor.setTextSize(Integer.parseInt(pref.getString(SharedPreferenceKeys.FONT_SIZE, "12"))); + + DirectAccessProps props = editor.getProps(); + props.overScrollEnabled = false; + props.allowFullscreen = false; + props.deleteEmptyLineFast = pref.getBoolean(SharedPreferenceKeys.DELETE_WHITESPACES, false); + } + + private void postConfigureEditor() { + // noinspection ClickableViewAccessibility + mEditor.setOnTouchListener((view12, motionEvent) -> { + if (mDragToOpenListener instanceof ForwardingListener) { + PopupMenuHelper.setForwarding((ForwardingListener) mDragToOpenListener); + // noinspection RestrictedApi + mDragToOpenListener.onTouch(view12, motionEvent); + } + return false; + }); + mEditor.subscribeEvent(LongPressEvent.class, (event, unsubscribe) -> { + event.intercept(); + + updateFile(mEditor.getText()); + Cursor cursor = mEditor.getCursor(); + if (cursor.isSelected()) { + int index = mEditor.getCharIndex(event.getLine(), event.getColumn()); + int cursorLeft = cursor.getLeft(); + int cursorRight = cursor.getRight(); + char c = mEditor.getText().charAt(index); + if (Character.isWhitespace(c)) { + mEditor.setSelection(event.getLine(), event.getColumn()); + } else if (index < cursorLeft || index > cursorRight) { + EditorUtil.selectWord(mEditor, event.getLine(), event.getColumn()); + } + } else { + char c = mEditor.getText().charAt(event.getIndex()); + if (!Character.isWhitespace(c)) { + EditorUtil.selectWord(mEditor, event.getLine(), event.getColumn()); + } else { + mEditor.setSelection(event.getLine(), event.getColumn()); + } + } + + ProgressManager.getInstance().runLater(() -> { + showPopupMenu(event); + }); + }); + mEditor.subscribeEvent(ClickEvent.class, (event, unsubscribe) -> { + Cursor cursor = mEditor.getCursor(); + if (mEditor.getCursor().isSelected()) { + int index = mEditor.getCharIndex(event.getLine(), event.getColumn()); + int cursorLeft = cursor.getLeft(); + int cursorRight = cursor.getRight(); + if (!EditorUtil.isWhitespace(mEditor.getText().charAt(index) + "") && + index >= cursorLeft && + index <= cursorRight) { + mEditor.showSoftInput(); + event.intercept(); + } + } + }); + mEditor.subscribeEvent(ContentChangeEvent.class, (event, unsubscribe) -> { + if (event.getAction() == ContentChangeEvent.ACTION_SET_NEW_TEXT) { + return; + } + updateFile(event.getEditor().getText()); + }); + + LogViewModel logViewModel = + new ViewModelProvider(requireActivity()).get(LogViewModel.class); + mEditor.setDiagnosticsListener(diagnostics -> { + for (DiagnosticWrapper diagnostic : diagnostics) { + DiagnosticUtil.setLineAndColumn(diagnostic, mEditor); + } + ProgressManager.getInstance() + .runLater(() -> logViewModel.updateLogs(LogViewModel.DEBUG, diagnostics)); + }); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences pref, String key) { + if (mEditor == null) { + return; + } + switch (key) { + case SharedPreferenceKeys.FONT_SIZE: + mEditor.setTextSize(Integer.parseInt(pref.getString(key, "14"))); + break; + case SharedPreferenceKeys.EDITOR_WORDWRAP: + mEditor.setWordwrap(pref.getBoolean(key, false)); + break; + case SharedPreferenceKeys.DELETE_WHITESPACES: + mEditor.getProps().deleteEmptyLineFast = + pref.getBoolean(SharedPreferenceKeys.DELETE_WHITESPACES, false); + break; + case SharedPreferenceKeys.SCHEME: + ListenableFuture scheme = + getScheme(pref.getString(SharedPreferenceKeys.SCHEME, null)); + Futures.addCallback(scheme, new FutureCallback() { + @Override + public void onSuccess(@Nullable TextMateColorScheme result) { + if (getContext() == null) { + return; + } + assert result != null; + mEditor.setColorScheme(result); + if (mLanguage.getAnalyzeManager() instanceof BaseTextmateAnalyzer) { + ((BaseTextmateAnalyzer) mLanguage.getAnalyzeManager()) + .updateTheme(result.getRawTheme()); + mLanguage.getAnalyzeManager().rerun(); + } + } + + @Override + public void onFailure(@NonNull Throwable t) { + if (getContext() == null) { + return; + } + mEditor.setColorScheme(getDefaultColorScheme(requireContext())); + mLanguage.getAnalyzeManager().rerun(); + } + }, ContextCompat.getMainExecutor(requireContext())); + break; + } + } + + /** + * Show the popup menu with the actions api + */ + private void showPopupMenu(LongPressEvent event) { + MotionEvent e = event.getCausingEvent(); + CoordinatePopupMenu popupMenu = + new CoordinatePopupMenu(requireContext(), mEditor, Gravity.BOTTOM); + DataContext dataContext = createDataContext(); + ActionManager.getInstance() + .fillMenu(dataContext, popupMenu.getMenu(), ActionPlaces.EDITOR, true, false); + popupMenu.show((int) e.getX(), ((int) e.getY()) - AndroidUtilities.dp(24)); + + // we don't want to enable the drag to open listener right away, + // this may cause the buttons to be clicked right away + // so wait for a few ms + ProgressManager.getInstance().runLater(() -> { + popupMenu.setOnDismissListener(d -> mDragToOpenListener = null); + mDragToOpenListener = popupMenu.getDragToOpenListener(); + }, 300); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + if (currentProject != null) { + Module module = currentProject.getModule(mCurrentFile); + if (module != null) { + module.getFileManager().removeSnapshotListener(this); + } + } + ProjectManager.getInstance().removeOnProjectOpenListener(this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (ProjectManager.getInstance().getCurrentProject() != null && mCanSave) { + ProgressManager.getInstance().runNonCancelableAsync( + () -> ProjectManager.getInstance().getCurrentProject().getModule(mCurrentFile) + .getFileManager().closeFileForSnapshot(mCurrentFile)); + } + ApplicationLoader.getDefaultPreferences().unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + public void onPause() { + super.onPause(); + + hideEditorWindows(); + + save(true); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + + mEditor.setBackgroundAnalysisEnabled(false); + } + + @Override + public void onSnapshotChanged(File file, CharSequence contents) { + if (mCurrentFile.equals(file)) { + if (mEditor != null) { + if (!mEditor.getText().toString().contentEquals(contents)) { + Cursor cursor = mEditor.getCursor(); + int left = cursor.getLeft(); + mEditor.setText(contents); + + if (left > contents.length()) { + left = contents.length(); + } + CharPosition position = mEditor.getCharPosition(left); + mEditor.setSelection(position.getLine(), position.getColumn()); + } + } + } + } + + @Override + public boolean canSave() { + return mCanSave && !mReading; + } + + @Override + public void save(boolean toDisk) { + if (!mCanSave || mReading) { + return; + } + + // don't save if the file has been deleted externally but its still opened in the editor, + if (!mCurrentFile.exists()) { + return; + } + + if (ProjectManager.getInstance().getCurrentProject() != null && !toDisk) { + ProjectManager.getInstance().getCurrentProject().getModule(mCurrentFile) + .getFileManager() + .setSnapshotContent(mCurrentFile, mEditor.getText().toString(), false); + } else { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + try { + FileUtils.writeStringToFile(mCurrentFile, mEditor.getText().toString(), + StandardCharsets.UTF_8); + } catch (IOException e) { + LOG.severe("Unable to save file: " + mCurrentFile.getAbsolutePath() + "\n" + + "Reason: " + e.getMessage()); + } + }); + } + } + + @Override + public void onProjectOpen(Project project) { + ProgressManager.getInstance().runLater(() -> readFile(project, mSavedInstanceState)); + } + + /** + * Read the file immediately if there is a project open. If not, wait for the project + * to be opened first. + */ + private void readOrWait() { + if (ProjectManager.getInstance().getCurrentProject() != null) { + readFile(ProjectManager.getInstance().getCurrentProject(), mSavedInstanceState); + } else { + ProjectManager.getInstance().addOnProjectOpenListener(this); + } + } + + private ListenableFuture readFile() { + return Futures.submitAsync(() -> { + String contents = FileUtils.readFileToString(mCurrentFile, StandardCharsets.UTF_8); + return Futures.immediateFuture(contents); + }, Executors.newSingleThreadExecutor()); + } + + private void readFile(@NonNull Project currentProject, @Nullable Bundle savedInstanceState) { + mCanSave = false; + Module module = currentProject.getModule(mCurrentFile); + FileManager fileManager = module.getFileManager(); + fileManager.addSnapshotListener(this); + + // the file is already opened, so no need to load it. + if (fileManager.isOpened(mCurrentFile)) { + Optional contents = fileManager.getFileContent(mCurrentFile); + if (contents.isPresent()) { + mEditor.setText(contents.get()); + return; + } + } + + mReading = true; + mEditor.setBackgroundAnalysisEnabled(false); + ListenableFuture future = readFile(); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(@Nullable String result) { + mReading = false; + if (getContext() == null) { + mCanSave = false; + return; + } + if (mLanguage == null) { + return; + } + mCanSave = true; + mEditor.setBackgroundAnalysisEnabled(true); + mEditor.setEditable(true); + fileManager.openFileForSnapshot(mCurrentFile, result); + + Bundle bundle = new Bundle(); + bundle.putBoolean("loaded", true); + bundle.putBoolean("bg", true); + mEditor.setText(result, bundle); + + if (savedInstanceState != null) { + restoreState(savedInstanceState); + } else { + int line = requireArguments().getInt(KEY_LINE, 0); + int column = requireArguments().getInt(KEY_COLUMN, 0); + Content text = mEditor.getText(); + if (line < text.getLineCount() && column < text.getColumnCount(line)) { + setCursorPosition(line, column); + } + } + checkCanSave(); + } + + @Override + public void onFailure(@NonNull Throwable t) { + mCanSave = false; + mReading = false; + if (getContext() != null) { + checkCanSave(); + } + + LOG.severe("Unable to read current file: " + mCurrentFile + "\n" + + "Reason: " + t.getMessage()); + } + }, ContextCompat.getMainExecutor(requireContext())); + } + + private void checkCanSave() { + if (!mCanSave) { + Snackbar snackbar = + Snackbar.make(mEditor, R.string.editor_error_file, Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.menu_close, v -> FileEditorManagerImpl.getInstance() + .closeFile(mCurrentFile)); + ViewGroup snackbarView = (ViewGroup) snackbar.getView(); + AndroidUtilities.setMargins(snackbarView, 0, 0, 0, 50); + snackbar.show(); + } + } + + private void restoreState(@NonNull Bundle savedInstanceState) { + int leftLine = savedInstanceState.getInt(EDITOR_LEFT_LINE_KEY, 0); + int leftColumn = savedInstanceState.getInt(EDITOR_LEFT_COLUMN_KEY, 0); + int rightLine = savedInstanceState.getInt(EDITOR_RIGHT_LINE_KEY, 0); + int rightColumn = savedInstanceState.getInt(EDITOR_RIGHT_COLUMN_KEY, 0); + + Content text = mEditor.getText(); + if (leftLine > text.getLineCount() || rightLine > text.getLineCount()) { + return; + } + if (leftLine != rightLine && leftColumn != rightColumn) { + mEditor.setSelectionRegion(leftLine, leftColumn, rightLine, rightColumn, true); + } else { + mEditor.setSelection(leftLine, leftColumn); + } + } + + private void updateFile(CharSequence contents) { + Project project = ProjectManager.getInstance().getCurrentProject(); + if (project == null) { + return; + } + Module module = project.getModule(mCurrentFile); + if (module != null) { + if (!module.getFileManager().isOpened(mCurrentFile)) { + return; + } + module.getFileManager().setSnapshotContent(mCurrentFile, contents.toString(), this); + } + } + + public CodeEditorView getEditor() { + return mEditor; + } + + /** + * Undo the text in the editor if possible, if not the call is ignored + */ + public void undo() { + if (mEditor == null) { + return; + } + if (mEditor.canUndo()) { + mEditor.undo(); + } + } + + /** + * Redo the text in the editor if possible, if not the call is ignored + */ + public void redo() { + if (mEditor == null) { + return; + } + if (mEditor.canRedo()) { + mEditor.redo(); + } + } + + /** + * Sets the position of the cursor in the editor + * + * @param line zero-based line. + * @param column zero-based column. + */ + public void setCursorPosition(int line, int column) { + if (mEditor != null) { + mEditor.getCursor().set(line, column); + } + } + + /** + * Perform a shortcut item to the editor + * + * @param item the item to be performed + */ + public void performShortcut(ShortcutItem item) { + if (mEditor == null) { + return; + } + for (ShortcutAction action : item.actions) { + action.apply(mEditor, item); + } + } + + public void format() { + if (mEditor != null) { +// if (mEditor.getCursor().isSelected()) { +// if (mLanguage instanceof JavaLanguage) { +// Cursor cursor = mEditor.getCursor(); +// CharSequence format = mLanguage.format(mEditor.getText(), cursor.getLeft(), +// cursor.getRight()); +// mEditor.setText(format); +// return; +// } +// } + mEditor.formatCodeAsync(); + } + } + + /** + * Notifies the editor to analyze and highlight the current text + */ + public void analyze() { + if (mEditor != null && !mReading) { + mEditor.rerunAnalysis(); + } + } + + /** + * Create the data context specific to this fragment for use with the actions API. + * @return the data context. + */ + private DataContext createDataContext() { + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + + DataContext dataContext = DataContextUtils.getDataContext(mEditor); + dataContext.putData(CommonDataKeys.PROJECT, currentProject); + dataContext.putData(CommonDataKeys.ACTIVITY, requireActivity()); + dataContext.putData(CommonDataKeys.FILE_EDITOR_KEY, mMainViewModel.getCurrentFileEditor()); + dataContext.putData(CommonDataKeys.FILE, mCurrentFile); + dataContext.putData(CommonDataKeys.EDITOR, mEditor); + + if (currentProject != null && mLanguage instanceof JavaLanguage) { + JavaDataContextUtil.addEditorKeys(dataContext, currentProject, mCurrentFile, + mEditor.getCursor().getLeft()); + } + + DiagnosticWrapper diagnosticWrapper = DiagnosticUtil + .getDiagnosticWrapper(mEditor.getDiagnostics(), + mEditor.getCursor().getLeft(), + mEditor.getCursor().getRight()); + if (diagnosticWrapper == null && mLanguage instanceof LanguageXML) { + diagnosticWrapper = DiagnosticUtil.getXmlDiagnosticWrapper(mEditor.getDiagnostics(), + mEditor.getCursor() + .getLeftLine()); + } + dataContext.putData(CommonDataKeys.DIAGNOSTIC, diagnosticWrapper); + return dataContext; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorView.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorView.java new file mode 100644 index 00000000..74e9bc7c --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CodeEditorView.java @@ -0,0 +1,485 @@ +package com.tyron.code.ui.editor.impl.text.rosemoe; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.common.collect.ImmutableSet; +import com.tyron.actions.DataContext; +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.builder.project.Project; +import com.tyron.code.language.HighlightUtil; +import com.tyron.code.ui.editor.CodeAssistCompletionAdapter; +import com.tyron.code.ui.editor.CodeAssistCompletionWindow; +import com.tyron.code.ui.editor.EditorViewModel; +import com.tyron.code.ui.editor.NoOpTextActionWindow; +import com.tyron.code.language.EditorFormatter; +import com.tyron.code.analyzer.DiagnosticTextmateAnalyzer; +import com.tyron.code.language.xml.LanguageXML; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.completion.xml.model.XmlCompletionType; +import com.tyron.xml.completion.util.DOMUtils; +import com.tyron.completion.xml.util.XmlUtils; +import com.tyron.editor.Caret; +import com.tyron.editor.CharPosition; +import com.tyron.editor.Content; +import com.tyron.editor.Editor; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMNode; +import org.eclipse.lemminx.dom.DOMParser; +import org.jetbrains.kotlin.com.intellij.util.ReflectionUtil; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +import io.github.rosemoe.sora.lang.Language; +import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; +import io.github.rosemoe.sora.lang.styling.Styles; +import io.github.rosemoe.sora.text.Cursor; +import io.github.rosemoe.sora.text.TextUtils; +import io.github.rosemoe.sora.widget.CodeEditor; +import io.github.rosemoe.sora.widget.SymbolPairMatch; +import io.github.rosemoe.sora.widget.component.EditorAutoCompletion; +import io.github.rosemoe.sora.widget.component.EditorTextActionWindow; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; +import io.github.rosemoe.sora2.text.EditorUtil; + +public class CodeEditorView extends CodeEditor implements Editor { + + private static final Field sFormatThreadField; + + static { + try { + sFormatThreadField = CodeEditor.class.getDeclaredField("mFormatThread"); + sFormatThreadField.setAccessible(true); + } catch (Throwable e) { + throw new Error(e); + } + } + + private final Set IGNORED_PAIR_ENDS = + ImmutableSet.builder().add(')').add(']').add('"').add('>').add('\'').add(';') + .build(); + + private boolean mIsBackgroundAnalysisEnabled; + + private List mDiagnostics; + private Consumer> mDiagnosticsListener; + private File mCurrentFile; + private EditorViewModel mViewModel; + + private final Paint mDiagnosticPaint; + private CodeAssistCompletionWindow mCompletionWindow; + + public CodeEditorView(Context context) { + this(DataContext.wrap(context), null); + } + + public CodeEditorView(Context context, AttributeSet attrs) { + this(DataContext.wrap(context), attrs, 0); + } + + public CodeEditorView(Context context, AttributeSet attrs, int defStyleAttr) { + this(DataContext.wrap(context), attrs, defStyleAttr, 0); + } + + public CodeEditorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(DataContext.wrap(context), attrs, defStyleAttr, defStyleRes); + + mDiagnosticPaint = new Paint(); + mDiagnosticPaint.setStrokeWidth(getDpUnit() * 2); + + init(); + } + + @Nullable + @Override + public Project getProject() { + return ProjectManager.getInstance().getCurrentProject(); + } + + @Override + public void setEditorLanguage(@Nullable Language lang) { + super.setEditorLanguage(lang); + + if (lang != null) { + // languages should have an option to declare their own tab width + try { + Class aClass = lang.getClass(); + Method method = ReflectionUtil.getDeclaredMethod(aClass, "getTabWidth"); + if (method != null) { + Object invoke = method.invoke(getEditorLanguage()); + if (invoke instanceof Integer) { + setTabWidth((Integer) invoke); + } + } + } catch (Throwable e) { + // use default + } + } + } + + private void init() { + mCompletionWindow = new CodeAssistCompletionWindow(this); + mCompletionWindow.setAdapter(new CodeAssistCompletionAdapter()); + replaceComponent(EditorAutoCompletion.class, mCompletionWindow); + replaceComponent(EditorTextActionWindow.class, new NoOpTextActionWindow(this)); + } + + @Override + public void setColorScheme(@NonNull EditorColorScheme colors) { + super.setColorScheme(colors); + } + + @Override + public List getDiagnostics() { + return mDiagnostics; + } + + @Override + public void setDiagnostics(List diagnostics) { + mDiagnostics = diagnostics; + + AnalyzeManager manager = getEditorLanguage().getAnalyzeManager(); + if (manager instanceof DiagnosticTextmateAnalyzer) { + ((DiagnosticTextmateAnalyzer) manager).setDiagnostics(this, diagnostics); + } + + if (mDiagnosticsListener != null) { + mDiagnosticsListener.accept(mDiagnostics); + } + + Styles styles = getStyles(); + if (styles != null) { + HighlightUtil.clearDiagnostics(styles); + HighlightUtil.markDiagnostics(this, diagnostics, styles); + setStyles(manager, styles); + } + } + + public void setDiagnosticsListener(Consumer> listener) { + mDiagnosticsListener = listener; + } + + @Override + public File getCurrentFile() { + return mCurrentFile; + } + + @Override + public void openFile(File file) { + mCurrentFile = file; + } + + @Override + public CharPosition getCharPosition(int index) { + io.github.rosemoe.sora.text.CharPosition charPosition = + getText().getIndexer().getCharPosition(index); + return new CharPosition(charPosition.line, charPosition.column); + } + + @Override + public int getCharIndex(int line, int column) { + return getText().getCharIndex(line, column); + } + + @Override + public boolean useTab() { + //noinspection ConstantConditions, editor language can be null + if (getEditorLanguage() == null) { + // enabled by default + return true; + } + + return getEditorLanguage().useTab(); + } + + @Override + public int getTabCount() { + return getTabWidth(); + } + + @Override + public void insert(int line, int column, String string) { + getText().insert(line, column, string); + } + + @Override + public void commitText(CharSequence text) { + super.commitText(text); + } + + @Override + public void commitText(CharSequence text, boolean applyAutoIndent) { + if (text.length() == 1) { + char currentChar = getText().charAt(getCursor().getLeft()); + char c = text.charAt(0); + if (IGNORED_PAIR_ENDS.contains(c) && c == currentChar) { + // ignored pair end, just move the cursor over the character + setSelection(getCursor().getLeftLine(), getCursor().getLeftColumn() + 1); + return; + } + } + super.commitText(text, applyAutoIndent); + + if (text.length() == 1) { + char c = text.charAt(0); + handleAutoInsert(c); + } + } + + private void handleAutoInsert(char c) { + if (getEditorLanguage() instanceof LanguageXML) { + if (c != '>' && c != '/') { + return; + } + boolean full = c == '>'; + + DOMDocument document = DOMParser.getInstance().parse(getText().toString(), "", null); + DOMNode nodeAt = document.findNodeAt(getCursor().getLeft()); + if (!DOMUtils.isClosed(nodeAt) && nodeAt.getNodeName() != null) { + if (XmlUtils.getCompletionType(document, getCursor().getLeft()) == + XmlCompletionType.ATTRIBUTE_VALUE) { + return; + } + String insertText = full ? "" : ">"; + commitText(insertText); + setSelection(getCursor().getLeftLine(), + getCursor().getLeftColumn() - (full ? insertText.length() : 0)); + } + } + } + + @Override + public void deleteText() { + Cursor cursor = getCursor(); + if (!cursor.isSelected()) { + io.github.rosemoe.sora.text.Content text = getText(); + int startIndex = cursor.getLeft(); + if (startIndex - 1 >= 0) { + char deleteChar = text.charAt(startIndex - 1); + char afterChar = text.charAt(startIndex); + SymbolPairMatch.Replacement replacement = null; + + SymbolPairMatch pairs = getEditorLanguage().getSymbolPairs(); + if (pairs != null) { + replacement = pairs.getCompletion(deleteChar); + } + if (replacement != null) { + if (("" + deleteChar + afterChar + "").equals(replacement.text)) { + text.delete(startIndex - 1, startIndex + 1); + return; + } + } + } + } + super.deleteText(); + } + + @Override + public void insertMultilineString(int line, int column, String string) { + String currentLine = getText().getLineString(line); + + String[] lines = string.split("\\n"); + if (lines.length == 0) { + return; + } + int count = TextUtils.countLeadingSpaceCount(currentLine, getTabWidth()); + for (int i = 0; i < lines.length; i++) { + String trimmed = lines[i].trim(); + + int advance = EditorUtil.getFormatIndent(getEditorLanguage(), trimmed); + + if (advance < 0) { + count += advance; + } + + if (i != 0) { + String indent = TextUtils.createIndent(count, getTabWidth(), useTab()); + trimmed = indent + trimmed; + } + + lines[i] = trimmed; + + if (advance > 0) { + count += advance; + } + } + + String textToInsert = String.join("\n", lines); + getText().insert(line, column, textToInsert); + } + + @Override + public void delete(int startLine, int startColumn, int endLine, int endColumn) { + getText().delete(startLine, startColumn, endLine, endColumn); + } + + @Override + public void delete(int startIndex, int endIndex) { + getText().delete(startIndex, endIndex); + } + + @Override + public void replace(int line, int column, int endLine, int endColumn, String string) { + getText().replace(line, column, endLine, endColumn, string); + } + + @Override + public void setSelection(int line, int column) { + super.setSelection(line, column); + } + + @Override + public void setSelectionRegion(int lineLeft, int columnLeft, int lineRight, int columnRight) { + CodeEditorView.super.setSelectionRegion(lineLeft, columnLeft, lineRight, columnRight); + } + + @Override + public void setSelectionRegion(int startIndex, int endIndex) { + CharPosition start = getCharPosition(startIndex); + CharPosition end = getCharPosition(endIndex); + CodeEditorView.super.setSelectionRegion(start.getLine(), start.getColumn(), end.getLine(), + end.getColumn()); + } + + @Override + public void beginBatchEdit() { + getText().beginBatchEdit(); + } + + @Override + public void endBatchEdit() { + getText().endBatchEdit(); + } + + public boolean isFormatting() { + try { + return sFormatThreadField.get(this) != null; + } catch (IllegalAccessException e) { + return false; + } + } + + @Override + public synchronized boolean formatCodeAsync() { + return CodeEditorView.super.formatCodeAsync(); + } + + + @Override + public synchronized boolean formatCodeAsync(int start, int end) { + if (isFormatting()) { + return false; + } + if (getEditorLanguage() instanceof EditorFormatter) { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + CharSequence originalText = getText(); + final CharSequence formatted = + ((EditorFormatter) getEditorLanguage()).format(originalText, start, end); + super.onFormatSucceed(originalText, formatted); + }); + return true; + } + return false; + } + + @Override + public Caret getCaret() { + return new CursorWrapper(getCursor()); + } + + @Override + public Content getContent() { + return new ContentWrapper(CodeEditorView.this.getText()); + } + + /** + * Background analysis can sometimes be expensive. + * Set whether background analysis should be enabled for this editor. + */ + public void setBackgroundAnalysisEnabled(boolean enabled) { + mIsBackgroundAnalysisEnabled = enabled; + } + + @Override + public void rerunAnalysis() { + //noinspection ConstantConditions + if (getEditorLanguage() != null) { + AnalyzeManager analyzeManager = getEditorLanguage().getAnalyzeManager(); + Project project = ProjectManager.getInstance().getCurrentProject(); + + if (analyzeManager instanceof DiagnosticTextmateAnalyzer) { + if (isBackgroundAnalysisEnabled() && (project != null && !project.isCompiling())) { + ((DiagnosticTextmateAnalyzer) analyzeManager).rerunWithBg(); + } else { + ((DiagnosticTextmateAnalyzer) analyzeManager).rerunWithoutBg(); + } + } else { + analyzeManager.rerun(); + } + } + } + + @Override + public boolean isBackgroundAnalysisEnabled() { + return mIsBackgroundAnalysisEnabled; + } + + public void setAnalyzing(boolean analyzing) { + if (mViewModel != null) { + mViewModel.setAnalyzeState(analyzing); + } + } + + @Override + public void requireCompletion() { + mCompletionWindow.requireCompletion(); + } + + public void setViewModel(EditorViewModel editorViewModel) { + mViewModel = editorViewModel; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + } + + private void drawSquigglyLine(Canvas canvas, + float startX, + float startY, + float endX, + float endY) { + float waveSize = getDpUnit() * 3; + float doubleWaveSize = waveSize * 2; + float width = endX - startX; + for (int i = (int) startX; i < startX + width; i += doubleWaveSize) { + canvas.drawLine(i, startY, i + waveSize, startY - waveSize, mDiagnosticPaint); + canvas.drawLine(i + waveSize, startY - waveSize, i + doubleWaveSize, startY, + mDiagnosticPaint); + } + } + + private void setDiagnosticColor(DiagnosticWrapper wrapper) { + EditorColorScheme color = getColorScheme(); + switch (wrapper.getKind()) { + case ERROR: + mDiagnosticPaint.setColor(color.getColor(EditorColorScheme.PROBLEM_ERROR)); + break; + case MANDATORY_WARNING: + case WARNING: + mDiagnosticPaint.setColor(color.getColor(EditorColorScheme.PROBLEM_WARNING)); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/ContentWrapper.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/ContentWrapper.java new file mode 100644 index 00000000..6df9d44e --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/ContentWrapper.java @@ -0,0 +1,121 @@ +package com.tyron.code.ui.editor.impl.text.rosemoe; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.editor.Content; + +import java.util.stream.IntStream; + +import io.github.rosemoe.sora.text.CharPosition; + +public class ContentWrapper implements Content { + + private final io.github.rosemoe.sora.text.Content mContent; + + public ContentWrapper(io.github.rosemoe.sora.text.Content content) { + mContent = content; + } + + @Override + public int length() { + return mContent.length(); + } + + @Override + public char charAt(int index) { + return mContent.charAt(index); + } + + @NonNull + @Override + public CharSequence subSequence(int start, int end) { + return mContent.subSequence(start, end); + } + + @NonNull + @Override + public IntStream chars() { + return mContent.chars(); + } + + @NonNull + @Override + public IntStream codePoints() { + return mContent.codePoints(); + } + + @Override + public int hashCode() { + return mContent.hashCode(); + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(@Nullable Object obj) { + return mContent.equals(obj); + } + + @NonNull + @Override + public String toString() { + return mContent.toString(); + } + + @Override + public boolean canRedo() { + return mContent.canRedo(); + } + + @Override + public void redo() { + mContent.redo(); + } + + @Override + public boolean canUndo() { + return mContent.canUndo(); + } + + @Override + public void undo() { + mContent.undo(); + } + + @Override + public int getLineCount() { + return mContent.getLineCount(); + } + + @Override + public String getLineString(int line) { + return mContent.getLineString(line); + } + + @Override + public void insert(int line, int column, CharSequence text) { + mContent.insert(line, column, text); + } + + @Override + public void insert(int index, CharSequence string) { + CharPosition startPos = mContent.getIndexer() + .getCharPosition(index); + insert(startPos.getLine(), startPos.getColumn(), string); + } + + @Override + public void delete(int start, int end) { + mContent.delete(start, end); + } + + @Override + public void replace(int start, int end, CharSequence text) { + CharPosition startPos = mContent.getIndexer() + .getCharPosition(start); + CharPosition endPos = mContent.getIndexer() + .getCharPosition(end); + mContent.replace(startPos.getLine(), startPos.getColumn(), + endPos.getLine(), endPos.getColumn(), text); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CursorWrapper.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CursorWrapper.java new file mode 100644 index 00000000..5ad87a57 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/CursorWrapper.java @@ -0,0 +1,50 @@ +package com.tyron.code.ui.editor.impl.text.rosemoe; + +import com.tyron.editor.Caret; + +import io.github.rosemoe.sora.text.Cursor; + +public class CursorWrapper implements Caret { + + private final Cursor mCursor; + + public CursorWrapper(Cursor cursor) { + mCursor = cursor; + } + + + @Override + public int getStart() { + return mCursor.getLeft(); + } + + @Override + public int getEnd() { + return mCursor.getRight(); + } + + @Override + public int getStartLine() { + return mCursor.getLeftLine(); + } + + @Override + public int getStartColumn() { + return mCursor.getLeftColumn(); + } + + @Override + public int getEndLine() { + return mCursor.getRightLine(); + } + + @Override + public int getEndColumn() { + return mCursor.getRightColumn(); + } + + @Override + public boolean isSelected() { + return mCursor.isSelected(); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeCodeEditor.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeCodeEditor.java new file mode 100644 index 00000000..f9e39a69 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeCodeEditor.java @@ -0,0 +1,97 @@ +package com.tyron.code.ui.editor.impl.text.rosemoe; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.fileeditor.api.FileEditorManager; +import com.tyron.fileeditor.api.TextEditor; + +import java.io.File; +import java.time.Instant; +import java.util.Objects; + +public class RosemoeCodeEditor implements TextEditor { + + private final File mFile; + private final RosemoeEditorProvider mProvider; + private final CodeEditorFragment mFragment; + + public RosemoeCodeEditor(File file, RosemoeEditorProvider provider) { + mFile = file; + mProvider = provider; + mFragment = createFragment(file); + } + + protected CodeEditorFragment createFragment(File file) { + return CodeEditorFragment.newInstance(file); + } + + @Override + public Fragment getFragment() { + if (mFragment == null || mFragment.getContext() == null || mFragment.isDetached()) { + FileEditorManagerImpl instance = (FileEditorManagerImpl) FileEditorManagerImpl.getInstance(); + Fragment fragment = + instance.getFragmentManager().findFragmentByTag("f" + hashCode()); + if (fragment != null) { + return fragment; + } + } + return mFragment; + } + + @Override + public View getPreferredFocusedView() { + return mFragment.getView(); + } + + @NonNull + @Override + public String getName() { + return "Rosemoe Code Editor"; + } + + @Override + public boolean isModified() { + Project project = ProjectManager.getInstance().getCurrentProject(); + if (project != null) { + Module module = project.getModule(mFile); + if (module != null) { + Instant diskModified = Instant.ofEpochMilli(mFile.lastModified()); + Instant lastModified = module.getFileManager().getLastModified(mFile); + if (lastModified != null) { + return lastModified.isAfter(diskModified); + } + } + } + return false; + } + + @Override + public boolean isValid() { + return mFile.exists(); + } + + @Override + public File getFile() { + return mFile; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RosemoeCodeEditor that = (RosemoeCodeEditor) o; + return Objects.equals(mFile, that.mFile); + } + + @Override + public int hashCode() { + return Objects.hash(mFile); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorProvider.java b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorProvider.java new file mode 100644 index 00000000..332a201c --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/text/rosemoe/RosemoeEditorProvider.java @@ -0,0 +1,44 @@ +package com.tyron.code.ui.editor.impl.text.rosemoe; + +import androidx.annotation.NonNull; + +import com.google.common.collect.ImmutableSet; +import com.tyron.fileeditor.api.FileEditor; +import com.tyron.fileeditor.api.FileEditorProvider; + +import java.io.File; +import java.util.Set; + +import kotlin.io.FilesKt; + +public class RosemoeEditorProvider implements FileEditorProvider { + + private static final Set NON_TEXT_FILES = ImmutableSet.builder() + .add("jar", "zip", "png", "jpg") + .add("jpeg", "mp4", "mp3", "ogg") + .add("7zip", "tar") + .build(); + private static final String TYPE_ID = "rosemoe-code-editor"; + + @Override + public boolean accept(@NonNull File file) { + boolean nonText = NON_TEXT_FILES.stream() + .anyMatch(it -> FilesKt.getExtension(file).endsWith(it)); + if (nonText) { + return false; + } + return file.exists() && !file.isDirectory(); + } + + @NonNull + @Override + public FileEditor createEditor(@NonNull File file) { + return new RosemoeCodeEditor(file, this); + } + + @NonNull + @Override + public String getEditorTypeId() { + return TYPE_ID; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutEditor.java b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutEditor.java new file mode 100644 index 00000000..504d5f5a --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutEditor.java @@ -0,0 +1,27 @@ +package com.tyron.code.ui.editor.impl.xml; + +import androidx.annotation.NonNull; + +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorFragment; +import com.tyron.code.ui.editor.impl.text.rosemoe.RosemoeCodeEditor; +import com.tyron.code.ui.editor.impl.text.rosemoe.RosemoeEditorProvider; + +import java.io.File; + +public class LayoutEditor extends RosemoeCodeEditor { + + public LayoutEditor(File file, RosemoeEditorProvider provider) { + super(file, provider); + } + + @Override + protected CodeEditorFragment createFragment(File file) { + return LayoutTextEditorFragment.newInstance(file); + } + + @NonNull + @Override + public String getName() { + return "Layout Editor"; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorFragment.java b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorFragment.java new file mode 100644 index 00000000..417e07c1 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorFragment.java @@ -0,0 +1,113 @@ +package com.tyron.code.ui.editor.impl.xml; + +import android.os.Bundle; +import android.util.Pair; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.os.BundleKt; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentResultListener; + +import com.tyron.builder.compiler.manifest.xml.XmlFormatPreferences; +import com.tyron.builder.compiler.manifest.xml.XmlFormatStyle; +import com.tyron.builder.compiler.manifest.xml.XmlPrettyPrinter; +import com.tyron.code.R; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorFragment; +import com.tyron.code.ui.layoutEditor.LayoutEditorFragment; +import com.tyron.code.util.ProjectUtils; + +import java.io.File; + +/** + * A {@link CodeEditorFragment} that supports editing layout files + */ +public class LayoutTextEditorFragment extends CodeEditorFragment { + + public static LayoutTextEditorFragment newInstance(File file) { + Bundle args = new Bundle(); + args.putString("path", file.getAbsolutePath()); + LayoutTextEditorFragment fragment = new LayoutTextEditorFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + FragmentManager fragmentManager = getChildFragmentManager(); + FragmentResultListener listener = ((requestKey, result) -> { + String xml = result.getString("text", getEditor().getText() + .toString()); + xml = XmlPrettyPrinter.prettyPrint(xml, XmlFormatPreferences.defaults(), + XmlFormatStyle.LAYOUT, "\n"); + Bundle bundle = new Bundle(); + bundle.putBoolean("loaded", true); + bundle.putBoolean("bg", true); + getEditor().setText(xml, bundle); + }); + fragmentManager.setFragmentResultListener(LayoutEditorFragment.KEY_SAVE, + getViewLifecycleOwner(), listener); + } + + public void preview() { + + File currentFile = getEditor().getCurrentFile(); + if (ProjectUtils.isLayoutXMLFile(currentFile)) { + getChildFragmentManager().beginTransaction().add(R.id.layout_editor_container, + LayoutEditorFragment.newInstance(currentFile)).addToBackStack(null).commit(); + } else { + // TODO: handle unknown files +// JavaCompilerProvider service = +// CompilerService.getInstance().getIndex(JavaCompilerProvider.KEY); +// JavaCompilerService compiler = service.getCompiler(ProjectManager.getInstance().getCurrentProject(), +// (JavaModule) ProjectManager.getInstance().getCurrentProject().getMainModule()); +// ParseTask parse = compiler.parse(mCurrentFile.toPath(), mEditor.getText().toString()); +// CompilationUnitConverter compilationUnitConverter = new CompilationUnitConverter(parse, mEditor.getText().toString(), new CompilationUnitConverter.LineColumnCallback() { +// @Override +// public int getLine(int pos) { +// return mEditor.getText().getIndexer().getCharLine(pos); +// } +// +// @Override +// public int getColumn(int pos) { +// return mEditor.getText().getIndexer().getCharColumn(pos); +// } +// }); +// CompilationUnit compilationUnit = compilationUnitConverter.startScan(); +// compilationUnit.register(new AstObserverAdapter() { +// @Override +// public void listChange(NodeList observedNode, ListChangeType type, int index, +// Node node) { +// if (type == ListChangeType.ADDITION) { +// Optional optionalRange = node.getRange(); +// com.github.javaparser.Range range = optionalRange.get(); +// mEditor.getText().insert(range.begin.line, range.begin.column - 1, node.toString()); +// } +// System.out.println(observedNode + ", type: " + type + ", added: " + node.getRange()); +// } +// +// @Override +// public void propertyChange(Node observedNode, ObservableProperty property, +// Object oldValue, Object newValue) { +// Optional optionalRange = observedNode.getRange(); +// if (oldValue instanceof NodeWithRange) { +// optionalRange = ((NodeWithRange) oldValue).getRange(); +// } else { +// optionalRange = observedNode.getRange(); +// } +// if (!optionalRange.isPresent()) { +// return; +// } +// com.github.javaparser.Range range = optionalRange.get(); +// mEditor.getText().replace(range.begin.line, range.begin.column, range.end.line, range.end.column, ""); +// mEditor.getText().insert(range.begin.line, range.begin.column, newValue.toString()); +// mEditor.hideAutoCompleteWindow(); +// } +// }, Node.ObserverRegistrationMode.THIS_NODE_AND_EXISTING_DESCENDANTS); +// System.out.println(compilationUnit); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorProvider.java b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorProvider.java new file mode 100644 index 00000000..4d30d026 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/impl/xml/LayoutTextEditorProvider.java @@ -0,0 +1,34 @@ +package com.tyron.code.ui.editor.impl.xml; + +import androidx.annotation.NonNull; + +import com.tyron.fileeditor.api.FileEditor; +import com.tyron.code.ui.editor.impl.text.rosemoe.RosemoeEditorProvider; +import com.tyron.code.util.ProjectUtils; + +import java.io.File; + +public class LayoutTextEditorProvider extends RosemoeEditorProvider { + + private static final String TYPE_ID = "layout-rosemoe-code-editor"; + + @Override + public boolean accept(@NonNull File file) { + if (file.isDirectory()) { + return false; + } + return ProjectUtils.isLayoutXMLFile(file); + } + + @NonNull + @Override + public FileEditor createEditor(@NonNull File file) { + return new LayoutEditor(file, this); + } + + @NonNull + @Override + public String getEditorTypeId() { + return TYPE_ID; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/log/AppLogFragment.java b/app/src/main/java/com/tyron/code/ui/editor/log/AppLogFragment.java new file mode 100644 index 00000000..b7335163 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/log/AppLogFragment.java @@ -0,0 +1,124 @@ +package com.tyron.code.ui.editor.log; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.tyron.builder.log.LogViewModel; +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.builder.project.Project; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorFragment; +import com.tyron.code.ui.editor.log.adapter.LogAdapter; +import com.tyron.code.ui.main.MainViewModel; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.fileeditor.api.FileEditorManager; + +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +public class AppLogFragment extends Fragment + implements ProjectManager.OnProjectOpenListener { + + /** Only used in IDE Logs **/ + private Handler mHandler; + + public static AppLogFragment newInstance(int id) { + AppLogFragment fragment = new AppLogFragment(); + Bundle bundle = new Bundle(); + bundle.putInt("id", id); + fragment.setArguments(bundle); + return fragment; + } + + private int id; + private MainViewModel mMainViewModel; + private LogViewModel mModel; + private LogAdapter mAdapter; + private RecyclerView mRecyclerView; + + public AppLogFragment() { + + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + id = requireArguments().getInt("id"); + + mModel = new ViewModelProvider(requireActivity()).get(LogViewModel.class); + mMainViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + FrameLayout mRoot = new FrameLayout(requireContext()); + + mAdapter = new LogAdapter(); + mAdapter.setListener(diagnostic -> { + if (diagnostic.getSource() != null) { + if (getContext() != null) { + FileEditorManager manager = FileEditorManagerImpl.getInstance(); + manager.openFile(requireContext(), diagnostic.getSource(), it -> { + if (diagnostic.getLineNumber() > 0 && diagnostic.getColumnNumber() > 0) { + Bundle bundle = new Bundle(it.getFragment() + .getArguments()); + bundle.putInt(CodeEditorFragment.KEY_LINE, (int) diagnostic.getLineNumber()); + bundle.putInt(CodeEditorFragment.KEY_COLUMN, (int) diagnostic.getColumnNumber()); + it.getFragment() + .setArguments(bundle); + manager.openFileEditor(it); + } + }); + } + } + }); + mRecyclerView = new RecyclerView(requireContext()); + mRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); + mRecyclerView.setAdapter(mAdapter); + mRoot.addView(mRecyclerView, + new FrameLayout.LayoutParams(-1, -1)); + return mRoot; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + mModel.getLogs(id).observe(getViewLifecycleOwner(), this::process); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + } + + private void process(List texts) { + mAdapter.submitList(texts); + + if (mRecyclerView.canScrollVertically(-1)) { + mRecyclerView.scrollToPosition(mAdapter.getItemCount()); + } + } + + @Override + public void onProjectOpen(Project project) { + + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/log/adapter/LogAdapter.java b/app/src/main/java/com/tyron/code/ui/editor/log/adapter/LogAdapter.java new file mode 100644 index 00000000..deed1277 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/log/adapter/LogAdapter.java @@ -0,0 +1,175 @@ +package com.tyron.code.ui.editor.log.adapter; + +import android.graphics.Color; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.text.style.ForegroundColorSpan; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.code.R; + +import javax.tools.Diagnostic; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class LogAdapter extends RecyclerView.Adapter{ + + public interface OnClickListener { + void onClick(DiagnosticWrapper diagnostic); + } + + private final List mData = new ArrayList<>(); + private OnClickListener mListener; + + public LogAdapter() { + + } + + public void setListener(OnClickListener listener) { + mListener = listener; + } + + public void submitList(List newData) { + DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return mData.size(); + } + + @Override + public int getNewListSize() { + return newData.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return mData.get(oldItemPosition).equals(newData.get(newItemPosition)); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + return mData.get(oldItemPosition).equals(newData.get(newItemPosition)); + } + }); + mData.clear(); + mData.addAll(newData); + try { + result.dispatchUpdatesTo(this); + } catch (IndexOutOfBoundsException e) { + notifyDataSetChanged(); + } + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + FrameLayout layout = new FrameLayout(parent.getContext()); + layout.setLayoutParams(new RecyclerView.LayoutParams( + RecyclerView.LayoutParams.MATCH_PARENT, + RecyclerView.LayoutParams.WRAP_CONTENT)); + return new ViewHolder(layout); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.bind(mData.get(position)); + } + + @Override + public int getItemCount() { + return mData.size(); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + + public TextView textView; + + public ViewHolder(FrameLayout layout) { + super(layout); + + textView = new TextView(layout.getContext()); + textView.setTypeface(ResourcesCompat.getFont(layout.getContext(), R.font.jetbrains_mono_regular)); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + layout.addView(textView); + } + + public void bind(DiagnosticWrapper diagnostic) { + SpannableStringBuilder builder = new SpannableStringBuilder(); + if (diagnostic.getKind() != null) { + builder.append(diagnostic.getKind().name() + ": ", + new ForegroundColorSpan(getColor(diagnostic.getKind())), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { + builder.append(diagnostic.getMessage(Locale.getDefault()), + new ForegroundColorSpan(getColor(diagnostic.getKind())), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + builder.append(diagnostic.getMessage(Locale.getDefault())); + } + if (diagnostic.getSource() != null) { + builder.append(' '); + addClickableFile(builder, diagnostic); + } + textView.setText(builder); + } + } + + @ColorInt + private int getColor(Diagnostic.Kind kind) { + switch (kind) { + case ERROR: + return 0xffcf6679; + case MANDATORY_WARNING: + case WARNING: + return Color.YELLOW; + case NOTE: + return Color.CYAN; + default: + return 0xffFFFFFF; + } + } + + private void addClickableFile(SpannableStringBuilder sb, final DiagnosticWrapper diagnostic) { + if (diagnostic.getSource() == null || !diagnostic.getSource().exists()) { + return; + } + if (diagnostic.getOnClickListener() != null) { + ClickableSpan span = new ClickableSpan() { + @Override + public void onClick(@NonNull View widget) { + diagnostic.getOnClickListener().onClick(widget); + } + }; + sb.append("[" + diagnostic.getExtra() + "]", span, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + return; + } + ClickableSpan span = new ClickableSpan() { + @Override + public void onClick(@NonNull View view) { + if (mListener != null) { + mListener.onClick(diagnostic); + } + } + }; + + String label = diagnostic.getSource().getName(); + label = label + ":" + diagnostic.getLineNumber(); + + sb.append("[" + label + "]", span, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/scheme/CodeAssistColorScheme.java b/app/src/main/java/com/tyron/code/ui/editor/scheme/CodeAssistColorScheme.java new file mode 100644 index 00000000..31c7833a --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/scheme/CodeAssistColorScheme.java @@ -0,0 +1,170 @@ +package com.tyron.code.ui.editor.scheme; + +import android.graphics.Color; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Maps; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; + +/** + * An editor color scheme that can be serialized and deserialized as json + */ +@Keep +public class CodeAssistColorScheme extends EditorColorScheme { + + @WorkerThread + public static CodeAssistColorScheme fromFile(@NonNull File file) throws IOException { + String contents = FileUtils.readFileToString(file, StandardCharsets.UTF_8); + CodeAssistColorScheme scheme = + new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setPrettyPrinting().create().fromJson(contents, CodeAssistColorScheme.class); + if (scheme == null) { + throw new IOException("Unable to parse scheme file."); + } + if (scheme.mName == null) { + throw new IOException("Scheme does not contain a name."); + } + if (scheme.mColors == null) { + throw new IOException("Scheme does not have colors."); + } + return scheme; + } + + @Expose + @SerializedName("name") + private String mName; + + @Expose + @SerializedName("colors") + private Map mNameToColorMap; + + public CodeAssistColorScheme() { + for (Integer id : Keys.sIdToNameMap.keySet()) { + int color = getColor(id); + setColor(id, color); + } + } + + /** + * Return the key of the id that will be serialized. + * @param id The editor color scheme id + * @return the mapped key + */ + protected String getName(int id) { + return Keys.sIdToNameMap.get(id); + } + + @Override + public void setColor(int type, int color) { + super.setColor(type, color); + + if (mNameToColorMap == null) { + mNameToColorMap = new HashMap<>(); + } + + String name = getName(type); + if (name != null) { + mNameToColorMap.remove(name); + mNameToColorMap.put(name, "#" + Integer.toHexString(color)); + } + } + + @Override + public int getColor(int type) { + if (mNameToColorMap == null) { + mNameToColorMap = new HashMap<>(); + } + + String name = getName(type); + if (name != null) { + String color = mNameToColorMap.get(name); + if (color != null) { + try { + return Color.parseColor(color); + } catch (IllegalArgumentException ignored) { + // fall through + } + } + } + + return super.getColor(type); + } + + @NonNull + public String toString() { + return new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .setPrettyPrinting() + .create() + .toJson(this); + } + + public static final class Keys { + private static final BiMap sIdToNameMap = HashBiMap.create(); + + static { + sIdToNameMap.put(WHOLE_BACKGROUND, "wholeBackground"); + sIdToNameMap.put(AUTO_COMP_PANEL_BG, "completionPanelBackground"); + sIdToNameMap.put(AUTO_COMP_PANEL_CORNER, "completionPanelStrokeColor"); + sIdToNameMap.put(LINE_NUMBER, "lineNumber"); + sIdToNameMap.put(LINE_NUMBER_BACKGROUND, "lineNumberBackground"); + sIdToNameMap.put(LINE_NUMBER_PANEL, "lineNumberPanel"); + sIdToNameMap.put(LINE_NUMBER_PANEL_TEXT, "lineNumberPanelText"); + sIdToNameMap.put(LINE_DIVIDER, "lineDivider"); + sIdToNameMap.put(SELECTION_HANDLE, "selectionHandle"); + sIdToNameMap.put(SELECTION_INSERT, "selectionInsert"); + sIdToNameMap.put(SCROLL_BAR_TRACK, "scrollbarTrack"); + sIdToNameMap.put(SCROLL_BAR_THUMB, "scrollbarThumb"); + sIdToNameMap.put(SCROLL_BAR_THUMB_PRESSED, "scrollbarThumbPressed"); + + sIdToNameMap.put(PROBLEM_TYPO, "problemTypo"); + sIdToNameMap.put(PROBLEM_ERROR, "problemError"); + sIdToNameMap.put(PROBLEM_WARNING, "problemWarning"); + + sIdToNameMap.put(BLOCK_LINE, "blockLine"); + sIdToNameMap.put(BLOCK_LINE_CURRENT, "blockLineCurrent"); + sIdToNameMap.put(UNDERLINE, "underline"); + sIdToNameMap.put(CURRENT_LINE, "currentLine"); + + sIdToNameMap.put(TEXT_NORMAL, "textNormal"); + sIdToNameMap.put(SELECTED_TEXT_BACKGROUND, "selectedTextBackground"); + sIdToNameMap.put(MATCHED_TEXT_BACKGROUND, "matchedTextBackground"); + sIdToNameMap.put(ATTRIBUTE_NAME, "attributeName"); + sIdToNameMap.put(ATTRIBUTE_VALUE, "attributeValue"); + sIdToNameMap.put(HTML_TAG, "htmlTag"); + sIdToNameMap.put(ANNOTATION, "annotation"); + sIdToNameMap.put(FUNCTION_NAME, "functionName"); + sIdToNameMap.put(IDENTIFIER_NAME, "identifierName"); + sIdToNameMap.put(IDENTIFIER_VAR, "identifierVar"); + sIdToNameMap.put(LITERAL, "literal"); + sIdToNameMap.put(OPERATOR, "operator"); + sIdToNameMap.put(COMMENT, "comment"); + sIdToNameMap.put(KEYWORD, "keyword"); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/scheme/CompiledEditorScheme.java b/app/src/main/java/com/tyron/code/ui/editor/scheme/CompiledEditorScheme.java new file mode 100644 index 00000000..5188e7fa --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/scheme/CompiledEditorScheme.java @@ -0,0 +1,85 @@ +package com.tyron.code.ui.editor.scheme; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.TypedValue; + +import androidx.annotation.StyleableRes; +import androidx.appcompat.widget.ThemeUtils; + +import com.google.android.material.color.MaterialColors; +import com.google.common.collect.ImmutableMap; +import com.tyron.code.R; + +import java.util.Map; + +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; + +/** + * An editor color scheme that is based on compiled xml files. + */ +public class CompiledEditorScheme extends EditorColorScheme { + + private static final Map sResIdMap = ImmutableMap.builder() + .put(R.styleable.EditorColorScheme_keyword, KEYWORD) + .put(R.styleable.EditorColorScheme_operator, OPERATOR) + .put(R.styleable.EditorColorScheme_annotation, ANNOTATION) + .put(R.styleable.EditorColorScheme_xmlAttributeName, ATTRIBUTE_NAME) + .put(R.styleable.EditorColorScheme_xmlAttributeValue, ATTRIBUTE_VALUE) + .put(R.styleable.EditorColorScheme_comment, COMMENT) + .put(R.styleable.EditorColorScheme_htmlTag, HTML_TAG) + .put(R.styleable.EditorColorScheme_identifierName, IDENTIFIER_NAME) + .put(R.styleable.EditorColorScheme_identifierVar, IDENTIFIER_VAR) + .put(R.styleable.EditorColorScheme_functionName, FUNCTION_NAME) + .put(R.styleable.EditorColorScheme_literal, LITERAL) + .put(R.styleable.EditorColorScheme_textNormal, TEXT_NORMAL) + .put(R.styleable.EditorColorScheme_blockLineColor, BLOCK_LINE) + .put(R.styleable.EditorColorScheme_problemError, PROBLEM_ERROR) + .put(R.styleable.EditorColorScheme_problemWarning, PROBLEM_WARNING) + .put(R.styleable.EditorColorScheme_problemTypo, PROBLEM_TYPO) + .put(R.styleable.EditorColorScheme_selectedTextBackground, SELECTED_TEXT_BACKGROUND) + .put(R.styleable.EditorColorScheme_completionPanelBackground, AUTO_COMP_PANEL_BG) + .put(R.styleable.EditorColorScheme_completionPanelStrokeColor, AUTO_COMP_PANEL_CORNER) + .put(R.styleable.EditorColorScheme_lineNumberBackground, LINE_NUMBER_BACKGROUND) + .put(R.styleable.EditorColorScheme_lineNumberTextColor, LINE_NUMBER_PANEL_TEXT) + .put(R.styleable.EditorColorScheme_lineNumberDividerColor, LINE_DIVIDER) + .put(R.styleable.EditorColorScheme_wholeBackground, WHOLE_BACKGROUND) + .build(); + + public CompiledEditorScheme(Context context) { + Resources.Theme theme = context.getTheme(); + TypedValue value = new TypedValue(); + theme.resolveAttribute(R.attr.editorColorScheme, value, true); + TypedArray typedArray = context.obtainStyledAttributes(value.data, R.styleable.EditorColorScheme); + for (Integer resId : sResIdMap.keySet()) { + putColor(context, resId, typedArray); + } + typedArray.recycle(); + } + + private void putColor(Context context, @StyleableRes int res, TypedArray array) { + if (!array.hasValue(res)) { + return; + } + + Integer integer = sResIdMap.get(res); + if (integer == null) { + return; + } + + if (array.getType(res) == TypedValue.TYPE_ATTRIBUTE) { + TypedValue typedValue = new TypedValue(); + array.getValue(res, typedValue); + int color = MaterialColors.getColor(context, typedValue.data, -1); + setColorInternal(integer, color); + return; + } + setColorInternal(integer, array.getColor(res, 0)); + } + + private void setColorInternal(int id, int value) { + mColors.put(id, value); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutAction.java new file mode 100644 index 00000000..92d4d0de --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutAction.java @@ -0,0 +1,10 @@ +package com.tyron.code.ui.editor.shortcuts; + +import com.tyron.editor.Editor; + +public interface ShortcutAction { + + boolean isApplicable(String kind); + + void apply(Editor editor, ShortcutItem item); +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutItem.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutItem.java new file mode 100644 index 00000000..78096e33 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutItem.java @@ -0,0 +1,22 @@ +package com.tyron.code.ui.editor.shortcuts; + +import java.util.List; + +public class ShortcutItem { + + public ShortcutItem() { + + } + + public ShortcutItem(List actions, String label, String kind) { + this.actions = actions; + this.label = label; + this.kind = kind; + } + + public List actions; + + public String kind; + + public String label; +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutsAdapter.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutsAdapter.java new file mode 100644 index 00000000..94765449 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/ShortcutsAdapter.java @@ -0,0 +1,73 @@ +package com.tyron.code.ui.editor.shortcuts; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.tyron.code.R; + +import java.util.ArrayList; +import java.util.List; + +public class ShortcutsAdapter extends RecyclerView.Adapter { + + public interface OnShortcutSelected { + void onShortcutClicked(ShortcutItem item, int position); + } + + private final List mItems = new ArrayList<>(); + private OnShortcutSelected mOnShortcutSelectedListener; + + public ShortcutsAdapter(List items) { + mItems.addAll(items); + } + + public void setOnShortcutSelectedListener(OnShortcutSelected listener) { + mOnShortcutSelectedListener = listener; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.shortcut_item, parent, false); + ViewHolder holder = new ViewHolder(view); + + view.setOnClickListener(view1 -> { + int position = holder.getBindingAdapterPosition(); + if (position != RecyclerView.NO_POSITION && mOnShortcutSelectedListener != null) { + mOnShortcutSelectedListener.onShortcutClicked(mItems.get(position), position); + } + }); + return holder; + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.bind(mItems.get(position)); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + + private final TextView textView; + + public ViewHolder(View view) { + super(view); + + textView = view.findViewById(R.id.shortcut_label); + } + + public void bind(ShortcutItem item) { + textView.setText(item.label); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/CursorMoveAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/CursorMoveAction.java new file mode 100644 index 00000000..dcfec0a1 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/CursorMoveAction.java @@ -0,0 +1,38 @@ +package com.tyron.code.ui.editor.shortcuts.action; + +import com.tyron.code.ui.editor.shortcuts.ShortcutAction; +import com.tyron.code.ui.editor.shortcuts.ShortcutItem; +import com.tyron.editor.Editor; + +public class CursorMoveAction implements ShortcutAction { + + public static final String KIND = "cursorMove"; + + public enum Direction { + UP, + DOWN, + LEFT, + RIGHT + } + + private final Direction mDirection; + + public CursorMoveAction(Direction direction, int count) { + mDirection = direction; + } + + @Override + public boolean isApplicable(String kind) { + return KIND.equals(kind); + } + + @Override + public void apply(Editor editor, ShortcutItem item) { + switch (mDirection) { + case UP: editor.moveSelectionUp(); break; + case DOWN: editor.moveSelectionDown(); break; + case LEFT: editor.moveSelectionLeft(); break; + case RIGHT: editor.moveSelectionRight(); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/RedoAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/RedoAction.java new file mode 100644 index 00000000..9c974e35 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/RedoAction.java @@ -0,0 +1,22 @@ +package com.tyron.code.ui.editor.shortcuts.action; + +import com.tyron.code.ui.editor.shortcuts.ShortcutAction; +import com.tyron.code.ui.editor.shortcuts.ShortcutItem; +import com.tyron.editor.Editor; + +public class RedoAction implements ShortcutAction { + + public static final String KIND = "redoAction"; + + @Override + public boolean isApplicable(String kind) { + return KIND.equals(kind); + } + + @Override + public void apply(Editor editor, ShortcutItem item) { + if (editor.getContent().canRedo()) { + editor.getContent().redo(); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextEditAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextEditAction.java new file mode 100644 index 00000000..583b6236 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextEditAction.java @@ -0,0 +1,18 @@ +package com.tyron.code.ui.editor.shortcuts.action; + +import com.tyron.code.ui.editor.shortcuts.ShortcutAction; +import com.tyron.code.ui.editor.shortcuts.ShortcutItem; +import com.tyron.editor.Editor; + +public class TextEditAction implements ShortcutAction { + + @Override + public boolean isApplicable(String kind) { + return kind.equals("textedit"); + } + + @Override + public void apply(Editor editor, ShortcutItem item) { + + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextInsertAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextInsertAction.java new file mode 100644 index 00000000..3ea26280 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/TextInsertAction.java @@ -0,0 +1,29 @@ +package com.tyron.code.ui.editor.shortcuts.action; + +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorView; +import com.tyron.code.ui.editor.shortcuts.ShortcutAction; +import com.tyron.code.ui.editor.shortcuts.ShortcutItem; +import com.tyron.editor.Caret; +import com.tyron.editor.Editor; + +public class TextInsertAction implements ShortcutAction { + + public static final String KIND = "textInsert"; + + @Override + public boolean isApplicable(String kind) { + return KIND.equals(kind); + } + + @Override + public void apply(Editor editor, ShortcutItem item) { + Caret cursor = editor.getCaret(); + + // temporary solution + if (editor instanceof CodeEditorView) { + ((CodeEditorView) editor).commitText(item.label); + } else { + editor.insert(cursor.getStartLine(), cursor.getEndColumn(), item.label); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/UndoAction.java b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/UndoAction.java new file mode 100644 index 00000000..48644bcb --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/editor/shortcuts/action/UndoAction.java @@ -0,0 +1,22 @@ +package com.tyron.code.ui.editor.shortcuts.action; + +import com.tyron.code.ui.editor.shortcuts.ShortcutAction; +import com.tyron.code.ui.editor.shortcuts.ShortcutItem; +import com.tyron.editor.Editor; + +public class UndoAction implements ShortcutAction { + + public static final String KIND = "undoAction"; + + @Override + public boolean isApplicable(String kind) { + return KIND.equals(kind); + } + + @Override + public void apply(Editor editor, ShortcutItem item) { + if (editor.getContent().canUndo()) { + editor.getContent().undo(); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/CommonFileKeys.java b/app/src/main/java/com/tyron/code/ui/file/CommonFileKeys.java new file mode 100644 index 00000000..94a60c29 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/CommonFileKeys.java @@ -0,0 +1,11 @@ +package com.tyron.code.ui.file; + +import com.tyron.ui.treeview.TreeNode; +import com.tyron.code.ui.file.tree.model.TreeFile; + +import org.jetbrains.kotlin.com.intellij.openapi.util.Key; + +public class CommonFileKeys { + + public static final Key> TREE_NODE = Key.create("treeFile"); +} diff --git a/app/src/main/java/com/tyron/code/ui/file/FileManagerAdapter.java b/app/src/main/java/com/tyron/code/ui/file/FileManagerAdapter.java new file mode 100644 index 00000000..08d0c834 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/FileManagerAdapter.java @@ -0,0 +1,132 @@ +package com.tyron.code.ui.file; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.tyron.code.R; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class FileManagerAdapter extends RecyclerView.Adapter { + + private static final int TYPE_NORMAL = 0; + private static final int TYPE_BACK = 1; + + public interface OnItemClickListener { + void onItemClick(File file, int position); + } + + private OnItemClickListener mListener; + private final List mFiles = new ArrayList<>(); + + public void setOnItemClickListener(OnItemClickListener listener) { + mListener = listener; + } + + public void submitFile(File file) { + mFiles.clear(); + + File[] files = file.listFiles(); + if (files != null) { + mFiles.addAll(List.of(files)); + } + + Collections.sort(mFiles, (p1, p2) -> { + if (p1.isFile() && p2.isFile()) { + return p1.getName().compareTo(p2.getName()); + } + + if (p1.isFile() && p2.isDirectory()) { + return -1; + } + + if (p1.isDirectory() && p2.isDirectory()) { + return p1.getName().compareTo(p2.getName()); + } + return 0; + }); + notifyDataSetChanged(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int p2) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.file_manager_item, parent, false); + final ViewHolder holder = new ViewHolder(view); + + if (mListener != null) { + view.setOnClickListener(v -> { + int position = holder.getBindingAdapterPosition(); + if (position != RecyclerView.NO_POSITION) { + File selected = null; + if (position != 0) { + selected = mFiles.get(position - 1); + } + mListener.onItemClick(selected, position); + } + }); + } + return holder; + } + + @Override + public void onBindViewHolder(ViewHolder holder, int p2) { + int type = holder.getItemViewType(); + if (type == TYPE_BACK) { + holder.bindBack(); + } else { + holder.bind(mFiles.get(p2 - 1)); + } + } + + @Override + public int getItemCount() { + return mFiles.size() + 1; + } + + @Override + public int getItemViewType(int position) { + if (position == 0) { + return TYPE_BACK; + } + return TYPE_NORMAL; + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + + public ImageView icon; + public TextView name; + + public ViewHolder(View view) { + super(view); + + icon = view.findViewById(R.id.icon); + name = view.findViewById(R.id.name); + } + + public void bind(File file) { + name.setText(file.getName()); + + if (file.isDirectory()) { + icon.setImageResource(R.drawable.round_folder_24); + } else if (file.isFile()) { + icon.setImageResource(R.drawable.round_insert_drive_file_24); + } + } + + public void bindBack() { + name.setText("..."); + icon.setImageResource(R.drawable.round_arrow_upward_24); + } + } +} + diff --git a/app/src/main/java/com/tyron/code/ui/file/FilePickerDialogFixed.java b/app/src/main/java/com/tyron/code/ui/file/FilePickerDialogFixed.java new file mode 100644 index 00000000..2af7d229 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/FilePickerDialogFixed.java @@ -0,0 +1,78 @@ +package com.tyron.code.ui.file; + +import android.content.Context; +import android.graphics.Color; +import android.os.Bundle; +import android.util.Log; +import android.widget.Button; +import android.widget.TextView; + +import com.github.angads25.filepicker.controller.adapters.FileListAdapter; +import com.github.angads25.filepicker.model.DialogProperties; +import com.github.angads25.filepicker.model.MarkedItemList; +import com.github.angads25.filepicker.view.FilePickerDialog; + +import java.lang.reflect.Field; + +public class FilePickerDialogFixed extends FilePickerDialog { + + public FilePickerDialogFixed(Context context) { + super(context); + } + + public FilePickerDialogFixed(Context context, DialogProperties properties) { + super(context, properties); + } + + public FilePickerDialogFixed(Context context, DialogProperties properties, int themeResId) { + super(context, properties, themeResId); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + + @Override + protected void onStart() { + super.onStart(); + + Button cancel = findViewById(com.github.angads25.filepicker.R.id.cancel); + Button select = findViewById(com.github.angads25.filepicker.R.id.select); + String positiveButtonNameStr = getContext().getString(com.github.angads25.filepicker.R.string.choose_button_label); + + cancel.setTextColor(Color.WHITE); + select.setTextColor(Color.WHITE); + + try { + Field mAdapterField = FilePickerDialog.class.getDeclaredField("mFileListAdapter"); + mAdapterField.setAccessible(true); + FileListAdapter adapter = (FileListAdapter) mAdapterField.get(this); + assert adapter != null; + adapter.setNotifyItemCheckedListener(() -> { + int size = MarkedItemList.getFileCount(); + if (size == 0) { + select.setEnabled(false); + select.setTextColor(Color.WHITE); + select.setText(positiveButtonNameStr); + } else { + select.setEnabled(true); + select.setText(positiveButtonNameStr + " (" + size + ") "); + } + adapter.notifyDataSetChanged(); + }); + } catch (NoSuchFieldException | IllegalAccessException e) { + Log.w("WizardFragment", "Unable to get declared field", e); + } + } + + /** + * Return the current path of the current directory + * @return the absolute path of the directory + */ + public String getCurrentPath() { + TextView path = findViewById(com.github.angads25.filepicker.R.id.dir_path); + return String.valueOf(path.getText()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/FileViewModel.java b/app/src/main/java/com/tyron/code/ui/file/FileViewModel.java new file mode 100644 index 00000000..a90f077a --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/FileViewModel.java @@ -0,0 +1,46 @@ +package com.tyron.code.ui.file; + +import android.os.Environment; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.tyron.completion.progress.ProgressManager; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.code.ui.file.tree.TreeUtil; +import com.tyron.code.ui.file.tree.model.TreeFile; + +import java.io.File; +import java.util.concurrent.Executors; + +public class FileViewModel extends ViewModel { + + private MutableLiveData mRoot = + new MutableLiveData<>(Environment.getExternalStorageDirectory()); + private MutableLiveData> mNode = new MutableLiveData<>(); + + public LiveData> getNodes() { + return mNode; + } + + public LiveData getRootFile() { + return mRoot; + } + + public void setRootFile(File root) { + mRoot.setValue(root); + refreshNode(root); + } + + public void setRootNode(TreeNode rootNode) { + mNode.setValue(rootNode); + } + + public void refreshNode(File root) { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + TreeNode node = TreeNode.root(TreeUtil.getNodes(root)); + mNode.postValue(node); + }); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/RegexReason.java b/app/src/main/java/com/tyron/code/ui/file/RegexReason.java new file mode 100644 index 00000000..cd1e488c --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/RegexReason.java @@ -0,0 +1,67 @@ +package com.tyron.code.ui.file; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.tyron.code.ui.file.dialog.CreateClassDialogFragment; + +/** + * Class that holds a Regex String and a reason if the regex doesn't match. + * + * This is used by {@link CreateClassDialogFragment} to check if the class name + * is a valid class name, certain providers may have different name requirements + * such as an xml file, in android xml files can only have lowercase letters and + * an underscore. + */ +public class RegexReason implements Parcelable { + + private String regexString; + + private String reason; + + public RegexReason(String regexString, String reason) { + this.regexString = regexString; + this.reason = reason; + } + + public String getRegexString() { + return regexString; + } + + public String getReason() { + return reason; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.regexString); + dest.writeString(this.reason); + } + + public void readFromParcel(Parcel source) { + this.regexString = source.readString(); + this.reason = source.readString(); + } + + protected RegexReason(Parcel in) { + this.regexString = in.readString(); + this.reason = in.readString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public RegexReason createFromParcel(Parcel source) { + return new RegexReason(source); + } + + @Override + public RegexReason[] newArray(int size) { + return new RegexReason[size]; + } + }; +} diff --git a/app/src/main/java/com/tyron/code/ui/file/action/ActionContext.java b/app/src/main/java/com/tyron/code/ui/file/action/ActionContext.java new file mode 100644 index 00000000..d7787efb --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/ActionContext.java @@ -0,0 +1,33 @@ +package com.tyron.code.ui.file.action; + +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; + +public class ActionContext { + + private final TreeFileManagerFragment mFragment; + + private final TreeView mTreeView; + + private final TreeNode mCurrentNode; + + public ActionContext(TreeFileManagerFragment mFragment, TreeView mTreeView, TreeNode mCurrentNode) { + this.mFragment = mFragment; + this.mTreeView = mTreeView; + this.mCurrentNode = mCurrentNode; + } + + public TreeFileManagerFragment getFragment() { + return mFragment; + } + + public TreeView getTreeView() { + return mTreeView; + } + + public TreeNode getCurrentNode() { + return mCurrentNode; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/action/FileAction.java b/app/src/main/java/com/tyron/code/ui/file/action/FileAction.java new file mode 100644 index 00000000..53c2dc4d --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/FileAction.java @@ -0,0 +1,50 @@ +package com.tyron.code.ui.file.action; + +import android.content.Context; +import android.view.Menu; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.Presentation; +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; + +import java.io.File; + +public abstract class FileAction extends AnAction { + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + if (!ActionPlaces.FILE_MANAGER.equals(event.getPlace())) { + return; + } + + File file = event.getData(CommonDataKeys.FILE); + if (file == null) { + return; + } + + if (!isApplicable(file)) { + return; + } + + Fragment fragment = event.getData(CommonDataKeys.FRAGMENT); + if (!(fragment instanceof TreeFileManagerFragment)) { + return; + } + + presentation.setVisible(true); + presentation.setText(getTitle(event.getDataContext())); + } + + public abstract String getTitle(Context context); + + public abstract boolean isApplicable(File file); +} diff --git a/app/src/main/java/com/tyron/code/ui/file/action/ImportFileActionGroup.java b/app/src/main/java/com/tyron/code/ui/file/action/ImportFileActionGroup.java new file mode 100644 index 00000000..462fe6e5 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/ImportFileActionGroup.java @@ -0,0 +1,39 @@ +package com.tyron.code.ui.file.action; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.actions.ActionGroup; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.Presentation; +import com.tyron.code.R; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.file.ImportDirectoryAction; +import com.tyron.code.ui.file.action.file.ImportFileAction; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.ui.treeview.TreeNode; + +public class ImportFileActionGroup extends ActionGroup { + + public static final String ID = "fileManagerImportGroup"; + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + TreeNode data = event.getData(CommonFileKeys.TREE_NODE); + if (data == null) { + return; + } + + presentation.setVisible(true); + presentation.setText(event.getDataContext().getString(R.string.menu_import)); + } + + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return new AnAction[]{new ImportFileAction(),new ImportDirectoryAction()}; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/file/action/NewFileActionGroup.java b/app/src/main/java/com/tyron/code/ui/file/action/NewFileActionGroup.java new file mode 100644 index 00000000..302b71fe --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/NewFileActionGroup.java @@ -0,0 +1,44 @@ +package com.tyron.code.ui.file.action; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.actions.ActionGroup; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.Presentation; +import com.tyron.code.R; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.file.CreateDirectoryAction; +import com.tyron.code.ui.file.action.file.CreateFileAction; +import com.tyron.code.ui.file.action.java.CreateClassAction; +import com.tyron.code.ui.file.action.kotlin.CreateKotlinClassAction; +import com.tyron.code.ui.file.action.xml.CreateLayoutAction; +import com.tyron.code.ui.file.tree.model.TreeFile; + +public class NewFileActionGroup extends ActionGroup { + + public static final String ID = "fileManagerNewGroup"; + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + TreeNode data = event.getData(CommonFileKeys.TREE_NODE); + if (data == null) { + return; + } + + presentation.setVisible(true); + presentation.setText(event.getDataContext().getString(R.string.menu_new)); + } + + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return new AnAction[]{new CreateFileAction(), new CreateClassAction(), + new CreateKotlinClassAction(), new CreateLayoutAction(), + new CreateDirectoryAction()}; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/action/android/CreateAndroidClassAction.java b/app/src/main/java/com/tyron/code/ui/file/action/android/CreateAndroidClassAction.java new file mode 100644 index 00000000..9061efb2 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/android/CreateAndroidClassAction.java @@ -0,0 +1,75 @@ +package com.tyron.code.ui.file.action.android; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.template.android.ActivityTemplate; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.java.CreateClassAction; +import com.tyron.code.ui.file.dialog.CreateClassDialogFragment; +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; +import com.tyron.code.ui.file.tree.TreeUtil; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.code.ui.project.ProjectManager; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; + +public class CreateAndroidClassAction extends CreateClassAction { + + @Override + public String getTitle(Context context) { + return "Android Class"; + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + TreeFileManagerFragment treeFragment = (TreeFileManagerFragment) e.getData(CommonDataKeys.FRAGMENT); + TreeView treeView = treeFragment.getTreeView(); + TreeNode treeNode = e.getData(CommonFileKeys.TREE_NODE); + + CreateClassDialogFragment fragment = CreateClassDialogFragment.newInstance( + Collections.singletonList(new ActivityTemplate()), Collections.emptyList()); + fragment.show(treeFragment.getChildFragmentManager(), null); + fragment.setOnClassCreatedListener((className, template) -> { + try { + File currentFile = treeNode.getContent().getFile(); + if (currentFile == null) { + throw new IOException("Unable to create class"); + } + + File createdFile = ProjectManager.createClass(currentFile, + className, template); + TreeUtil.updateNode(treeNode.getParent()); + treeView.refreshTreeView(); + + FileEditorManagerImpl.getInstance().openFile(treeFragment.requireContext(), + createdFile, + fileEditor -> treeFragment.getMainViewModel().openFile(fileEditor)); + + Module currentModule = ProjectManager.getInstance() + .getCurrentProject() + .getModule(treeNode.getContent().getFile()); + if (currentModule instanceof AndroidModule) { + ((AndroidModule) currentModule).addJavaFile(createdFile); + } + } catch (IOException exception) { + new MaterialAlertDialogBuilder(treeFragment.requireContext()) + .setMessage(exception.getMessage()) + .setPositiveButton(android.R.string.ok, null) + .setTitle("Error") + .show(); + } + }); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/CreateDirectoryAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/CreateDirectoryAction.java new file mode 100644 index 00000000..05594cb9 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/CreateDirectoryAction.java @@ -0,0 +1,105 @@ +package com.tyron.code.ui.file.action.file; + +import android.content.Context; +import android.content.DialogInterface; +import android.text.Editable; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.textfield.TextInputLayout; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.FileAction; +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; +import com.tyron.code.ui.file.tree.TreeUtil; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.common.util.SingleTextWatcher; + +import java.io.File; + +public class CreateDirectoryAction extends FileAction { + + public static final String ID = "fileManagerCreateDirectoryAction"; + + @Override + public String getTitle(Context context) { + return context.getString(R.string.menu_action_new_directory); + } + + @Override + public boolean isApplicable(File file) { + return file.isDirectory(); + } + + private void refreshTreeView(TreeNode currentNode, TreeView treeView) { + TreeUtil.updateNode(currentNode); + treeView.refreshTreeView(); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + TreeFileManagerFragment fragment = (TreeFileManagerFragment) e.getData(CommonDataKeys.FRAGMENT); + File currentDir = e.getData(CommonDataKeys.FILE); + TreeNode currentNode = e.getData(CommonFileKeys.TREE_NODE); + + AlertDialog dialog = new MaterialAlertDialogBuilder(fragment.requireContext()) + .setView(R.layout.create_class_dialog) + .setTitle(R.string.menu_action_new_directory) + .setPositiveButton(R.string.create_class_dialog_positive, null) + .setNegativeButton(android.R.string.cancel, null) + .create(); + dialog.setOnShowListener((d) -> { + dialog.findViewById(R.id.til_class_type).setVisibility(View.GONE); + TextInputLayout til = dialog.findViewById(R.id.til_class_name); + EditText editText = dialog.findViewById(R.id.et_class_name); + Button positive = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + + til.setHint(R.string.directory_name); + positive.setOnClickListener(v -> { + ProgressManager progress = ProgressManager.getInstance(); + progress.runNonCancelableAsync(() -> { + File fileToCreate = new File(currentDir, editText.getText().toString()); + if (!fileToCreate.mkdirs()) { + progress.runLater(() -> { + new MaterialAlertDialogBuilder(fragment.requireContext()) + .setTitle(R.string.error) + .setMessage(R.string.error_dir_access) + .setPositiveButton(android.R.string.ok, null) + .show(); + }); + } else { + progress.runLater(() -> { + if (fragment == null || fragment.isDetached()) { + return; + } + refreshTreeView(currentNode, fragment.getTreeView()); + dialog.dismiss(); + }); + } + }); + + + }); + editText.addTextChangedListener(new SingleTextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + File file = new File(currentDir, editable.toString()); + positive.setEnabled(!file.exists()); + } + }); + }); + dialog.show(); + } + +} diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/CreateFileAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/CreateFileAction.java new file mode 100644 index 00000000..52ade56c --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/CreateFileAction.java @@ -0,0 +1,112 @@ +package com.tyron.code.ui.file.action.file; + +import android.content.Context; +import android.content.DialogInterface; +import android.text.Editable; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.textfield.TextInputLayout; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.completion.xml.task.InjectResourcesTask; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.ActionContext; +import com.tyron.code.ui.file.action.FileAction; +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; +import com.tyron.code.ui.file.tree.TreeUtil; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.common.util.SingleTextWatcher; + +import java.io.File; +import java.io.IOException; + +public class CreateFileAction extends FileAction { + + @Override + public String getTitle(Context context) { + return context.getString(R.string.menu_action_new_file); + } + + @Override + public boolean isApplicable(File file) { + return file.isDirectory(); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + TreeFileManagerFragment fragment = (TreeFileManagerFragment) e.getRequiredData(CommonDataKeys.FRAGMENT); + TreeNode currentNode = e.getRequiredData(CommonFileKeys.TREE_NODE); + ActionContext actionContext = new ActionContext(fragment, fragment.getTreeView(), + currentNode); + onMenuItemClick(actionContext); + } + + @SuppressWarnings("ConstantConditions") + private void onMenuItemClick(ActionContext context) { + File currentDir = context.getCurrentNode().getValue().getFile(); + AlertDialog dialog = new MaterialAlertDialogBuilder(context.getFragment().requireContext()) + .setView(R.layout.create_class_dialog) + .setTitle(R.string.menu_action_new_file) + .setPositiveButton(R.string.create_class_dialog_positive, null) + .setNegativeButton(android.R.string.cancel, null) + .create(); + dialog.setOnShowListener((d) -> { + dialog.findViewById(R.id.til_class_type).setVisibility(View.GONE); + TextInputLayout til = dialog.findViewById(R.id.til_class_name); + EditText editText = dialog.findViewById(R.id.et_class_name); + Button positive = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + + til.setHint(R.string.file_name); + positive.setOnClickListener(v -> { + File fileToCreate = new File(currentDir, editText.getText().toString()); + if (!createFileSilently(fileToCreate)) { + new AlertDialog.Builder(context.getFragment().requireContext()) + .setTitle(R.string.error) + .setMessage(R.string.error_dir_access) + .setPositiveButton(android.R.string.ok, null) + .show(); + } else { + refreshTreeView(context); + dialog.dismiss(); + + try { + InjectResourcesTask.inject(ProjectManager.getInstance().getCurrentProject()); + } catch (IOException e) { + // ignored + } + } + }); + editText.addTextChangedListener(new SingleTextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + File file = new File(currentDir, editable.toString()); + positive.setEnabled(!file.exists()); + } + }); + }); + dialog.show(); + } + + private boolean createFileSilently(File file) { + try { + return file.createNewFile(); + } catch (IOException e) { + return false; + } + } + + private void refreshTreeView(ActionContext context) { + TreeNode currentNode = context.getCurrentNode(); + TreeUtil.updateNode(currentNode); + context.getTreeView().refreshTreeView(); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/DeleteFileAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/DeleteFileAction.java new file mode 100644 index 00000000..6674e6b3 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/DeleteFileAction.java @@ -0,0 +1,126 @@ +package com.tyron.code.ui.file.action.file; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.builder.project.api.FileManager; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; +import com.tyron.code.ui.file.tree.TreeUtil; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.builder.project.api.JavaModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.R; +import com.tyron.code.ui.file.action.FileAction; +import com.tyron.common.util.StringSearch; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; + +import kotlin.io.FileWalkDirection; +import kotlin.io.FilesKt; + +public class DeleteFileAction extends FileAction { + + public static final String ID = "fileManagerDeleteFileAction"; + + @Override + public String getTitle(Context context) { + return context.getString(R.string.dialog_delete); + } + + @Override + public boolean isApplicable(File file) { + return true; + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + TreeFileManagerFragment fragment = + (TreeFileManagerFragment) e.getRequiredData(CommonDataKeys.FRAGMENT); + TreeView treeView = fragment.getTreeView(); + TreeNode currentNode = e.getRequiredData(CommonFileKeys.TREE_NODE); + + new MaterialAlertDialogBuilder(fragment.requireContext()) + .setMessage(String.format(fragment.getString(R.string.dialog_confirm_delete), + currentNode.getValue().getFile().getName())) + .setPositiveButton(fragment.getString(R.string.dialog_delete), (d, which) -> { + ProgressManager progress = ProgressManager.getInstance(); + progress.runNonCancelableAsync(() -> { + boolean success = deleteFiles(currentNode, fragment); + progress.runLater(() -> { + if (fragment.isDetached()) { + return; + } + if (success) { + treeView.deleteNode(currentNode); + TreeUtil.updateNode(currentNode.getParent()); + treeView.refreshTreeView(); + FileEditorManagerImpl.getInstance().closeFile(currentNode.getValue() + .getFile()); + } else { + new MaterialAlertDialogBuilder(fragment.requireContext()) + .setTitle(R.string.error) + .setMessage("Failed to delete file.") + .setPositiveButton(android.R.string.ok, null) + .show(); + } + }); + }); + + }) + .show(); + } + + + + private boolean deleteFiles(TreeNode currentNode, TreeFileManagerFragment fragment) { + File currentFile = currentNode.getContent().getFile(); + FilesKt.walk(currentFile, FileWalkDirection.TOP_DOWN).iterator().forEachRemaining(file -> { + Module module = ProjectManager.getInstance() + .getCurrentProject() + .getModule(file); + + if (file.getName().endsWith(".java")) { + // todo: add .kt and .xml checks + + ProgressManager.getInstance().runLater(() -> + fragment.getMainViewModel().removeFile(file)); + + if (module instanceof JavaModule) { + String packageName = StringSearch.packageName(file); + if (packageName != null) { + packageName += "." + file.getName() + .substring(0, file.getName().lastIndexOf(".")); + } + ((JavaModule) module).removeJavaFile(packageName); + } + } + + ProgressManager.getInstance().runLater(() -> { + FileManager fileManager = module.getFileManager(); + if (fileManager.isOpened(file)) { + fileManager.closeFileForSnapshot(file); + } + }); + }); + try { + FileUtils.forceDelete(currentFile); + } catch (IOException e) { + return false; + } + return true; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/ImportDirectoryAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/ImportDirectoryAction.java new file mode 100644 index 00000000..192883c1 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/ImportDirectoryAction.java @@ -0,0 +1,92 @@ +package com.tyron.code.ui.file.action.file; + +import android.content.Context; +import android.os.Environment; + +import androidx.annotation.NonNull; + +import com.github.angads25.filepicker.model.DialogConfigs; +import com.github.angads25.filepicker.model.DialogProperties; +import com.github.angads25.filepicker.view.FilePickerDialog; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.FileAction; +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; +import com.tyron.code.ui.file.tree.TreeUtil; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; + +public class ImportDirectoryAction extends FileAction { + + public static final String ID = "fileManagerImportDirectoryAction"; + + @Override + public String getTitle(Context context) { + return context.getString(R.string.menu_action_new_directory); + } + + @Override + public boolean isApplicable(File file) { + return file.isDirectory(); + } + + private void refreshTreeView(TreeNode currentNode, TreeView treeView) { + TreeUtil.updateNode(currentNode); + treeView.refreshTreeView(); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + TreeFileManagerFragment fragment = + (TreeFileManagerFragment) e.getData(CommonDataKeys.FRAGMENT); + File currentDir = e.getData(CommonDataKeys.FILE); + TreeNode currentNode = e.getData(CommonFileKeys.TREE_NODE); + + DialogProperties properties = new DialogProperties(); + properties.selection_mode = DialogConfigs.SINGLE_MODE; + properties.selection_type = DialogConfigs.DIR_SELECT; + properties.root = Environment.getExternalStorageDirectory(); + properties.error_dir = fragment.requireContext().getExternalFilesDir(null); + + FilePickerDialog dialog = new FilePickerDialog(fragment.requireContext(), properties); + dialog.setDialogSelectionListener(files -> { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + String file = files[0]; + try { + FileUtils.copyDirectoryToDirectory(new File(file), currentDir); + } catch (IOException ioException) { + ProgressManager.getInstance().runLater(() -> { + if (fragment.isDetached() || fragment.getContext() == null) { + return; + } + AndroidUtilities.showSimpleAlert(e.getDataContext(), R.string.error, + ioException.getLocalizedMessage()); + }); + } + + ProgressManager.getInstance().runLater(() -> { + if (fragment.isDetached() || fragment.getContext() == null) { + return; + } + refreshTreeView(currentNode, fragment.getTreeView()); + }); + + }); + + }); + dialog.show(); + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/file/action/file/ImportFileAction.java b/app/src/main/java/com/tyron/code/ui/file/action/file/ImportFileAction.java new file mode 100644 index 00000000..ccbde3c4 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/file/ImportFileAction.java @@ -0,0 +1,89 @@ +package com.tyron.code.ui.file.action.file; + +import android.content.Context; +import android.os.Environment; + +import androidx.annotation.NonNull; + +import com.github.angads25.filepicker.model.DialogConfigs; +import com.github.angads25.filepicker.model.DialogProperties; +import com.github.angads25.filepicker.view.FilePickerDialog; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.FileAction; +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; +import com.tyron.code.ui.file.tree.TreeUtil; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; + +public class ImportFileAction extends FileAction { + public static final String ID = "fileManagerImportFileAction"; + + @Override + public String getTitle(Context context) { + return context.getString(R.string.menu_action_new_file); + } + + @Override + public boolean isApplicable(File file) { + return file.isDirectory(); + } + + private void refreshTreeView(TreeNode currentNode, TreeView treeView) { + TreeUtil.updateNode(currentNode); + treeView.refreshTreeView(); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + TreeFileManagerFragment fragment = + (TreeFileManagerFragment) e.getData(CommonDataKeys.FRAGMENT); + File currentDir = e.getData(CommonDataKeys.FILE); + TreeNode currentNode = e.getData(CommonFileKeys.TREE_NODE); + + DialogProperties properties = new DialogProperties(); + properties.selection_mode = DialogConfigs.MULTI_MODE; + properties.selection_type = DialogConfigs.FILE_SELECT; + properties.root = Environment.getExternalStorageDirectory(); + properties.error_dir = fragment.requireContext().getExternalFilesDir(null); + + FilePickerDialog dialog = new FilePickerDialog(fragment.requireContext(), properties); + dialog.setDialogSelectionListener(files -> { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + for (String file : files) { + try { + FileUtils.copyFileToDirectory(new File(file), currentDir); + } catch (IOException ioException) { + ProgressManager.getInstance().runLater(() -> { + if (fragment.isDetached() || fragment.getContext() == null) { + return; + } + AndroidUtilities.showSimpleAlert(e.getDataContext(), R.string.error, + ioException.getLocalizedMessage()); + }); + } + } + ProgressManager.getInstance().runLater(() -> { + if (fragment.isDetached() || fragment.getContext() == null) { + return; + } + refreshTreeView(currentNode, fragment.getTreeView()); + }); + + }); + }); + dialog.show(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/file/action/java/CreateClassAction.java b/app/src/main/java/com/tyron/code/ui/file/action/java/CreateClassAction.java new file mode 100644 index 00000000..5418c751 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/java/CreateClassAction.java @@ -0,0 +1,103 @@ +package com.tyron.code.ui.file.action.java; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.builder.project.api.JavaModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.R; +import com.tyron.code.template.CodeTemplate; +import com.tyron.code.template.java.AbstractTemplate; +import com.tyron.code.template.java.InterfaceTemplate; +import com.tyron.code.template.java.JavaClassTemplate; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.FileAction; +import com.tyron.code.ui.file.dialog.CreateClassDialogFragment; +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.code.util.ProjectUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class CreateClassAction extends FileAction { + + @Override + public boolean isApplicable(File file) { + if (file.isDirectory()) { + return ProjectUtils.getPackageName(file) != null; + } + return false; + } + + @Override + public String getTitle(Context context) { + return context.getString(R.string.menu_action_new_java_class); + } + + private List getTemplates() { + return Arrays.asList( + new JavaClassTemplate(), + new AbstractTemplate(), + new InterfaceTemplate()); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + + TreeFileManagerFragment fragment = (TreeFileManagerFragment) e.getData(CommonDataKeys.FRAGMENT); + TreeView treeView = fragment.getTreeView(); + File file = e.getData(CommonDataKeys.FILE); + TreeNode treeNode = e.getData(CommonFileKeys.TREE_NODE); + + CreateClassDialogFragment dialogFragment = + CreateClassDialogFragment.newInstance(getTemplates(), + Collections.emptyList()); + dialogFragment.show(fragment.getChildFragmentManager(), null); + dialogFragment.setOnClassCreatedListener((className, template) -> { + try { + File createdFile = ProjectManager.createClass( + file, + className, template); + TreeNode newNode = new TreeNode<>( + TreeFile.fromFile(createdFile), + treeNode.getLevel() + 1 + ); + + if (createdFile == null) { + throw new IOException("Unable to create file"); + } + + treeView.addNode(treeNode, newNode); + treeView.refreshTreeView(); + FileEditorManagerImpl.getInstance().openFile(fragment.requireContext(), + createdFile, + fileEditor -> fragment.getMainViewModel().openFile(fileEditor)); + + Module currentModule = ProjectManager.getInstance() + .getCurrentProject() + .getModule(treeNode.getContent().getFile()); + if (currentModule instanceof JavaModule) { + ((JavaModule) currentModule).addJavaFile(createdFile); + } + } catch (IOException exception) { + new MaterialAlertDialogBuilder(fragment.requireContext()) + .setMessage(exception.getMessage()) + .setPositiveButton(android.R.string.ok, null) + .setTitle(R.string.error) + .show(); + } + }); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/action/kotlin/CreateKotlinClassAction.java b/app/src/main/java/com/tyron/code/ui/file/action/kotlin/CreateKotlinClassAction.java new file mode 100644 index 00000000..ed2f3089 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/kotlin/CreateKotlinClassAction.java @@ -0,0 +1,97 @@ +package com.tyron.code.ui.file.action.kotlin; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.builder.project.api.KotlinModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.R; +import com.tyron.code.template.CodeTemplate; +import com.tyron.code.template.kotlin.KotlinAbstractClassTemplate; +import com.tyron.code.template.kotlin.KotlinClassTemplate; +import com.tyron.code.template.kotlin.KotlinInterfaceTemplate; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.action.FileAction; +import com.tyron.code.ui.file.dialog.CreateClassDialogFragment; +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.code.util.ProjectUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class CreateKotlinClassAction extends FileAction { + + @Override + public String getTitle(Context context) { + return context.getString(R.string.menu_action_new_kotlin_class); + } + + @Override + public boolean isApplicable(File file) { + if (file.isDirectory()) { + return ProjectUtils.getPackageName(file) != null; + } + return false; + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + TreeFileManagerFragment fragment = (TreeFileManagerFragment) e.getData(CommonDataKeys.FRAGMENT); + TreeView treeView = fragment.getTreeView(); + TreeNode currentNode = e.getData(CommonFileKeys.TREE_NODE); + CreateClassDialogFragment dialogFragment = + CreateClassDialogFragment.newInstance(getTemplates(), + Collections.emptyList()); + dialogFragment.show(fragment.getChildFragmentManager(), null); + dialogFragment.setOnClassCreatedListener((className, template) -> { + try { + File createdFile = ProjectManager.createClass( + currentNode.getContent().getFile(), + className, template + ); + TreeNode newNode = new TreeNode<>( + TreeFile.fromFile(createdFile), + currentNode.getLevel() + 1 + ); + + treeView.addNode(currentNode, newNode); + treeView.refreshTreeView(); + FileEditorManagerImpl.getInstance().openFile(fragment.requireContext(), + createdFile, + fileEditor -> fragment.getMainViewModel().openFile(fileEditor)); + + Module currentModule = ProjectManager.getInstance() + .getCurrentProject() + .getModule(currentNode.getContent().getFile()); + if (currentModule instanceof KotlinModule) { + ((KotlinModule) currentModule).addKotlinFile(createdFile); + } + } catch (IOException exception) { + new MaterialAlertDialogBuilder(fragment.requireContext()) + .setMessage(exception.getMessage()) + .setPositiveButton(android.R.string.ok, null) + .setTitle(R.string.error) + .show(); + } + }); + } + + private List getTemplates() { + return Arrays.asList( + new KotlinClassTemplate(), + new KotlinInterfaceTemplate(), + new KotlinAbstractClassTemplate()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/action/xml/CreateLayoutAction.java b/app/src/main/java/com/tyron/code/ui/file/action/xml/CreateLayoutAction.java new file mode 100644 index 00000000..9318afd9 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/action/xml/CreateLayoutAction.java @@ -0,0 +1,83 @@ +package com.tyron.code.ui.file.action.xml; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; +import com.tyron.code.template.CodeTemplate; +import com.tyron.code.template.xml.LayoutTemplate; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.RegexReason; +import com.tyron.code.ui.file.action.FileAction; +import com.tyron.code.ui.file.dialog.CreateClassDialogFragment; +import com.tyron.code.ui.file.tree.TreeFileManagerFragment; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.code.util.ProjectUtils; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +public class CreateLayoutAction extends FileAction { + + @Override + public String getTitle(Context context) { + return context.getString(R.string.menu_new_layout); + } + + @Override + public boolean isApplicable(File file) { + if (file.isDirectory()) { + return ProjectUtils.isResourceXMLDir(file) && file.getName().startsWith("layout"); + } + return false; + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + TreeFileManagerFragment fragment = + (TreeFileManagerFragment) e.getRequiredData(CommonDataKeys.FRAGMENT); + TreeView treeView = fragment.getTreeView(); + TreeNode currentNode = e.getRequiredData(CommonFileKeys.TREE_NODE); + + CreateClassDialogFragment dialogFragment = + CreateClassDialogFragment.newInstance(getTemplates(), + Collections.singletonList(new RegexReason("^[a-z0-9_]+$", + fragment.getString(R.string.error_resource_name_restriction)))); + dialogFragment.show(fragment.getChildFragmentManager(), null); + dialogFragment.setOnClassCreatedListener((className, template) -> { + try { + File createdFile = ProjectManager.createFile(currentNode.getContent().getFile(), + className, template); + + if (createdFile == null) { + throw new IOException(fragment.getString(R.string.error_file_creation)); + } + + TreeNode newNode = new TreeNode<>(TreeFile.fromFile(createdFile), + currentNode.getLevel() + 1); + + treeView.addNode(currentNode, newNode); + treeView.refreshTreeView(); + FileEditorManagerImpl.getInstance().openFile(fragment.requireContext(), + createdFile, + fileEditor -> fragment.getMainViewModel().openFile(fileEditor)); + } catch (IOException exception) { + new MaterialAlertDialogBuilder(fragment.requireContext()).setMessage(exception.getMessage()).setPositiveButton(android.R.string.ok, null).setTitle(R.string.error).show(); + } + }); + } + + private List getTemplates() { + return Collections.singletonList(new LayoutTemplate()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/dialog/CreateClassDialogFragment.java b/app/src/main/java/com/tyron/code/ui/file/dialog/CreateClassDialogFragment.java new file mode 100644 index 00000000..5f761189 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/dialog/CreateClassDialogFragment.java @@ -0,0 +1,146 @@ +package com.tyron.code.ui.file.dialog; + +import android.app.Dialog; +import android.os.Bundle; +import android.text.Editable; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.Button; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.AppCompatAutoCompleteTextView; +import androidx.fragment.app.DialogFragment; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; +import com.tyron.code.R; +import com.tyron.code.template.CodeTemplate; +import com.tyron.code.template.java.JavaClassTemplate; +import com.tyron.code.ui.file.RegexReason; +import com.tyron.common.util.SingleTextWatcher; + +import javax.lang.model.SourceVersion; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CreateClassDialogFragment extends DialogFragment { + + public static CreateClassDialogFragment newInstance(List templates, List regex) { + CreateClassDialogFragment fragment = new CreateClassDialogFragment(); + Bundle args = new Bundle(); + args.putParcelableArrayList("regex", new ArrayList<>(regex)); + args.putParcelableArrayList("templates", new ArrayList<>(templates)); + fragment.setArguments(args); + return fragment; + } + + public interface OnClassCreatedListener { + void onClassCreated(String className, CodeTemplate template); + } + + private List mTemplates; + private OnClassCreatedListener mListener; + + private TextInputLayout mClassNameLayout; + private TextInputEditText mClassNameEditText; + private AppCompatAutoCompleteTextView mClassTypeTextView; + + public void setOnClassCreatedListener(OnClassCreatedListener listener) { + mListener = listener; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mTemplates = getTemplates(); + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext()); + builder.setTitle(R.string.dialog_create_class_title); + + // noinspection InflateParams + View view = getLayoutInflater().inflate(R.layout.create_class_dialog, null); + + mClassNameLayout = view.findViewById(R.id.til_class_name); + mClassNameEditText = view.findViewById(R.id.et_class_name); + + ArrayAdapter adapter = new ArrayAdapter<>( + requireContext(), android.R.layout.simple_list_item_1, mTemplates); + mClassTypeTextView = view.findViewById(R.id.et_class_type); + mClassTypeTextView.setAdapter(adapter); + mClassTypeTextView.setText(mTemplates.get(0).getName(), false); + + builder.setView(view); + builder.setPositiveButton(R.string.create_class_dialog_positive, ((dialogInterface, i) -> { + if (mListener != null) { + mListener.onClassCreated(String.valueOf(mClassNameEditText.getText()), getCurrentTemplate()); + } + })); + builder.setNegativeButton(android.R.string.cancel, null); + + AlertDialog dialog = builder.create(); + dialog.setOnShowListener(dialogInterface -> { + final Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE); + positiveButton.setEnabled(false); + + mClassNameEditText.addTextChangedListener(new SingleTextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + String name = editable.toString(); + if (getCurrentTemplate() instanceof JavaClassTemplate) { + if (SourceVersion.isName(name)) { + mClassNameLayout.setErrorEnabled(false); + positiveButton.setEnabled(true); + } else { + positiveButton.setEnabled(false); + mClassNameLayout.setError(getString(R.string.create_class_dialog_invalid_name)); + } + } else { + if (isValid(name)) { + mClassNameLayout.setErrorEnabled(false); + positiveButton.setEnabled(true); + } else { + positiveButton.setEnabled(false); + } + } + } + }); + }); + return dialog; + } + + + private CodeTemplate getCurrentTemplate() { + String name = String.valueOf(mClassTypeTextView.getText()); + return mTemplates.stream() + .filter(temp -> temp.getName().equals(name)) + .findAny() + .orElse(new JavaClassTemplate()); + } + private List getTemplates() { + return requireArguments().getParcelableArrayList("templates"); + } + + private boolean isValid(String string) { + List regex = requireArguments().getParcelableArrayList("regex"); + for (RegexReason s : regex) { + Pattern pattern = Pattern.compile(s.getRegexString()); + Matcher matcher = pattern.matcher(string); + if (!matcher.find()) { + mClassNameLayout.setError(s.getReason()); + return false; + } + } + return true; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/dialog/FileManagerFragment.java b/app/src/main/java/com/tyron/code/ui/file/dialog/FileManagerFragment.java new file mode 100644 index 00000000..c4a7ed56 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/dialog/FileManagerFragment.java @@ -0,0 +1,135 @@ +package com.tyron.code.ui.file.dialog; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import java.io.File; +import java.util.Objects; + +import android.os.Bundle; +import android.view.ViewGroup; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; +import androidx.recyclerview.widget.RecyclerView; + +import com.tyron.code.R; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.activity.OnBackPressedCallback; + +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; +import com.tyron.code.ui.file.FileManagerAdapter; +import com.tyron.code.ui.main.MainFragment; + +@SuppressWarnings("FieldCanBeLocal") +public class FileManagerFragment extends Fragment { + + public static FileManagerFragment newInstance(File file) { + FileManagerFragment fragment = new FileManagerFragment(); + Bundle args = new Bundle(); + args.putString("path", file.getAbsolutePath()); + fragment.setArguments(args); + return fragment; + } + + OnBackPressedCallback callback = new OnBackPressedCallback(false) { + @Override + public void handleOnBackPressed() { + if (!mCurrentFile.equals(mRootFile)) { + mAdapter.submitFile(Objects.requireNonNull(mCurrentFile.getParentFile())); + check(mCurrentFile.getParentFile()); + } + } + }; + + private File mRootFile; + private File mCurrentFile; + + private LinearLayout mRoot; + private RecyclerView mListView; + private LinearLayoutManager mLayoutManager; + private FileManagerAdapter mAdapter; + + public FileManagerFragment() { + + } + + public void disableBackListener() { + callback.setEnabled(false); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + assert getArguments() != null; + mRootFile = new File(getArguments().getString("path")); + if (savedInstanceState != null) { + mCurrentFile = new File(savedInstanceState.getString("currentFile"), mRootFile.getAbsolutePath()); + } else { + mCurrentFile = mRootFile; + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + mRoot = (LinearLayout) inflater.inflate(R.layout.file_manager_fragment, container, false); + + mLayoutManager = new LinearLayoutManager(requireContext()); + mAdapter = new FileManagerAdapter(); + + mListView = mRoot.findViewById(R.id.listView); + mListView.setLayoutManager(mLayoutManager); + mListView.setAdapter(mAdapter); + + requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), callback); + return mRoot; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + mAdapter.submitFile(mCurrentFile); + mAdapter.setOnItemClickListener((file, position) -> { + if (position == 0) { + if (!mCurrentFile.equals(mRootFile)) { + mAdapter.submitFile(Objects.requireNonNull(mCurrentFile.getParentFile())); + check(mCurrentFile.getParentFile()); + } + return; + } + + if (file.isFile()) { + openFile(file); + } else if (file.isDirectory()) { + mAdapter.submitFile(file); + check(file); + } + }); + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putSerializable("currentDir", mCurrentFile); + } + + private void openFile(File file) { + Fragment parent = getParentFragment(); + + if (parent != null) { + if (parent instanceof MainFragment) { + ((MainFragment) parent).openFile(FileEditorManagerImpl.getInstance().openFile(requireContext(), file, true)[0]); + } + } + } + /** + * Checks if the current file is equal to the root file if so, + * it disables the OnBackPressedCallback + */ + private void check(File currentFile) { + mCurrentFile = currentFile; + + callback.setEnabled(!currentFile.getAbsolutePath().equals(mRootFile.getAbsolutePath())); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/event/RefreshRootEvent.java b/app/src/main/java/com/tyron/code/ui/file/event/RefreshRootEvent.java new file mode 100644 index 00000000..dbad8845 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/event/RefreshRootEvent.java @@ -0,0 +1,24 @@ +package com.tyron.code.ui.file.event; + +import androidx.annotation.NonNull; + +import com.tyron.code.event.Event; + +import java.io.File; + +/** + * Used to notify the file manager that its root needs to be refreshed + */ +public class RefreshRootEvent extends Event { + + private final File mRoot; + + public RefreshRootEvent(@NonNull File root) { + mRoot = root; + } + + @NonNull + public File getRoot() { + return mRoot; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/tree/TreeFileManagerFragment.java b/app/src/main/java/com/tyron/code/ui/file/tree/TreeFileManagerFragment.java new file mode 100644 index 00000000..954d7abe --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/tree/TreeFileManagerFragment.java @@ -0,0 +1,201 @@ +package com.tyron.code.ui.file.tree; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.FrameLayout; +import android.widget.HorizontalScrollView; +import android.widget.PopupMenu; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.ThemeUtils; +import androidx.core.view.ViewCompat; +import androidx.core.widget.NestedScrollView; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import com.tyron.actions.ActionManager; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.R; +import com.tyron.code.event.EventManager; +import com.tyron.code.event.EventReceiver; +import com.tyron.code.event.SubscriptionReceipt; +import com.tyron.code.event.Unsubscribe; +import com.tyron.code.ui.file.event.RefreshRootEvent; +import com.tyron.code.util.UiUtilsKt; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.ui.treeview.TreeNode; +import com.tyron.ui.treeview.TreeView; +import com.tyron.code.ui.editor.impl.FileEditorManagerImpl; +import com.tyron.code.ui.file.CommonFileKeys; +import com.tyron.code.ui.file.FileViewModel; +import com.tyron.code.ui.file.tree.binder.TreeFileNodeViewBinder.TreeFileNodeListener; +import com.tyron.code.ui.file.tree.binder.TreeFileNodeViewFactory; +import com.tyron.code.ui.file.tree.model.TreeFile; +import com.tyron.code.ui.main.MainViewModel; +import com.tyron.code.ui.project.ProjectManager; + +import java.io.File; +import java.util.Collections; +import java.util.concurrent.Executors; + +public class TreeFileManagerFragment extends Fragment { + + /** + * @deprecated Instantiate this fragment directly without arguments and + * use {@link FileViewModel} to update the nodes + */ + @Deprecated + public static TreeFileManagerFragment newInstance(File root) { + TreeFileManagerFragment fragment = new TreeFileManagerFragment(); + Bundle args = new Bundle(); + args.putSerializable("rootFile", root); + fragment.setArguments(args); + return fragment; + } + + private MainViewModel mMainViewModel; + private FileViewModel mFileViewModel; + private TreeView treeView; + + public TreeFileManagerFragment() { + super(R.layout.tree_file_manager_fragment); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mMainViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class); + mFileViewModel = new ViewModelProvider(requireActivity()).get(FileViewModel.class); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + ViewCompat.requestApplyInsets(view); + UiUtilsKt.addSystemWindowInsetToPadding(view, false, true, false, true); + + SwipeRefreshLayout refreshLayout = view.findViewById(R.id.refreshLayout); + refreshLayout.setOnRefreshListener(() -> partialRefresh(() -> { + refreshLayout.setRefreshing(false); + treeView.refreshTreeView(); + })); + + + treeView = new TreeView<>( + requireContext(), TreeNode.root(Collections.emptyList())); + + HorizontalScrollView horizontalScrollView = view.findViewById(R.id.horizontalScrollView); + horizontalScrollView.addView(treeView.getView(), new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.MATCH_PARENT + )); + treeView.getView().setNestedScrollingEnabled(false); + + EventManager eventManager = ApplicationLoader.getInstance() + .getEventManager(); + eventManager.subscribeEvent(getViewLifecycleOwner(), RefreshRootEvent.class, (event, unsubscribe) -> { + File refreshRoot = event.getRoot(); + TreeNode currentRoot = treeView.getRoot(); + if (currentRoot != null && refreshRoot.equals(currentRoot.getValue().getFile())) { + partialRefresh(() -> treeView.refreshTreeView()); + } else { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + TreeNode node = TreeNode.root(TreeUtil.getNodes(refreshRoot)); + ProgressManager.getInstance().runLater(() -> { + if (getActivity() == null) { + return; + } + treeView.refreshTreeView(node); + }); + }); + } + }); + + treeView.setAdapter(new TreeFileNodeViewFactory(new TreeFileNodeListener() { + @Override + public void onNodeToggled(TreeNode treeNode, boolean expanded) { + if (treeNode.isLeaf()) { + if (treeNode.getValue().getFile().isFile()) { + FileEditorManagerImpl.getInstance().openFile(requireContext(), treeNode.getValue().getFile(), true); + } + } + } + + @Override + public boolean onNodeLongClicked(View view, TreeNode treeNode, boolean expanded) { + PopupMenu popupMenu = new PopupMenu(requireContext(), view); + addMenus(popupMenu, treeNode); + popupMenu.show(); + return true; + } + })); + mFileViewModel.getNodes().observe(getViewLifecycleOwner(), node -> { + treeView.refreshTreeView(node); + }); + } + + + private void partialRefresh(Runnable callback) { + ProgressManager.getInstance().runNonCancelableAsync(() -> { + if (!treeView.getAllNodes().isEmpty()) { + TreeNode node = treeView.getAllNodes().get(0); + TreeUtil.updateNode(node); + ProgressManager.getInstance().runLater(() -> { + if (getActivity() == null) { + return; + } + callback.run(); + }); + } + }); + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + } + + /** + * Add menus to the current PopupMenu based on the current {@link TreeNode} + * + * @param popupMenu The PopupMenu to add to + * @param node The current TreeNode in the file tree + */ + private void addMenus(PopupMenu popupMenu, TreeNode node) { + DataContext dataContext = DataContext.wrap(requireContext()); + dataContext.putData(CommonDataKeys.FILE, node.getContent().getFile()); + dataContext.putData(CommonDataKeys.PROJECT, ProjectManager.getInstance().getCurrentProject()); + dataContext.putData(CommonDataKeys.FRAGMENT, TreeFileManagerFragment.this); + dataContext.putData(CommonDataKeys.ACTIVITY, requireActivity()); + dataContext.putData(CommonFileKeys.TREE_NODE, node); + + ActionManager.getInstance().fillMenu(dataContext, + popupMenu.getMenu(), ActionPlaces.FILE_MANAGER, + true, + false); + } + + public TreeView getTreeView() { + return treeView; + } + + public MainViewModel getMainViewModel() { + return mMainViewModel; + } + + public FileViewModel getFileViewModel() { + return mFileViewModel; + } +} + diff --git a/app/src/main/java/com/tyron/code/ui/file/tree/TreeUtil.java b/app/src/main/java/com/tyron/code/ui/file/tree/TreeUtil.java new file mode 100644 index 00000000..7f3d8f79 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/tree/TreeUtil.java @@ -0,0 +1,113 @@ +package com.tyron.code.ui.file.tree; + +import com.tyron.ui.treeview.TreeNode; +import com.tyron.code.ui.file.tree.model.TreeFile; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class TreeUtil { + + public static final Comparator FILE_FIRST_ORDER = (file1, file2) -> { + if (file1.isFile() && file2.isDirectory()) { + return 1; + } else if (file2.isFile() && file1.isDirectory()) { + return -1; + } else { + return String.CASE_INSENSITIVE_ORDER.compare(file1.getName(), file2.getName()); + } + }; + + public static TreeNode getRootNode(TreeNode node) { + TreeNode parent = node.getParent(); + TreeNode root = node; + while (parent != null) { + root = parent; + parent = parent.getParent(); + } + return root; + } + + public static void updateNode(TreeNode node) { + Set expandedNodes = TreeUtil.getExpandedNodes(node); + List> newChildren = getNodes(node.getValue().getFile(), node.getLevel()) + .get(0).getChildren(); + setExpandedNodes(newChildren, expandedNodes); + node.setChildren(newChildren); + } + + private static void setExpandedNodes(List> nodeList, Set expandedNodes) { + for (TreeNode treeFileTreeNode : nodeList) { + if (expandedNodes.contains(treeFileTreeNode.getValue().getFile())) { + treeFileTreeNode.setExpanded(true); + } + + setExpandedNodes(treeFileTreeNode.getChildren(), expandedNodes); + } + } + + private static Set getExpandedNodes(TreeNode node) { + Set expandedNodes = new HashSet<>(); + if (node.isExpanded()) { + expandedNodes.add(node.getValue().getFile()); + } + for (TreeNode child : node.getChildren()) { + if (child.getValue().getFile().isDirectory()) { + expandedNodes.addAll(getExpandedNodes(child)); + } + } + return expandedNodes; + } + + public static List> getNodes(File rootFile) { + return getNodes(rootFile, 0); + } + + /** + * Get all the tree note at the given root + */ + public static List> getNodes(File rootFile, int initialLevel) { + List> nodes = new ArrayList<>(); + if (rootFile == null) { + return nodes; + } + + TreeNode root = new TreeNode<>( + TreeFile.fromFile(rootFile), initialLevel + ); + root.setExpanded(true); + + File[] children = rootFile.listFiles(); + if (children != null) { + Arrays.sort(children, FILE_FIRST_ORDER); + for (File file : children) { + addNode(root, file, initialLevel + 1); + } + } + nodes.add(root); + return nodes; + } + + private static void addNode(TreeNode node, File file, int level) { + TreeNode childNode = new TreeNode<>( + TreeFile.fromFile(file), level + ); + + if (file.isDirectory()) { + File[] children = file.listFiles(); + if (children != null) { + Arrays.sort(children, FILE_FIRST_ORDER); + for (File child : children) { + addNode(childNode, child, level + 1); + } + } + } + + node.addChild(childNode); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/tree/binder/TreeFileNodeViewBinder.kt b/app/src/main/java/com/tyron/code/ui/file/tree/binder/TreeFileNodeViewBinder.kt new file mode 100644 index 00000000..be0f29f9 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/tree/binder/TreeFileNodeViewBinder.kt @@ -0,0 +1,67 @@ +package com.tyron.code.ui.file.tree.binder + +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import com.tyron.code.R +import com.tyron.ui.treeview.TreeNode +import com.tyron.ui.treeview.base.BaseNodeViewBinder +import com.tyron.code.ui.file.tree.model.TreeFile +import com.tyron.code.util.dp +import com.tyron.code.util.setMargins + +class TreeFileNodeViewBinder( + itemView: View, + private val level: Int, + private val nodeListener: TreeFileNodeListener +): BaseNodeViewBinder(itemView) { + + private lateinit var viewHolder: ViewHolder + + override fun bindView(treeNode: TreeNode) { + viewHolder = ViewHolder(itemView) + + viewHolder.rootView.setMargins( + left = level * 15.dp + ) + + with(viewHolder.arrow) { + setImageResource(R.drawable.ic_baseline_keyboard_arrow_right_24) + rotation = if (treeNode.isExpanded) 90F else 0F + visibility = if (treeNode.isLeaf) View.INVISIBLE else View.VISIBLE + } + + val file = treeNode.content.file + + viewHolder.dirName.text = file.name + + with(viewHolder.icon) { + setImageDrawable(treeNode.content.getIcon(context)) + } + } + + override fun onNodeToggled(treeNode: TreeNode, expand: Boolean) { + viewHolder.arrow.animate() + .rotation(if (expand) 90F else 0F) + .setDuration(150) + .start() + + nodeListener.onNodeToggled(treeNode, expand) + } + + override fun onNodeLongClicked(view: View, treeNode: TreeNode, expanded: Boolean): Boolean { + return nodeListener.onNodeLongClicked(view, treeNode, expanded) + } + + class ViewHolder(val rootView: View) { + val arrow: ImageView = rootView.findViewById(R.id.arrow) + val icon: ImageView = rootView.findViewById(R.id.icon) + val dirName: TextView = rootView.findViewById(R.id.name) + } + + interface TreeFileNodeListener { + fun onNodeToggled(treeNode: TreeNode?, expanded: Boolean) + fun onNodeLongClicked(view: View?, treeNode: TreeNode?, expanded: Boolean): Boolean + } + +} diff --git a/app/src/main/java/com/tyron/code/ui/file/tree/binder/TreeFileNodeViewFactory.kt b/app/src/main/java/com/tyron/code/ui/file/tree/binder/TreeFileNodeViewFactory.kt new file mode 100644 index 00000000..0bc9f382 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/tree/binder/TreeFileNodeViewFactory.kt @@ -0,0 +1,18 @@ +package com.tyron.code.ui.file.tree.binder + +import android.view.View +import com.tyron.code.R +import com.tyron.ui.treeview.base.BaseNodeViewFactory +import com.tyron.code.ui.file.tree.binder.TreeFileNodeViewBinder.TreeFileNodeListener +import com.tyron.code.ui.file.tree.model.TreeFile + +class TreeFileNodeViewFactory( + private var nodeListener: TreeFileNodeListener +): BaseNodeViewFactory() { + + override fun getNodeViewBinder(view: View, level: Int) = + TreeFileNodeViewBinder(view, level, nodeListener) + + override fun getNodeLayoutId(level: Int) = R.layout.file_manager_item + +} diff --git a/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeFile.java b/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeFile.java new file mode 100644 index 00000000..36cecfa8 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeFile.java @@ -0,0 +1,61 @@ +package com.tyron.code.ui.file.tree.model; + +import android.content.Context; +import android.graphics.drawable.Drawable; + +import androidx.annotation.Nullable; +import androidx.appcompat.content.res.AppCompatResources; + +import com.tyron.code.R; + +import java.io.File; +import java.util.Objects; + +public class TreeFile { + + @Nullable + public static TreeFile fromFile(File file) { + if (file == null) { + return null; + } + if (file.isDirectory()) { + return new TreeFolder(file); + } + if (file.getName().endsWith(".java")) { + return new TreeJavaFile(file); + } + return new TreeFile(file); + } + + private final File mFile; + + public TreeFile(File file) { + mFile = file; + } + + public File getFile() { + return mFile; + } + + public Drawable getIcon(Context context) { + return AppCompatResources.getDrawable(context, + R.drawable.round_insert_drive_file_24); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TreeFile treeFile = (TreeFile) o; + return Objects.equals(mFile, treeFile.mFile); + } + + @Override + public int hashCode() { + return Objects.hash(mFile); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeFolder.java b/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeFolder.java new file mode 100644 index 00000000..2151fc28 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeFolder.java @@ -0,0 +1,23 @@ +package com.tyron.code.ui.file.tree.model; + +import android.content.Context; +import android.graphics.drawable.Drawable; + +import androidx.appcompat.content.res.AppCompatResources; + +import com.tyron.code.R; + +import java.io.File; + +public class TreeFolder extends TreeFile { + + public TreeFolder(File file) { + super(file); + } + + @Override + public Drawable getIcon(Context context) { + return AppCompatResources.getDrawable(context, + R.drawable.round_folder_20); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeJavaFile.java b/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeJavaFile.java new file mode 100644 index 00000000..1bfecd1f --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/file/tree/model/TreeJavaFile.java @@ -0,0 +1,22 @@ +package com.tyron.code.ui.file.tree.model; + +import android.content.Context; +import android.graphics.drawable.Drawable; + +import androidx.appcompat.content.res.AppCompatResources; + +import com.tyron.code.R; + +import java.io.File; + +public class TreeJavaFile extends TreeFile { + + public TreeJavaFile(File file) { + super(file); + } + + @Override + public Drawable getIcon(Context context) { + return super.getIcon(context); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/EditorDragListener.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/EditorDragListener.java new file mode 100644 index 00000000..52fa8859 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/EditorDragListener.java @@ -0,0 +1,229 @@ +package com.tyron.code.ui.layoutEditor; + +import android.animation.LayoutTransition; +import android.graphics.Rect; +import android.util.Log; +import android.view.DragEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; + +import com.flipkart.android.proteus.ProteusView; +import com.tyron.code.BuildConfig; +import com.tyron.code.ui.layoutEditor.model.EditorDragState; +import com.tyron.code.ui.layoutEditor.model.EditorShadowView; +import com.tyron.code.ui.layoutEditor.model.ViewPalette; +import com.tyron.layoutpreview.BoundaryDrawingFrameLayout; + +import java.util.Objects; + +public class EditorDragListener implements View.OnDragListener { + + private static final String TAG = EditorDragListener.class.getSimpleName(); + + /** + * Callbacks when views are added to the editor. + * Shadow View is not included + */ + public interface Delegate { + void onAddView(ViewGroup parent, View view); + void onRemoveView(ViewGroup parent, View view); + } + + public interface InflateCallback { + ProteusView inflate(ViewGroup parent, ViewPalette palette); + } + + private final EditorShadowView mEditorShadow; + private Delegate mDelegate; + private InflateCallback mInflateCallback; + + public EditorDragListener(View root) { + mEditorShadow = new EditorShadowView(root.getContext()); + mEditorShadow.setLayoutParams(new ViewGroup.LayoutParams(50, 50)); + mEditorShadow.setBackgroundColor(0xff000000); + } + + public void setInflateCallback(@NonNull InflateCallback inflateCallback) { + mInflateCallback = inflateCallback; + } + + public void setDelegate(@NonNull Delegate delegate) { + mDelegate = delegate; + } + + @Override + public boolean onDrag(View view, DragEvent event) { + + // Only ViewGroups should receive the drag events + // since we can only add views on ViewGroups + if (!(view instanceof ViewGroup)) { + return false; + } + + if (!(event.getLocalState() instanceof EditorDragState)) { + return false; + } + + EditorDragState dragState = (EditorDragState) event.getLocalState(); + + ViewGroup hostView = (ViewGroup) view; + switch (event.getAction()) { + case DragEvent.ACTION_DRAG_LOCATION: + case DragEvent.ACTION_DRAG_ENTERED: + ensureNoParent(mEditorShadow); + if (event.getLocalState() instanceof ProteusView) { + ensureNoParent(((ProteusView) event.getLocalState()).getAsView()); + } + addView(hostView, mEditorShadow, event); + break; + case DragEvent.ACTION_DRAG_EXITED: + ensureNoParent(mEditorShadow); + break; + case DragEvent.ACTION_DROP: + ensureNoParent(mEditorShadow); + + if (dragState.isExistingView()) { + + if (getParentOfType(hostView, BoundaryDrawingFrameLayout.class) == null) { + ensureNoParent(Objects.requireNonNull(dragState.getView())); + return true; + } + + if (dragState.getView() instanceof ProteusView) { + boolean added = addProteusView(hostView, (ProteusView) dragState.getView(), event); + + // the view cannot be added, add it back to its previous parent + if (!added) { + ensureNoParent(dragState.getView()); + dragState.getParent().addView(dragState.getView(), dragState.getIndex()); + + // inform the main editor + if (mDelegate != null) { + mDelegate.onAddView(dragState.getParent(), dragState.getView()); + } + } + } + } else { + addPalette(hostView, dragState.getPalette(), event); + } + break; + } + return true; + } + + private boolean addProteusView(ViewGroup parent, ProteusView view, DragEvent event) { + return addView(parent, view.getAsView(), event); + } + + private void addPalette(ViewGroup parent, ViewPalette palette, DragEvent event) { + if (mInflateCallback != null) { + ProteusView inflated = mInflateCallback.inflate(parent, palette); + addView(parent, inflated.getAsView(), event); + } + } + + private boolean addView(ViewGroup parent, View child, DragEvent event) { + try { + ensureNoParent(child); + int index = parent.getChildCount(); + if (parent instanceof LinearLayout) { + if (((LinearLayout) parent).getOrientation() == LinearLayout.VERTICAL) { + index = getVerticalIndexForEvent(parent, event); + } else { + index = getHorizontalIndexForEvent(parent, event); + } + } + + if (mEditorShadow.equals(child)) { + LayoutTransition transition = parent.getLayoutTransition(); + parent.setLayoutTransition(null); + parent.addView(child, index); + parent.setLayoutTransition(transition); + } else { + parent.addView(child, index); + if (mDelegate != null) { + mDelegate.onAddView(parent, child); + } + } + return true; + } catch (Throwable e) { + return false; + } + } + + private void ensureNoParent(View view) { + ViewGroup parent = (ViewGroup) view.getParent(); + if (parent != null) { + if (view.equals(mEditorShadow)) { + LayoutTransition transition = parent.getLayoutTransition(); + parent.setLayoutTransition(null); + parent.removeView(view); + parent.setLayoutTransition(transition); + } else { + parent.removeView(view); + if (mDelegate != null) { + mDelegate.onRemoveView(parent, view); + } + } + } + } + + private int getHorizontalIndexForEvent(ViewGroup parent, DragEvent event) { + float dropX = event.getX(); + int index = 0; + for (int i = 0; i < parent.getChildCount(); i++) { + View child = parent.getChildAt(i); + + if (mEditorShadow.equals(child)) { + // do not count the shadow as a child view + continue; + } + + if (getMiddle(child.getLeft(), child.getRight()) < dropX) { + index++; + } + } + return index; + } + + private int getVerticalIndexForEvent(ViewGroup parent, DragEvent event) { + float dropY = event.getY(); + int index = 0; + for (int i = 0; i < parent.getChildCount(); i++) { + View child = parent.getChildAt(i); + + if (mEditorShadow.equals(child)) { + // dont count the shadow as a child view + continue; + } + + if (getMiddle(child.getTop(), child.getBottom()) < dropY) { + index++; + } + } + return index; + } + + private int getMiddle(int lower, int higher) { + int length = higher - lower; + int middle = length / 2; + return lower + middle; + } + + private View getParentOfType(View view, Class type) { + ViewParent current = view.getParent(); + while (current != null) { + if (current.getClass().isAssignableFrom(type)) { + return (View) current; + } + + current = current.getParent(); + } + + return null; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/LayoutEditorFragment.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/LayoutEditorFragment.java new file mode 100644 index 00000000..25d9056e --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/LayoutEditorFragment.java @@ -0,0 +1,525 @@ +package com.tyron.code.ui.layoutEditor; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.LayoutTransition; +import android.app.Dialog; +import android.content.ClipData; +import android.content.Context; +import android.graphics.Point; +import android.os.Bundle; +import android.util.Log; +import android.view.Display; +import android.view.DragEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.view.ContextThemeWrapper; +import androidx.core.view.ViewCompat; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.transition.TransitionManager; + +import com.flipkart.android.proteus.ProteusContext; +import com.flipkart.android.proteus.ProteusView; +import com.flipkart.android.proteus.ViewTypeParser; +import com.flipkart.android.proteus.exceptions.ProteusInflateException; +import com.flipkart.android.proteus.toolbox.Attributes; +import com.flipkart.android.proteus.toolbox.ProteusHelper; +import com.flipkart.android.proteus.value.Dimension; +import com.flipkart.android.proteus.value.Layout; +import com.flipkart.android.proteus.value.ObjectValue; +import com.flipkart.android.proteus.value.Primitive; +import com.flipkart.android.proteus.value.Value; +import com.flipkart.android.proteus.view.UnknownViewGroup; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.common.collect.ImmutableMap; +import com.tyron.code.ui.layoutEditor.model.EditorDragState; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.R; +import com.tyron.code.ui.layoutEditor.attributeEditor.AttributeEditorDialogFragment; +import com.tyron.code.ui.layoutEditor.model.ViewPalette; +import com.tyron.completion.java.provider.CompletionEngine; +import com.tyron.layoutpreview.BoundaryDrawingFrameLayout; +import com.tyron.layoutpreview.convert.LayoutToXmlConverter; +import com.tyron.layoutpreview.inflate.PreviewLayoutInflater; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import kotlin.Pair; + +public class LayoutEditorFragment extends Fragment implements ProjectManager.OnProjectOpenListener { + + public static final String KEY_SAVE = "KEY_SAVE"; + + /** + * Creates a new LayoutEditorFragment instance for a layout xml file. + * Make sure that the file exists and is a valid layout file and that + * {@code ProjectManager#getCurrentProject} is not null + */ + public static LayoutEditorFragment newInstance(File file) { + Bundle args = new Bundle(); + args.putSerializable("file", file); + LayoutEditorFragment fragment = new LayoutEditorFragment(); + fragment.setArguments(args); + return fragment; + } + + private final ExecutorService mService = Executors.newSingleThreadExecutor(); + private LayoutEditorViewModel mEditorViewModel; + + private File mCurrentFile; + private PreviewLayoutInflater mInflater; + private BoundaryDrawingFrameLayout mEditorRoot; + private EditorDragListener mDragListener; + + private LinearLayout mLoadingLayout; + private TextView mLoadingText; + + private boolean isDumb; + + private final View.OnLongClickListener mOnLongClickListener = v -> { + ClipData clipData = ClipData.newPlainText("", ""); + View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(v); + EditorDragState state = EditorDragState.fromView(v); + ViewCompat.startDragAndDrop(v, clipData, shadowBuilder, state, 0); + return true; + }; + + private final View.OnClickListener mOnClickListener = v -> { + if (v instanceof ProteusView) { + ProteusView view = (ProteusView) v; + ProteusView.Manager manager = view.getViewManager(); + ProteusContext context = manager.getContext(); + Layout layout = manager.getLayout(); + String tag = layout.type; + String parentTag = view.getAsView().getParent() instanceof ProteusView + ? ((ProteusView) view.getAsView().getParent()).getViewManager().getLayout().type + : ""; + ArrayList> attributes = new ArrayList<>(); + for (Layout.Attribute attribute : + layout.getAttributes()) { + String name = ProteusHelper.getAttributeName(view, attribute.id); + attributes.add(new Pair<>(name, attribute.value.toString())); + } + if (layout.extras != null) { + layout.extras.entrySet().forEach(entry -> { + ViewTypeParser parser; + int id = ProteusHelper.getAttributeId(view, entry.getKey()); + if (id == -1) { + parser = context.getParser("android.view.View"); + if (parser != null) { + id = parser.getAttributeId(entry.getKey()); + } + } + if (id != -1) { + String name = ProteusHelper.getAttributeName(view, id, true); + attributes.add(new Pair<>(name, entry.getValue().toString())); + } + }); + } + + ArrayList> availableAttributes = new ArrayList<>(); + manager.getAvailableAttributes().forEach((key, value) -> { + availableAttributes.add(new Pair<>(key, "")); + }); + + AttributeEditorDialogFragment.newInstance(tag, parentTag, availableAttributes, attributes) + .show(getChildFragmentManager(), null); + + getChildFragmentManager().setFragmentResultListener( + AttributeEditorDialogFragment.KEY_ATTRIBUTE_CHANGED, + getViewLifecycleOwner(), + (requestKey, result) -> { + String key = result.getString("key", ""); + String value = result.getString("value", ""); + if (value.isEmpty()) { + getChildFragmentManager().setFragmentResult(AttributeEditorDialogFragment.KEY_ATTRIBUTE_REMOVED, result); + manager.removeAttribute(key); + } else { + manager.updateAttribute(key, value); + } + getChildFragmentManager() + .clearFragmentResult(AttributeEditorDialogFragment.KEY_ATTRIBUTE_CHANGED); + }); + } + }; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mCurrentFile = (File) requireArguments().getSerializable("file"); + isDumb = ProjectManager.getInstance().getCurrentProject() == null || + CompletionEngine.isIndexing(); + mEditorViewModel = new ViewModelProvider(this) + .get(LayoutEditorViewModel.class); + requireActivity().getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + new MaterialAlertDialogBuilder(requireContext()) + .setTitle("Save to xml") + .setMessage("Do you want to save the layout?") + .setPositiveButton(android.R.string.yes, (d, w) -> { + String converted = convertLayoutToXml(); + if (converted == null) { + new MaterialAlertDialogBuilder(requireContext()) + .setTitle("Error") + .setMessage("An unknown error has occurred during layout conversion") + .show(); + } else { + Bundle args = new Bundle(); + args.putString("text", converted); + getParentFragmentManager().setFragmentResult(KEY_SAVE, + args); + } + getParentFragmentManager().popBackStack(); + }) + .setNegativeButton(android.R.string.no, (d, w) -> { + getParentFragmentManager().popBackStack(); + }) + .show(); + } + }); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.layout_editor_fragment, container, false); + + mLoadingLayout = root.findViewById(R.id.loading_root); + mLoadingText = root.findViewById(R.id.loading_text); + + mEditorRoot = root.findViewById(R.id.editor_root); + mDragListener = new EditorDragListener(mEditorRoot); + mDragListener.setInflateCallback((parent, palette) -> { + Layout layout = new Layout(palette.getClassName()); + ProteusView inflated = mInflater.getContext() + .getInflater() + .inflate(layout, new ObjectValue(), parent, 0); + palette.getDefaultValues().forEach((key, value) -> + inflated.getViewManager().updateAttribute(key, value)); + return inflated; + }); + mDragListener.setDelegate(new EditorDragListener.Delegate() { + @Override + public void onAddView(ViewGroup parent, View view) { + if (view instanceof ViewGroup) { + setDragListeners(((ViewGroup) view)); + } + setClickListeners(view); + mEditorRoot.postDelayed(() -> mEditorRoot.invalidate(), 300); + + if (parent instanceof ProteusView && view instanceof ProteusView) { + ProteusView proteusParent = (ProteusView) parent; + ProteusView proteusChild = (ProteusView) view; + ProteusHelper.addChildToLayout(proteusParent, proteusChild); + } + } + + @Override + public void onRemoveView(ViewGroup parent, View view) { + if (parent instanceof ProteusView && view instanceof ProteusView) { + ProteusView proteusParent = (ProteusView) parent; + ProteusView proteusChild = (ProteusView) view; + ProteusHelper.removeChildFromLayout(proteusParent, proteusChild); + } + } + }); + return root; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + mEditorViewModel.setPalettes(populatePalettes()); + if (isDumb) { + ProjectManager.getInstance().addOnProjectOpenListener(this); + setLoadingText("Indexing"); + } else { + createInflater(); + } + + view.setOnDragListener((v, event) -> { + if (!(event.getLocalState() instanceof EditorDragState)) { + return false; + } + + if (event.getAction() != DragEvent.ACTION_DROP) { + return true; + } + + EditorDragState state = (EditorDragState) event.getLocalState(); + if (state.isExistingView()) { + View dragged = state.getView(); + ViewGroup parent = (ViewGroup) dragged.getParent(); + if (parent != null) { + parent.removeView(dragged); + + if (parent instanceof ProteusView && dragged instanceof ProteusView) { + ProteusHelper.removeChildFromLayout(((ProteusView) parent), + ((ProteusView) dragged)); + } + return true; + } + } + return false; + }); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + ProjectManager.getInstance().removeOnProjectOpenListener(this); + } + + private Dialog exit(String title, String message) { + AlertDialog show = + new MaterialAlertDialogBuilder(requireActivity()) + .setTitle(title) + .setMessage(message) + .setPositiveButton(android.R.string.ok, null).show(); + getParentFragmentManager().popBackStack(); + return show; + } + + private void createInflater() { + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + if (currentProject == null) { + exit(getString(R.string.error), "No project opened."); + return; + } + Module module = currentProject.getModule(mCurrentFile); + if (!(module instanceof AndroidModule)) { + exit(getString(R.string.error), "Layout preview is only for android projects."); + return; + } + setLoadingText("Parsing xml files"); + + // need to wrap the context to a default theme so + // material widgets wont use CodeAssist's theme + ContextThemeWrapper wrapper = new ContextThemeWrapper(requireContext(), R.style.Theme_MaterialComponents_DayNight); + mInflater = new PreviewLayoutInflater(wrapper, (AndroidModule) module); + mInflater.parseResources(mService).whenComplete((inflater, exception) -> + requireActivity().runOnUiThread(() -> { + if (inflater == null) { + exit(getString(R.string.error), + "Unable to inflate layout: \n" + Log.getStackTraceString(exception)); + } else { + afterParse(inflater); + } + })); + } + + private void afterParse(PreviewLayoutInflater inflater) { + mInflater = inflater; + setLoadingText("Inflating xml"); + inflateFile(mCurrentFile); + } + + private void inflateFile(File file) { + Optional optionalView; + + try { + optionalView = mInflater.inflateLayout(file.getName() + .replace(".xml", "")); + } catch (ProteusInflateException e) { + optionalView = Optional.empty(); + } + setLoadingText(null); + + if (optionalView.isPresent()) { + mEditorRoot.removeAllViews(); + mEditorRoot.addView(optionalView.get().getAsView()); + setDragListeners(mEditorRoot); + setClickListeners(mEditorRoot); + + requireActivity().runOnUiThread(() -> resizeLayoutEditor(mEditorRoot)); + } else { + exit(getString(R.string.error), "Unable to inflate layout."); + } + } + + private void resizeLayoutEditor(View root) { + final Point point = new Point(); + ((WindowManager)requireActivity().getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay().getRealSize(point); + final int screenWidth = point.x; + final int screenHeight = point.y; + + final float xScale = (float) root.getWidth() / (float) screenWidth; + final float yScale = (float) root.getHeight() / (float) screenHeight; + final float minScale = Math.min(xScale, yScale); + + // keep the original layout params + ViewGroup.LayoutParams layoutParams = root.getLayoutParams(); + layoutParams.width = screenWidth; + layoutParams.height = screenHeight; + + root.setScaleX(minScale); + root.setScaleY(minScale); + + root.postDelayed(() -> { + final float xCorrection = (screenWidth - (screenWidth * minScale)) / 2; + root.setTranslationX(-xCorrection); + final float yCorrection = (screenHeight - (screenHeight * minScale)) / 2; + root.setTranslationY(-yCorrection); + }, 500); + } + + + private void setDragListeners(ViewGroup viewGroup) { + if (viewGroup instanceof ProteusView) { + ((ProteusView) viewGroup).getViewManager().setOnDragListener(mDragListener); + } + + try { + LayoutTransition transition = new LayoutTransition(); + transition.enableTransitionType(LayoutTransition.CHANGING); + transition.disableTransitionType(LayoutTransition.DISAPPEARING); + transition.setDuration(180L); + viewGroup.setLayoutTransition(new LayoutTransition()); + } catch (Throwable e) { + // ignored, some ViewGroup's may not allow layout transitions + } + for (int i = 0; i < viewGroup.getChildCount(); i++) { + View child = viewGroup.getChildAt(i); + if (child instanceof ViewGroup) { + setDragListeners(((ViewGroup) child)); + } + } + } + + private void setClickListeners(View view) { + if (view instanceof ProteusView) { + ((ProteusView) view).getViewManager().setOnClickListener(mOnClickListener); + ((ProteusView) view).getViewManager().setOnLongClickListener(mOnLongClickListener); + } + if (view instanceof ViewGroup && !(view instanceof UnknownViewGroup)) { + ViewGroup parent = (ViewGroup) view; + for (int i = 0; i < parent.getChildCount(); i++) { + View child = parent.getChildAt(i); + setClickListeners(child); + } + } + } + + + /** + * Show a loading text to the editor + * + * @param message The message to be displayed, pass null to remove the loading text + */ + private void setLoadingText(@Nullable String message) { + TransitionManager.beginDelayedTransition((ViewGroup) mLoadingLayout.getParent()); + if (null == message) { + mLoadingLayout.setVisibility(View.GONE); + } else { + mLoadingLayout.setVisibility(View.VISIBLE); + mLoadingText.setText(message); + } + } + + private List populatePalettes() { + List palettes = new ArrayList<>(); + + palettes.add(createPalette("android.widget.LinearLayout", R.drawable.ic_baseline_vertical_24, + ImmutableMap.of(Attributes.View.MinWidth, Dimension.valueOf("50dp"), Attributes.View.MinHeight, Dimension.valueOf("25dp")))); + palettes.add(createPalette("android.widget.FrameLayout", R.drawable.ic_baseline_frame_24, + ImmutableMap.of(Attributes.View.MinWidth, Dimension.valueOf("50dp"), Attributes.View.MinHeight, Dimension.valueOf("25dp")))); + palettes.add(createPalette("android.widget.ScrollView", R.drawable.ic_baseline_format_line_spacing_24, + ImmutableMap.of(Attributes.View.MinWidth, Dimension.valueOf("50dp"), Attributes.View.MinHeight, Dimension.valueOf("25dp")))); + palettes.add(createPalette("android.widget.HorizontalScrollView", R.drawable.ic_baseline_format_line_spacing_24, + ImmutableMap.of(Attributes.View.MinWidth, Dimension.valueOf("50dp"), Attributes.View.MinHeight, Dimension.valueOf("25dp")))); + palettes.add(createPalette("androidx.cardview.widget.CardView", R.drawable.ic_baseline_style_24, + ImmutableMap.of(Attributes.View.MinWidth, Dimension.valueOf("50dp"), Attributes.View.MinHeight, Dimension.valueOf("25dp")))); + + palettes.add(createPalette("Button", + R.drawable.ic_baseline_crop_16_9_24, + ImmutableMap.of(Attributes.TextView.Text, new Primitive("Button")))); + palettes.add(createPalette("TextView", + R.drawable.ic_baseline_text_fields_24, + ImmutableMap.of(Attributes.TextView.Text, new Primitive("TextView")))); + palettes.add(createPalette("android.widget.EditText", + R.drawable.ic_baseline_edit_24, + ImmutableMap.of(Attributes.TextView.Hint, new Primitive("EditText")))); + palettes.add(createPalette("android.widget.CheckBox", + R.drawable.ic_baseline_check_box_24, + ImmutableMap.of(Attributes.TextView.Text, new Primitive("CheckBox")))); + palettes.add(createPalette("android.widget.Switch", + R.drawable.ic_baseline_edit_attributes_24, + ImmutableMap.of(Attributes.TextView.Text, new Primitive("Switch")))); + palettes.add(createPalette("android.widget.SeekBar", R.drawable.ic_baseline_swipe_right_alt_24)); + + return palettes; + } + + private ViewPalette createPalette(@NonNull String className, @DrawableRes int icon) { + return createPalette(className, icon, Collections.emptyMap()); + } + + private ViewPalette createPalette(@NonNull String className, + @DrawableRes int icon, + Map attributes) { + String name = className.substring(className.lastIndexOf('.') + 1); + ViewPalette.Builder builder = ViewPalette.builder() + .setClassName(className) + .setName(name) + .setIcon(icon); + builder.addDefaultValue(Attributes.View.Width, Dimension.valueOf("wrap_content")); + builder.addDefaultValue(Attributes.View.Height, Dimension.valueOf("wrap_content")); + attributes.forEach(builder::addDefaultValue); + return builder.build(); + } + + @Override + public void onProjectOpen(Project module) { + if (getActivity() == null) { + return; + } + if (isDumb) { + isDumb = false; + requireActivity().runOnUiThread(this::createInflater); + } + } + + private String convertLayoutToXml() { + if (mInflater != null) { + LayoutToXmlConverter converter = + new LayoutToXmlConverter(mInflater.getContext()); + ProteusView view = (ProteusView) mEditorRoot.getChildAt(0); + if (view != null) { + Layout layout = view.getViewManager().getLayout(); + try { + return converter.convert(layout.getAsLayout()); + } catch (Exception e) { + return null; + } + } + } + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/LayoutEditorViewModel.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/LayoutEditorViewModel.java new file mode 100644 index 00000000..8b52f285 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/LayoutEditorViewModel.java @@ -0,0 +1,23 @@ +package com.tyron.code.ui.layoutEditor; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.tyron.code.ui.layoutEditor.model.ViewPalette; + +import java.util.ArrayList; +import java.util.List; + +public class LayoutEditorViewModel extends ViewModel { + + private MutableLiveData> mPalettes = new MutableLiveData<>(new ArrayList<>()); + + public LiveData> getPalettes() { + return mPalettes; + } + + public void setPalettes(List palettes) { + mPalettes.setValue(palettes); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/ViewPaletteAdapter.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/ViewPaletteAdapter.java new file mode 100644 index 00000000..463e2e56 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/ViewPaletteAdapter.java @@ -0,0 +1,104 @@ +package com.tyron.code.ui.layoutEditor; + +import android.content.ClipData; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.core.view.ViewCompat; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import com.tyron.code.R; +import com.tyron.code.ui.layoutEditor.model.EditorDragState; +import com.tyron.code.ui.layoutEditor.model.ViewPalette; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class ViewPaletteAdapter extends RecyclerView.Adapter { + + private final List mViewPaletteList; + + public ViewPaletteAdapter() { + mViewPaletteList = new ArrayList<>(); + } + + public void submitList(List newList) { + DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return mViewPaletteList.size(); + } + + @Override + public int getNewListSize() { + return newList.size(); + } + + @Override + public boolean areItemsTheSame(int old, int newPos) { + return Objects.equals(mViewPaletteList.get(old), newList.get(newPos)); + } + + @Override + public boolean areContentsTheSame(int old, int newPos) { + return Objects.equals(mViewPaletteList.get(old), newList.get(newPos)); + } + }); + mViewPaletteList.clear(); + mViewPaletteList.addAll(newList); + diffResult.dispatchUpdatesTo(this); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + FrameLayout root = new FrameLayout(parent.getContext()); + return new ViewHolder(root); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.bind(mViewPaletteList.get(position)); + } + + @Override + public int getItemCount() { + return mViewPaletteList.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + + private final View mInflatedView; + private final ImageView mIcon; + private final TextView mName; + + public ViewHolder(FrameLayout view) { + super(view); + + mInflatedView = LayoutInflater.from(view.getContext()) + .inflate(R.layout.editor_palette_item, view); + mIcon = mInflatedView.findViewById(R.id.icon); + mName = mInflatedView.findViewById(R.id.name); + } + + public void bind(ViewPalette item) { + mIcon.setImageResource(item.getIcon()); + mName.setText(item.getName()); + + mInflatedView.setOnLongClickListener(v -> { + ClipData clipData = ClipData.newPlainText("", ""); + View.DragShadowBuilder dragShadowBuilder = new View.DragShadowBuilder(v); + EditorDragState state = EditorDragState.fromPalette(item); + ViewCompat.startDragAndDrop(v, clipData, dragShadowBuilder, state, 0); + return true; + }); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/ViewPaletteFragment.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/ViewPaletteFragment.java new file mode 100644 index 00000000..9739b6e1 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/ViewPaletteFragment.java @@ -0,0 +1,49 @@ +package com.tyron.code.ui.layoutEditor; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.tyron.code.R; + +public class ViewPaletteFragment extends Fragment { + + private LayoutEditorViewModel mViewModel; + + private ViewPaletteAdapter mAdapter; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mViewModel = new ViewModelProvider(requireParentFragment()).get(LayoutEditorViewModel.class); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.view_palette_fragment, container, false); + + mAdapter = new ViewPaletteAdapter(); + RecyclerView listView = root.findViewById(R.id.palette_recyclerview); + listView.setLayoutManager(new LinearLayoutManager(requireContext())); + listView.setAdapter(mAdapter); + + return root; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + mViewModel.getPalettes().observe(getViewLifecycleOwner(), mAdapter::submitList); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/attributeEditor/AttributeEditorAdapter.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/attributeEditor/AttributeEditorAdapter.java new file mode 100644 index 00000000..1aa69baf --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/attributeEditor/AttributeEditorAdapter.java @@ -0,0 +1,117 @@ +package com.tyron.code.ui.layoutEditor.attributeEditor; + +import kotlin.Pair; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import com.tyron.code.R; + +import java.util.ArrayList; +import java.util.List; + +public class AttributeEditorAdapter extends RecyclerView.Adapter { + + public interface OnItemClickListener { + void onItemClick(int pos, Pair attribute); + } + + private List> mAttributes = new ArrayList<>(); + + public AttributeEditorAdapter() { + + } + + private OnItemClickListener mItemClickListener; + + public List> getAttributes() { + return new ArrayList<>(mAttributes); + } + public void setItemClickListener(OnItemClickListener listener) { + mItemClickListener = listener; + } + + public void submitList(List> newList) { + DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return mAttributes.size(); + } + + @Override + public int getNewListSize() { + return newList.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return mAttributes.get(oldItemPosition).getFirst() + .equals(newList.get(newItemPosition).getFirst()); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + return mAttributes.get(oldItemPosition).getSecond() + .equals(newList.get(newItemPosition).getSecond()); + } + }); + mAttributes.clear(); + mAttributes.addAll(newList); + result.dispatchUpdatesTo(this); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + ViewHolder viewHolder = new ViewHolder(new FrameLayout(parent.getContext())); + + viewHolder.itemView.findViewById(R.id.item_attribute).setOnClickListener(v -> { + if (mItemClickListener != null) { + int position = viewHolder.getBindingAdapterPosition(); + if (position != RecyclerView.NO_POSITION) { + mItemClickListener.onItemClick(position, mAttributes.get(position)); + } + } + }); + return viewHolder; + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.bind(mAttributes.get(position)); + } + + @Override + public int getItemCount() { + return mAttributes.size(); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + + private final TextView mKeyText; + private final TextView mValueText; + + public ViewHolder(FrameLayout frameLayout) { + super(frameLayout); + frameLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + View root = LayoutInflater.from(frameLayout.getContext()) + .inflate(R.layout.attribute_editor_item, frameLayout); + + mKeyText = root.findViewById(R.id.textview_key); + mValueText = root.findViewById(R.id.textview_value); + } + + public void bind(Pair item) { + mKeyText.setText(item.getFirst()); + mValueText.setText(item.getSecond()); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/attributeEditor/AttributeEditorDialogFragment.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/attributeEditor/AttributeEditorDialogFragment.java new file mode 100644 index 00000000..6076611c --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/attributeEditor/AttributeEditorDialogFragment.java @@ -0,0 +1,229 @@ +package com.tyron.code.ui.layoutEditor.attributeEditor; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.textfield.MaterialAutoCompleteTextView; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.R; +import com.tyron.code.ui.layoutEditor.dom.FakeDomElement; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.completion.index.CompilerService; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.xml.completion.repository.api.AttrResourceValue; +import com.tyron.xml.completion.repository.api.ResourceNamespace; +import com.tyron.completion.xml.util.AttributeProcessingUtil; +import com.tyron.completion.xml.XmlIndexProvider; +import com.tyron.completion.xml.XmlRepository; + +import org.eclipse.lemminx.commons.TextDocument; +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMElement; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import kotlin.Pair; + +public class AttributeEditorDialogFragment extends BottomSheetDialogFragment { + + public static final String KEY_ATTRIBUTE_CHANGED = "ATTRIBUTE_CHANGED"; + public static final String KEY_ATTRIBUTE_REMOVED = "ATTRIBUTE_REMOVED"; + + public static AttributeEditorDialogFragment newInstance(String tag, String parentTag, ArrayList> availableAttributes, ArrayList> attributes) { + Bundle args = new Bundle(); + args.putSerializable("attributes", attributes); + args.putSerializable("availableAttributes", availableAttributes); + args.putString("parentTag", parentTag); + args.putString("tag", tag); + AttributeEditorDialogFragment fragment = new AttributeEditorDialogFragment(); + fragment.setArguments(args); + return fragment; + } + + private AttributeEditorAdapter mAdapter; + private ArrayList> mAvailableAttributes; + private ArrayList> mAttributes; + private String mTag; + private String mParentTag; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + //noinspection unchecked + mAttributes = (ArrayList>) requireArguments().getSerializable( + "attributes"); + if (mAttributes == null) { + mAttributes = new ArrayList<>(); + } + + //noinspection unchecked + mAvailableAttributes = + (ArrayList>) requireArguments().getSerializable( + "availableAttributes"); + if (mAvailableAttributes == null) { + mAvailableAttributes = new ArrayList<>(); + } + + mTag = requireArguments().getString("tag", ""); + mParentTag = requireArguments().getString("parentTag", ""); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.attribute_editor_dialog_fragment, container, false); + + mAdapter = new AttributeEditorAdapter(); + RecyclerView recyclerView = root.findViewById(R.id.recyclerView); + recyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); + recyclerView.setAdapter(mAdapter); + + return root; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + if(getDialog() != null){ + getDialog().getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + } + + mAdapter.setItemClickListener(this::onAttributeItemClick); + mAdapter.submitList(mAttributes); + + LinearLayout linearAdd = view.findViewById(R.id.linear_add); + linearAdd.setOnClickListener(v -> { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext()); + builder.setTitle("Available Attributes"); + + final ArrayList items = new ArrayList<>(); + final ArrayList> filteredAttributes = new ArrayList<>(); + + Loop1: for (int i = 0; i < mAvailableAttributes.size(); i++) { + for(Pair pair : mAttributes){ + if(pair.getFirst().equals(mAvailableAttributes.get(i).getFirst())) + continue Loop1; + } + filteredAttributes.add(mAvailableAttributes.get(i)); + items.add(mAvailableAttributes.get(i).getFirst()); + } + boolean[] selectedAttrs = new boolean[filteredAttributes.size()]; + builder.setMultiChoiceItems(items.toArray(new CharSequence[0]), selectedAttrs, (d, which, isSelected) -> { + selectedAttrs[which] = isSelected; + }); + builder.setPositiveButton("Add", ((dialogInterface, which) -> { + for(int i = 0; i < selectedAttrs.length; i++){ + if(selectedAttrs[i]){ + mAttributes.add(filteredAttributes.get(i)); + } + } + mAdapter.submitList(mAttributes); + })); + builder.show(); + }); + + getParentFragmentManager().setFragmentResultListener(KEY_ATTRIBUTE_REMOVED, getViewLifecycleOwner(), ((requestKey, result) -> { + String key = result.getString("key"); + int index = -1; + for (Pair pair : mAttributes) { + if (pair.getFirst().equals(key)) { + index = mAttributes.indexOf(pair); + } + } + if (index != -1) { + mAttributes.remove(index); + mAdapter.submitList(mAttributes); + } + })); + } + + private void onAttributeItemClick(int pos, Pair attribute) { + View v = LayoutInflater.from(requireContext()).inflate(R.layout.attribute_editor_input, null); + MaterialAutoCompleteTextView editText = v.findViewById(R.id.value); + XmlRepository xmlRepository = getXmlRepository(); + + if (xmlRepository != null) { + FakeDomElement fakeDomElement = new FakeDomElement(-1, -1); + fakeDomElement.setTagName(mTag); + + FakeDomElement fakeParent = new FakeDomElement(-1, -1); + fakeParent.setTagName(mParentTag); + fakeDomElement.setParent(fakeParent); + + String attributeName = attribute.getFirst(); + if (attributeName.contains(":")) { + // strip the namespace prefix + attributeName = attributeName.substring(attributeName.indexOf(':') + 1); + } + + AttrResourceValue attr = + AttributeProcessingUtil.getLayoutAttributeFromNode( + xmlRepository.getRepository(), fakeDomElement, + attributeName, + ResourceNamespace.RES_AUTO); + + List values = new ArrayList<>(); + if (attr != null) { + values.addAll(attr.getAttributeValues().keySet()); + } + + ArrayAdapter adapter = new ArrayAdapter<>(requireContext(), + android.R.layout.simple_list_item_1, values); + + ProgressManager.getInstance().runLater(() -> { + editText.setThreshold(1); + editText.showDropDown(); + editText.setAdapter(adapter); + }, 300); + } + + editText.setText(attribute.getSecond(), false); + new MaterialAlertDialogBuilder(requireContext()).setTitle(attribute.getFirst()).setView(v).setPositiveButton("apply", (d, w) -> { + mAttributes.set(pos, new Pair<>(attribute.getFirst(), editText.getText().toString())); + mAdapter.submitList(mAttributes); + + Bundle bundle = new Bundle(); + bundle.putString("key", attribute.getFirst()); + bundle.putString("value", editText.getText().toString()); + getParentFragmentManager().setFragmentResult(KEY_ATTRIBUTE_CHANGED, bundle); + }).show(); + } + + @Nullable + private XmlRepository getXmlRepository() { + ProjectManager projectManager = ProjectManager.getInstance(); + Project currentProject = projectManager.getCurrentProject(); + if (currentProject == null) { + return null; + } + Module mainModule = currentProject.getMainModule(); + if (!(mainModule instanceof AndroidModule)) { + return null; + } + + XmlIndexProvider index = CompilerService.getInstance().getIndex(XmlIndexProvider.KEY); + XmlRepository xmlRepository = index.get(currentProject, mainModule); + try { + xmlRepository.initialize((AndroidModule) mainModule); + } catch (IOException e) { + // ignored + } + return xmlRepository; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/dom/FakeDomElement.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/dom/FakeDomElement.java new file mode 100644 index 00000000..7b8457d1 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/dom/FakeDomElement.java @@ -0,0 +1,42 @@ +package com.tyron.code.ui.layoutEditor.dom; + +import org.eclipse.lemminx.dom.DOMElement; +import org.eclipse.lemminx.dom.DOMNode; + +public class FakeDomElement extends DOMElement { + + private DOMElement parent; + + private String tagName; + + public FakeDomElement(int start, int end) { + super(start, end); + } + + @Override + public String getTagName() { + return tagName; + } + + public void setTagName(String tagName) { + this.tagName = tagName; + } + + public DOMElement getParent() { + return parent; + } + + @Override + public DOMElement getParentElement() { + return getParent(); + } + + @Override + public DOMNode getParentNode() { + return getParent(); + } + + public void setParent(DOMElement parent) { + this.parent = parent; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/model/EditorDragState.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/model/EditorDragState.java new file mode 100644 index 00000000..d2df97bf --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/model/EditorDragState.java @@ -0,0 +1,89 @@ +package com.tyron.code.ui.layoutEditor.model; + +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.Nullable; + +/** + * Information passed in an editor drag + */ +public class EditorDragState { + + public static EditorDragState fromView(View view) { + ViewGroup parent = (ViewGroup) view.getParent(); + return new EditorDragState(view, parent); + } + + public static EditorDragState fromPalette(ViewPalette palette) { + return new EditorDragState(palette); + } + + private final View mView; + private final ViewGroup mParent; + private final int mIndex; + private final boolean mIsExistingView; + private final ViewPalette mPalette; + + private EditorDragState(View view, ViewGroup parent) { + mView = view; + mParent = parent; + mIndex = parent.indexOfChild(view); + mIsExistingView = true; + mPalette = null; + } + + private EditorDragState(ViewPalette palette) { + mView = null; + mParent = null; + mIsExistingView = false; + mIndex = -1; + mPalette = palette; + } + + /** + * @return The index of this child to its parent prior to being removed + */ + public int getIndex() { + return mIndex; + } + + /** + * @return The parent of this view before the drag has been initiated. + * Returns null if {@link #isExistingView()} returned false. + */ + public ViewGroup getParent() { + if (!isExistingView()) { + return null; + } + return mParent; + } + + /** + * @return whether the view that is currently being dragged is already in the editor before + * e.g. when an exiting view is dragged to another view. + */ + public boolean isExistingView() { + return mIsExistingView; + } + + /** + * @return The view palette associated with the drag event, returns null + * if {@link #isExistingView()} returned true. + */ + public ViewPalette getPalette() { + return mPalette; + } + + /** + * @return the current view that is being dragged, may return null if + * {@link #isExistingView()} returned false. + */ + @Nullable + public View getView() { + if (!isExistingView()) { + return null; + } + return mView; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/model/EditorShadowView.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/model/EditorShadowView.java new file mode 100644 index 00000000..17a3c9d3 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/model/EditorShadowView.java @@ -0,0 +1,13 @@ +package com.tyron.code.ui.layoutEditor.model; + +import android.content.Context; +import android.view.View; + +/** + * View that represents the shadow visible on where the palette view will be placed + */ +public class EditorShadowView extends View { + public EditorShadowView(Context context) { + super(context); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/layoutEditor/model/ViewPalette.java b/app/src/main/java/com/tyron/code/ui/layoutEditor/model/ViewPalette.java new file mode 100644 index 00000000..4a33f9db --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/layoutEditor/model/ViewPalette.java @@ -0,0 +1,105 @@ +package com.tyron.code.ui.layoutEditor.model; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; + +import com.flipkart.android.proteus.value.Value; +import com.google.errorprone.annotations.Immutable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Represents an item that can be drag and dropped to the editor + */ +@Immutable +public class ViewPalette { + + @NonNull + public static Builder builder() { + return new Builder(); + } + + private final String mClassName; + private final String mName; + private final int mIcon; + private final Map mDefaultValues; + + private ViewPalette(String className, + String name, + @DrawableRes int icon, + Map defaultValues) { + mClassName = className; + mName = name; + mIcon = icon; + mDefaultValues = defaultValues; + } + + /** + * @return The class name that will be used to inflate the view + */ + public String getClassName() { + return mClassName; + } + + public String getName() { + return mName; + } + + @DrawableRes + public int getIcon() { + return mIcon; + } + + /** + * @return The default values that can be used when this palette is inflated + */ + public Map getDefaultValues() { + return mDefaultValues; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ViewPalette)) return false; + ViewPalette that = (ViewPalette) o; + return mIcon == that.mIcon && mClassName.equals(that.mClassName); + } + + @Override + public int hashCode() { + return Objects.hash(mClassName, mIcon); + } + + public static class Builder { + private String className; + private String name; + private int icon; + private final Map defaultValues = new HashMap<>(); + + public Builder setClassName(String name) { + this.className = name; + return this; + } + + public Builder setName(String name) { + this.name = name; + return this; + } + + public Builder setIcon(@DrawableRes int icon) { + this.icon = icon; + return this; + } + + public Builder addDefaultValue(@NonNull String name, @NonNull Value value) { + this.defaultValues.put(name, value); + return this; + } + + public ViewPalette build() { + return new ViewPalette(className, name, icon, defaultValues); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/library/AddDependencyDialogFragment.java b/app/src/main/java/com/tyron/code/ui/library/AddDependencyDialogFragment.java new file mode 100644 index 00000000..e2befd9b --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/library/AddDependencyDialogFragment.java @@ -0,0 +1,86 @@ +package com.tyron.code.ui.library; + +import android.app.Dialog; +import android.os.Bundle; +import android.text.Editable; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.tyron.code.R; +import com.tyron.common.util.SingleTextWatcher; + +public class AddDependencyDialogFragment extends DialogFragment { + + public static final String TAG = AddDependencyDialogFragment.class.getSimpleName(); + public static final String ADD_KEY = "addDependency"; + + @SuppressWarnings("ConstantConditions") + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext()); + // noinspection InflateParams + View inflate = getLayoutInflater().inflate(R.layout.add_dependency_dialog, null); + EditText groupId = inflate.findViewById(R.id.et_group_id); + EditText artifactId = inflate.findViewById(R.id.et_artifact_id); + EditText versionName = inflate.findViewById(R.id.et_version_name); + + builder.setView(inflate); + + builder.setPositiveButton(R.string.wizard_create, (d, w) -> { + Bundle bundle = new Bundle(); + bundle.putString("groupId", String.valueOf(groupId.getText())); + bundle.putString("artifactId", String.valueOf(artifactId.getText())); + bundle.putString("versionName", String.valueOf(versionName.getText())); + getParentFragmentManager().setFragmentResult(ADD_KEY, bundle); + }); + builder.setNegativeButton(android.R.string.cancel, null); + + AlertDialog dialog = builder.create(); + dialog.setOnShowListener(d -> { + final Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE); + positiveButton.setEnabled(false); + + SingleTextWatcher textWatcher = new SingleTextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + boolean valid = validate(groupId, artifactId, versionName); + positiveButton.setEnabled(valid); + } + }; + groupId.addTextChangedListener(textWatcher); + artifactId.addTextChangedListener(textWatcher); + versionName.addTextChangedListener(textWatcher); + }); + return dialog; + } + + private boolean validate(EditText groupId, EditText artifactId, EditText versionName) { + String groupIdString = String.valueOf(groupId.getText()); + String artifactIdString = String.valueOf(artifactId.getText()); + String versionNameString = String.valueOf(versionName.getText()); + if (groupIdString.contains(":")) { + return false; + } + if (groupIdString.isEmpty()) { + return false; + } + if (artifactIdString.isEmpty()) { + return false; + } + if (artifactIdString.contains(":")) { + return false; + } + if (versionNameString.isEmpty()) { + return false; + } + return !versionNameString.contains(":"); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/library/LibraryManagerFragment.java b/app/src/main/java/com/tyron/code/ui/library/LibraryManagerFragment.java new file mode 100644 index 00000000..251d7ebc --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/library/LibraryManagerFragment.java @@ -0,0 +1,392 @@ +package com.tyron.code.ui.library; + +import android.app.ProgressDialog; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.core.view.MenuProvider; +import androidx.core.view.ViewCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.transition.TransitionManager; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.transition.MaterialFade; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import com.tyron.builder.log.ILogger; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.JavaModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.R; +import com.tyron.code.ui.library.adapter.LibraryManagerAdapter; +import com.tyron.code.ui.project.DependencyManager; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.code.util.DependencyUtils; +import com.tyron.code.util.UiUtilsKt; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.resolver.DependencyResolver; +import com.tyron.resolver.model.Dependency; +import com.tyron.resolver.model.Pom; +import com.tyron.resolver.repository.Repository; +import com.tyron.resolver.repository.RepositoryManager; +import com.tyron.resolver.repository.RepositoryManagerImpl; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +public class LibraryManagerFragment extends Fragment implements ProjectManager.OnProjectOpenListener { + + public static final String TAG = LibraryManagerFragment.class.getSimpleName(); + private static final String ARG_PATH = "path"; + private static final Type TYPE = new TypeToken>(){}.getType(); + + + public static LibraryManagerFragment newInstance(String modulePath) { + Bundle args = new Bundle(); + args.putString(ARG_PATH, modulePath); + LibraryManagerFragment fragment = new LibraryManagerFragment(); + fragment.setArguments(args); + return fragment; + } + + private RepositoryManager mRepositoryManager; + private String mModulePath; + private boolean isDumb = false; + private LibraryManagerAdapter mAdapter; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + File cacheDir = ApplicationLoader.applicationContext.getExternalFilesDir("cache"); + mRepositoryManager = new RepositoryManagerImpl(); + mRepositoryManager.setCacheDirectory(cacheDir); + mRepositoryManager.addRepository("maven", "https://repo1.maven.org/maven2"); + mRepositoryManager.addRepository("maven-google", "https://maven.google.com"); + mRepositoryManager.addRepository("jitpack", "https://jitpack.io"); + mRepositoryManager.addRepository("jcenter", "https://jcenter.bintray.com"); + mRepositoryManager.initialize(); + mModulePath = requireArguments().getString(ARG_PATH); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.library_manager_fragment, container, false); + view.setClickable(true); + + mAdapter = new LibraryManagerAdapter(); + + RecyclerView recyclerView = view.findViewById(R.id.libraries_recyclerview); + recyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); + recyclerView.setAdapter(mAdapter); + + Toolbar toolbar = view.findViewById(R.id.toolbar); + toolbar.setNavigationOnClickListener(v -> + getParentFragmentManager().popBackStack()); + toolbar.addMenuProvider(new MenuProvider() { + @Override + public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) { + menu.add(R.string.menu_add_libs_gradle) + .setOnMenuItemClickListener(item -> { + Project currentProject = + ProjectManager.getInstance().getCurrentProject(); + if (currentProject != null) { + Module mainModule = currentProject.getMainModule(); + File rootFile = mainModule.getRootFile(); + File gradleFile = new File(rootFile, "build.gradle"); + if (gradleFile.exists()) { + try { + List poms; + if (mainModule.getFileManager().isOpened(gradleFile) && mainModule.getFileManager().getFileContent(gradleFile).isPresent()) { + poms = DependencyUtils.parseGradle(mRepositoryManager, + mainModule.getFileManager().getFileContent(gradleFile).toString(), + ILogger.EMPTY); + } else { + poms = DependencyUtils.parseGradle(mRepositoryManager, gradleFile, ILogger.EMPTY); + } + List data = new ArrayList<>(mAdapter.getData()); + poms.forEach(dependency -> { + if (!data.contains(dependency)) { + data.add(dependency); + } + }); + mAdapter.submitList(data); + if (!data.isEmpty()) { + toggleEmptyView(false, false, ""); + } + save(((JavaModule) mainModule).getLibraryFile(), data); + } catch (Throwable e) { + new MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.error) + .setMessage(e.getMessage()) + .show(); + } + } + } + return true; + }); + } + + @Override + public boolean onMenuItemSelected(@NonNull MenuItem menuItem) { + return false; + } + }); + + return view; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + Project project = ProjectManager.getInstance().getCurrentProject(); + if (project == null) { + isDumb = true; + ProjectManager.getInstance().addOnProjectOpenListener(this); + } else { + ProgressManager.getInstance().runNonCancelableAsync(() -> loadDependencies(project)); + } + Toolbar toolbar = view.findViewById(R.id.toolbar); + toolbar.setOnMenuItemClickListener(menu -> getParentFragmentManager().popBackStackImmediate()); + + View fab = view.findViewById(R.id.fab_add_dependency); + UiUtilsKt.addSystemWindowInsetToMargin(fab, false, false, false, true); + ViewCompat.requestApplyInsets(fab); + } + + @Override + public void onProjectOpen(Project project) { + if (isDumb) { + ProgressManager.getInstance().runNonCancelableAsync(() -> loadDependencies(project)); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + + ProjectManager.getInstance().removeOnProjectOpenListener(this); + } + + private void loadDependencies(Project project) { + ProgressManager.getInstance().runLater(() -> { + toggleEmptyView(true, true, "Parsing dependencies"); + }); + + List dependencies = new ArrayList<>(); + Module projectModule = project.getModule(new File(mModulePath)); + if (projectModule instanceof JavaModule) { + + try { + List repositories = + DependencyManager.getFromModule((JavaModule) projectModule); + for (Repository repository : repositories) { + mRepositoryManager.addRepository(repository); + } + mRepositoryManager.setCacheDirectory(requireContext().getExternalFilesDir("cache")); + mRepositoryManager.initialize(); + } catch (IOException e) { + e.printStackTrace(); + } + + File file = ((JavaModule) projectModule).getLibraryFile(); + try { + if (!file.exists()) { + //noinspection ResultOfMethodCallIgnored ridiculous apis + file.createNewFile(); + } + } catch (IOException e) { + // ignored, just display an empty list + } + dependencies.addAll(parse(file)); + } + + ProgressManager.getInstance().runLater(() -> { + mAdapter.submitList(dependencies); + if (dependencies.isEmpty()) { + toggleEmptyView(true, false, "No dependencies"); + } else { + toggleEmptyView(false, false, ""); + } + onPostLoad(projectModule); + }); + } + + private void onPostLoad(Module module) { + if (!(module instanceof JavaModule)) { + return; + } + JavaModule javaModule = ((JavaModule) module); + + FloatingActionButton fab = requireView().findViewById(R.id.fab_add_dependency); + + fab.setOnClickListener(v -> { + FragmentManager fm = getChildFragmentManager(); + if (fm.findFragmentByTag(AddDependencyDialogFragment.TAG) == null) { + AddDependencyDialogFragment fragment = new AddDependencyDialogFragment(); + fragment.show(fm, AddDependencyDialogFragment.TAG); + } + }); + getChildFragmentManager().setFragmentResultListener(AddDependencyDialogFragment.ADD_KEY, + getViewLifecycleOwner(), (key, result) -> { + String groupId = result.getString("groupId"); + String artifactId = result.getString("artifactId"); + String versionName = result.getString("versionName"); + Dependency dependency = new Dependency(); + dependency.setGroupId(groupId); + dependency.setArtifactId(artifactId); + dependency.setVersionName(versionName); + dependency.setScope("compile"); + mAdapter.addDependency(dependency); + save(javaModule.getLibraryFile(), mAdapter.getData()); + toggleEmptyView(false, false, ""); + }); + mAdapter.setItemLongClickListener((v, dependency) -> { + v.setOnCreateContextMenuListener((menu, v1, menuInfo) -> { + menu.add(R.string.dialog_delete).setOnMenuItemClickListener(item -> { + mAdapter.removeDependency(dependency); + save(javaModule.getLibraryFile(), mAdapter.getData()); + if (mAdapter.getData().isEmpty()) { + toggleEmptyView(true, false, "No dependencies"); + } + return true; + }); + + menu.add(R.string.menu_display_dependencies).setOnMenuItemClickListener(item -> { + DependencyResolver resolver = new DependencyResolver(mRepositoryManager); + + ProgressDialog dialog = new ProgressDialog(requireContext()); + dialog.show(); + + Executors.newSingleThreadExecutor().execute(() -> { + Pom pom = mRepositoryManager.getPom(dependency.toString()); + if (pom != null) { + List resolve = resolver.resolve(Collections.singletonList(pom)); + + if (getActivity() != null) { + requireActivity().runOnUiThread(() -> { + dialog.dismiss(); + new MaterialAlertDialogBuilder(requireContext()) + .setTitle("Dependencies") + .setMessage(resolve.stream(). + map(Pom::toString) + .collect(Collectors.joining("\n"))) + .show(); + }); + } + } + }); + return true; + }); + }); + v.showContextMenu(); + }); + } + + private void toggleEmptyView(boolean show, boolean showProgress, String message) { + View view = getView(); + if (view == null) { + return; + } + + View loadingLayout = view.findViewById(R.id.loading_layout_root); + View recyclerView = view.findViewById(R.id.libraries_recyclerview); + View progress = view.findViewById(R.id.progressbar); + TextView textView = view.findViewById(R.id.empty_label); + textView.setText(message); + + TransitionManager.beginDelayedTransition((ViewGroup) recyclerView.getParent(), + new MaterialFade()); + if (show) { + loadingLayout.setVisibility(View.VISIBLE); + recyclerView.setVisibility(View.GONE); + } else { + loadingLayout.setVisibility(View.GONE); + recyclerView.setVisibility(View.VISIBLE); + } + progress.setVisibility(showProgress ? View.VISIBLE : View.GONE); + } + + private List parse(File file) { + if (file == null || !file.exists()) { + return Collections.emptyList(); + } + String contents; + Module module = getContainingModule(file); + if (module != null && module.getFileManager().isOpened(file) && module.getFileManager().getFileContent(file).isPresent()) { + contents = module.getFileManager().getFileContent(file).get().toString(); + } else { + try { + contents = FileUtils.readFileToString(file, StandardCharsets.UTF_8); + } catch (IOException e) { + contents = ""; + } + } + + if (contents.isEmpty()) { + return Collections.emptyList(); + } + try { + return new Gson().fromJson(contents, TYPE); + } catch (JsonSyntaxException e) { + return Collections.emptyList(); + } + } + + private Future save(File file, List dependencies) { + String jsonString = new GsonBuilder() + .setPrettyPrinting() + .create() + .toJson(dependencies, TYPE); + return CompletableFuture.supplyAsync(() -> { + try { + Module module = getContainingModule(file); + if (module != null && module.getFileManager().isOpened(file)) { + module.getFileManager().setSnapshotContent(file, jsonString); + } else { + FileUtils.writeStringToFile(file, jsonString, StandardCharsets.UTF_8); + } + return true; + } catch (IOException e) { + return false; + } + }); + } + + @Nullable + private Module getContainingModule(@NonNull File file) { + Project currentProject = ProjectManager.getInstance().getCurrentProject(); + if (currentProject == null) { + return null; + } + return currentProject.getModule(file); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/library/adapter/LibraryManagerAdapter.java b/app/src/main/java/com/tyron/code/ui/library/adapter/LibraryManagerAdapter.java new file mode 100644 index 00000000..fea9ecfb --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/library/adapter/LibraryManagerAdapter.java @@ -0,0 +1,133 @@ +package com.tyron.code.ui.library.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.LayoutParams; + +import com.google.common.collect.ImmutableList; +import com.tyron.code.R; +import com.tyron.resolver.model.Dependency; + +import java.util.ArrayList; +import java.util.List; + +public class LibraryManagerAdapter extends RecyclerView.Adapter { + + public interface ItemLongClickListener { + void onItemLongClick(View view, Dependency item); + } + + private final List mData = new ArrayList<>(); + private ItemLongClickListener mListener; + + public LibraryManagerAdapter() { + + } + + public void setItemLongClickListener(ItemLongClickListener listener) { + mListener = listener; + } + + public void addDependency(Dependency dependency) { + List arrayList = new ArrayList<>(mData); + arrayList.add(dependency); + submitList(arrayList); + } + + public void removeDependency(Dependency dependency) { + List arrayList = new ArrayList<>(mData); + arrayList.remove(dependency); + submitList(arrayList); + } + + public List getData() { + return ImmutableList.copyOf(mData); + } + + public void submitList(List newData) { + DiffUtil.Callback callback = new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return mData.size(); + } + + @Override + public int getNewListSize() { + return newData.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return mData.get(oldItemPosition).equals(newData.get(newItemPosition)); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + return mData.get(oldItemPosition).equals(newData.get(newItemPosition)); + } + }; + DiffUtil.DiffResult result = DiffUtil.calculateDiff(callback); + mData.clear(); + mData.addAll(newData); + result.dispatchUpdatesTo(this); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + FrameLayout view = new FrameLayout(parent.getContext()); + view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + ViewHolder viewHolder = new ViewHolder(view); + view.setOnLongClickListener(v -> { + int pos = viewHolder.getBindingAdapterPosition(); + if (pos != RecyclerView.NO_POSITION) { + if (mListener != null) { + mListener.onItemLongClick(view, mData.get(pos)); + } + } + return true; + }); + return viewHolder; + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + if (position == 0) { + holder.mDivider.setVisibility(View.GONE); + } else { + holder.mDivider.setVisibility(View.VISIBLE); + } + holder.bind(mData.get(position)); + } + + @Override + public int getItemCount() { + return mData.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + + public final View mDivider; + private final TextView mTextView; + + public ViewHolder(FrameLayout view) { + super(view); + + LayoutInflater inflater = LayoutInflater.from(view.getContext()); + View root = inflater.inflate(R.layout.library_manager_item, view); + mDivider = root.findViewById(R.id.divider); + mTextView = root.findViewById(R.id.item_text); + } + + public void bind(Dependency dependency) { + mTextView.setText(dependency.toString()); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/library/model/LibraryItem.java b/app/src/main/java/com/tyron/code/ui/library/model/LibraryItem.java new file mode 100644 index 00000000..57329dc1 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/library/model/LibraryItem.java @@ -0,0 +1,12 @@ +package com.tyron.code.ui.library.model; + +import com.tyron.resolver.model.Dependency; + +public class LibraryItem { + + private Dependency dependency; + + public LibraryItem() { + + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/CompileCallback.java b/app/src/main/java/com/tyron/code/ui/main/CompileCallback.java new file mode 100644 index 00000000..8aca9192 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/CompileCallback.java @@ -0,0 +1,8 @@ +package com.tyron.code.ui.main; + +import com.tyron.builder.compiler.BuildType; + +public interface CompileCallback { + + void compile(BuildType buildType); +} diff --git a/app/src/main/java/com/tyron/code/ui/main/IndexCallback.java b/app/src/main/java/com/tyron/code/ui/main/IndexCallback.java new file mode 100644 index 00000000..ba4046d6 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/IndexCallback.java @@ -0,0 +1,8 @@ +package com.tyron.code.ui.main; + +import com.tyron.builder.project.Project; + +public interface IndexCallback { + + void index(Project project); +} diff --git a/app/src/main/java/com/tyron/code/ui/main/MainFragment.java b/app/src/main/java/com/tyron/code/ui/main/MainFragment.java new file mode 100644 index 00000000..18a0bee8 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/MainFragment.java @@ -0,0 +1,530 @@ +package com.tyron.code.ui.main; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.appcompat.widget.Toolbar; +import androidx.appcompat.widget.ViewUtils; +import androidx.core.view.GravityCompat; +import androidx.core.view.OnApplyWindowInsetsListener; +import androidx.core.view.ViewCompat; +import androidx.core.view.ViewKt; +import androidx.core.view.WindowInsetsCompat; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; + +import com.google.android.material.progressindicator.LinearProgressIndicator; +import com.google.android.material.transition.MaterialSharedAxis; +import com.google.gson.Gson; +import com.tyron.actions.ActionManager; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.DataContext; +import com.tyron.actions.util.DataContextUtils; +import com.tyron.builder.log.ILogger; +import com.tyron.builder.model.DiagnosticWrapper; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.ui.file.event.RefreshRootEvent; +import com.tyron.code.util.UiUtilsKt; +import com.tyron.common.logging.IdeLog; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.fileeditor.api.FileEditor; +import com.tyron.fileeditor.api.FileEditorSavedState; +import com.tyron.code.ui.project.ProjectManager; +import com.tyron.builder.compiler.BuildType; +import com.tyron.builder.log.LogViewModel; +import com.tyron.builder.model.ProjectSettings; +import com.tyron.builder.project.Project; +import com.tyron.code.R; +import com.tyron.code.service.CompilerService; +import com.tyron.code.service.CompilerServiceConnection; +import com.tyron.code.service.IndexService; +import com.tyron.code.service.IndexServiceConnection; +import com.tyron.code.ui.editor.EditorContainerFragment; +import com.tyron.code.ui.file.FileViewModel; +import com.tyron.completion.java.provider.CompletionEngine; + +import org.jetbrains.kotlin.com.intellij.openapi.util.Key; +import javax.tools.Diagnostic; + +import java.io.File; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +public class MainFragment extends Fragment implements ProjectManager.OnProjectOpenListener { + + public static final String REFRESH_TOOLBAR_KEY = "refreshToolbar"; + + public static final Key COMPILE_CALLBACK_KEY = Key.create("compileCallback"); + public static final Key INDEX_CALLBACK_KEY = Key.create("indexCallbackKey"); + public static final Key MAIN_VIEW_MODEL_KEY = Key.create("mainViewModel"); + + private Handler mHandler; + + public static MainFragment newInstance(@NonNull String projectPath) { + Bundle bundle = new Bundle(); + bundle.putString("project_path", projectPath); + + MainFragment fragment = new MainFragment(); + fragment.setArguments(bundle); + + return fragment; + } + + private LogViewModel mLogViewModel; + private MainViewModel mMainViewModel; + private FileViewModel mFileViewModel; + + private ProjectManager mProjectManager; + private View mRoot; + private Toolbar mToolbar; + private LinearProgressIndicator mProgressBar; + private BroadcastReceiver mLogReceiver; + + private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { + @Override + public void handleOnBackPressed() { + if (mRoot instanceof DrawerLayout) { + //noinspection ConstantConditions + if (mMainViewModel.getDrawerState() + .getValue()) { + mMainViewModel.setDrawerState(false); + } + } + } + }; + private Project mProject; + private CompilerServiceConnection mServiceConnection; + private IndexServiceConnection mIndexServiceConnection; + + private final CompileCallback mCompileCallback = this::compile; + private final IndexCallback mIndexCallback = this::openProject; + + + public MainFragment() { + + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setEnterTransition(new MaterialSharedAxis(MaterialSharedAxis.X, true)); + setExitTransition(new MaterialSharedAxis(MaterialSharedAxis.X, false)); + + requireActivity().getOnBackPressedDispatcher() + .addCallback(this, onBackPressedCallback); + + String projectPath = requireArguments().getString("project_path"); + mProject = new Project(new File(projectPath)); + mProjectManager = ProjectManager.getInstance(); + mProjectManager.addOnProjectOpenListener(this); + mLogViewModel = new ViewModelProvider(requireActivity()).get(LogViewModel.class); + mMainViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class); + mFileViewModel = new ViewModelProvider(requireActivity()).get(FileViewModel.class); + mIndexServiceConnection = new IndexServiceConnection(mMainViewModel, mLogViewModel); + mServiceConnection = new CompilerServiceConnection(mMainViewModel, mLogViewModel); + } + + @Override + public View onCreateView(LayoutInflater inflater, + ViewGroup container, + Bundle savedInstanceState) { + mRoot = inflater.inflate(R.layout.main_fragment, container, false); + + mProgressBar = mRoot.findViewById(R.id.progressbar); + mProgressBar.setIndeterminate(true); + mProgressBar.setVisibility(View.GONE); + + mToolbar = mRoot.findViewById(R.id.toolbar); + mToolbar.setNavigationIcon(R.drawable.ic_baseline_menu_24); + UiUtilsKt.addSystemWindowInsetToPadding(mToolbar, false, true, false, false); + + getChildFragmentManager().setFragmentResultListener(REFRESH_TOOLBAR_KEY, + getViewLifecycleOwner(), + (key, __) -> refreshToolbar()); + + refreshToolbar(); + + if (savedInstanceState != null) { + restoreViewState(savedInstanceState); + } + return mRoot; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + if (mRoot instanceof DrawerLayout) { + DrawerLayout drawerLayout = (DrawerLayout) mRoot; + mToolbar.setNavigationOnClickListener(v -> { + if (mRoot instanceof DrawerLayout) { + if (drawerLayout.isDrawerOpen(GravityCompat.START)) { + mMainViewModel.setDrawerState(false); + } else if (!drawerLayout.isDrawerOpen(GravityCompat.START)) { + mMainViewModel.setDrawerState(true); + } + } + }); + drawerLayout.addDrawerListener(new DrawerLayout.SimpleDrawerListener() { + @Override + public void onDrawerOpened(@NonNull View p1) { + mMainViewModel.setDrawerState(true); + onBackPressedCallback.setEnabled(true); + } + + @Override + public void onDrawerClosed(@NonNull View p1) { + mMainViewModel.setDrawerState(false); + onBackPressedCallback.setEnabled(false); + } + }); + } else { + mToolbar.setNavigationIcon(null); + } + + File root; + if (mProject != null) { + root = mProject.getRootFile(); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + root = requireActivity().getExternalFilesDir(null); + } else { + root = Environment.getExternalStorageDirectory(); + } + } + mFileViewModel.refreshNode(root); + + if (!mProject.equals(mProjectManager.getCurrentProject())) { + mRoot.postDelayed(() -> openProject(mProject), 200); + } + + // If the user has changed projects, clear the current opened files + if (!mProject.equals(mProjectManager.getCurrentProject())) { + mMainViewModel.setFiles(new ArrayList<>()); + mLogViewModel.clear(LogViewModel.BUILD_LOG); + } + mMainViewModel.isIndexing() + .observe(getViewLifecycleOwner(), indexing -> { + mProgressBar.setVisibility(indexing ? View.VISIBLE : View.GONE); + CompletionEngine.setIndexing(indexing); + refreshToolbar(); + }); + mMainViewModel.getCurrentState() + .observe(getViewLifecycleOwner(), mToolbar::setSubtitle); + mMainViewModel.getToolbarTitle() + .observe(getViewLifecycleOwner(), mToolbar::setTitle); + if (mRoot instanceof DrawerLayout) { + mMainViewModel.getDrawerState() + .observe(getViewLifecycleOwner(), isOpen -> { + if (isOpen) { + ((DrawerLayout) mRoot).open(); + } else { + ((DrawerLayout) mRoot).close(); + } + }); + } + + mHandler = new Handler() { + @Override + public void publish(LogRecord record) { + Level level = record.getLevel(); + if (Level.WARNING.equals(level)) { + mLogViewModel.w(LogViewModel.IDE, record.getMessage()); + } else if (Level.SEVERE.equals(level)) { + mLogViewModel.e(LogViewModel.IDE, record.getMessage()); + } else { + mLogViewModel.d(LogViewModel.IDE, record.getMessage()); + } + } + + @Override + public void flush() { + mLogViewModel.clear(LogViewModel.IDE); + } + + @Override + public void close() throws SecurityException { + mLogViewModel.clear(LogViewModel.IDE); + } + }; + IdeLog.getLogger().addHandler(mHandler); + + // can be null on tablets + View navRoot = view.findViewById(R.id.nav_root); + + ViewCompat.setOnApplyWindowInsetsListener(mRoot, (v, insets) -> { + if (navRoot != null) { + ViewCompat.dispatchApplyWindowInsets(navRoot, insets); + } + ViewGroup viewGroup = (ViewGroup) mRoot; + for (int i = 0; i < viewGroup.getChildCount(); i++) { + View child = viewGroup.getChildAt(i); + if (child == navRoot) { + continue; + } + + ViewCompat.dispatchApplyWindowInsets(child, insets); + } + return ViewCompat.onApplyWindowInsets(v, insets); + }); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + ProjectManager manager = ProjectManager.getInstance(); + Project project = manager.getCurrentProject(); + if (project != null) { + for (Module module : project.getModules()) { + module.getFileManager() + .shutdown(); + } + } + manager.removeOnProjectOpenListener(this); + + if (mLogReceiver != null) { + requireActivity().unregisterReceiver(mLogReceiver); + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + if (mHandler != null) { + IdeLog.getLogger().removeHandler(mHandler); + } + } + + @Override + public void onPause() { + super.onPause(); + + saveAll(); + mServiceConnection.setShouldShowNotification(true); + } + + @Override + public void onResume() { + super.onResume(); + + refreshToolbar(); + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + saveAll(); + if (mRoot instanceof DrawerLayout) { + outState.putBoolean("start_drawer_state", + ((DrawerLayout) mRoot).isDrawerOpen(GravityCompat.START)); + } + super.onSaveInstanceState(outState); + } + + private void restoreViewState(@NonNull Bundle state) { + if (mRoot instanceof DrawerLayout) { + boolean b = state.getBoolean("start_drawer_state", false); + mMainViewModel.setDrawerState(b); + } + } + + /** + * Tries to open a file into the editor + * + * @param file file to open + */ + public void openFile(FileEditor file) { + mMainViewModel.openFile(file); + } + + public void openProject(@NonNull Project project) { + if (CompletionEngine.isIndexing()) { + return; + } + if (getContext() == null) { + return; + } + + if (project.equals(ProjectManager.getInstance().getCurrentProject())) { + saveAll(false); + project.getSettings().refresh(); + } + + IndexServiceConnection.restoreFileEditors(project, mMainViewModel); + + mProject = project; + mIndexServiceConnection.setProject(project); + + mMainViewModel.setToolbarTitle(project.getRootFile() + .getName()); + mMainViewModel.setIndexing(true); + CompletionEngine.setIndexing(true); + + RefreshRootEvent event = new RefreshRootEvent(project.getRootFile()); + ApplicationLoader.getInstance().getEventManager().dispatchEvent(event); + + Intent intent = new Intent(requireContext(), IndexService.class); + requireActivity().startService(intent); + requireActivity().bindService(intent, mIndexServiceConnection, Context.BIND_IMPORTANT); + } + + private void saveAll() { + saveAll(true); + } + + private void saveAll(boolean async) { + if (mProject == null) { + return; + } + + if (CompletionEngine.isIndexing()) { + return; + } + + Collection modules = mProject.getModules(); + modules.forEach(it -> it.getFileManager().saveContents()); + + getChildFragmentManager().setFragmentResult(EditorContainerFragment.SAVE_ALL_KEY, + Bundle.EMPTY); + + ProjectSettings settings = mProject.getSettings(); + if (settings == null) { + return; + } + + List items = mMainViewModel.getFiles() + .getValue(); + if (items != null) { + String itemString = new Gson().toJson(items.stream() + .map(FileEditorSavedState::new) + .collect(Collectors.toList())); + SharedPreferences.Editor editor = settings.edit() + .putString(ProjectSettings.SAVED_EDITOR_FILES, itemString); + if (async) { + editor.apply(); + } else { + editor.commit(); + } + } + } + + private void compile(BuildType type) { + if (mServiceConnection.isCompiling() || CompletionEngine.isIndexing()) { + return; + } + + saveAll(); + mServiceConnection.setBuildType(type); + + mMainViewModel.setCurrentState(getString(R.string.compilation_state_compiling)); + mMainViewModel.setIndexing(true); + mLogViewModel.clear(LogViewModel.BUILD_LOG); + + requireActivity().startService(new Intent(requireContext(), CompilerService.class)); + requireActivity().bindService(new Intent(requireContext(), CompilerService.class), + mServiceConnection, Context.BIND_IMPORTANT); + } + + @Override + public void onProjectOpen(Project project) { + Module module = project.getMainModule(); + if (module instanceof AndroidModule) { + mLogReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String type = intent.getExtras() + .getString("type", "DEBUG"); + String message = intent.getExtras() + .getString("message", "No message provided"); + DiagnosticWrapper wrapped = ILogger.wrap(message); + + switch (type) { + case "DEBUG": + case "INFO": + wrapped.setKind(Diagnostic.Kind.NOTE); + mLogViewModel.d(LogViewModel.APP_LOG, wrapped); + break; + case "ERROR": + wrapped.setKind(Diagnostic.Kind.ERROR); + mLogViewModel.e(LogViewModel.APP_LOG, wrapped); + break; + case "WARNING": + wrapped.setKind(Diagnostic.Kind.WARNING); + mLogViewModel.w(LogViewModel.APP_LOG, wrapped); + break; + } + } + }; + String packageName = ((AndroidModule) module).getPackageName(); + if (packageName != null) { + requireActivity().registerReceiver(mLogReceiver, + new IntentFilter(packageName + ".LOG")); + } else { + mLogReceiver = null; + } + } + + ProgressManager.getInstance().runLater(() -> { + if (getContext() == null) { + return; + } + refreshToolbar(); + }); + } + + private void injectData(DataContext context) { + Boolean indexing = mMainViewModel.isIndexing().getValue(); + // to please lint + if (indexing == null) { + indexing = true; + } + if (!indexing) { + context.putData(CommonDataKeys.PROJECT, ProjectManager.getInstance().getCurrentProject()); + } + context.putData(CommonDataKeys.ACTIVITY, getActivity()); + context.putData(MAIN_VIEW_MODEL_KEY, mMainViewModel); + context.putData(COMPILE_CALLBACK_KEY, mCompileCallback); + context.putData(INDEX_CALLBACK_KEY, mIndexCallback); + context.putData(CommonDataKeys.FILE_EDITOR_KEY, mMainViewModel.getCurrentFileEditor()); + } + + public void refreshToolbar() { + mToolbar.getMenu() + .clear(); + + DataContext context = DataContextUtils.getDataContext(mToolbar); + injectData(context); + + Instant now = Instant.now(); + ActionManager.getInstance() + .fillMenu(context, mToolbar.getMenu(), ActionPlaces.MAIN_TOOLBAR, false, true); + Log.d("ActionManager", "fillMenu() took " + + Duration.between(now, Instant.now()) + .toMillis()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/MainViewModel.java b/app/src/main/java/com/tyron/code/ui/main/MainViewModel.java new file mode 100644 index 00000000..e5a9f6e0 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/MainViewModel.java @@ -0,0 +1,225 @@ +package com.tyron.code.ui.main; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.tyron.builder.project.api.Module; +import com.tyron.code.util.CustomMutableLiveData; +import com.tyron.fileeditor.api.FileEditor; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class MainViewModel extends ViewModel { + + /** + * The files currently opened in the editor + */ + private MutableLiveData> mFiles; + + /** + * Whether the current completion engine is indexing + */ + private MutableLiveData mIndexing; + + /** + * The text shown on the subtitle of the toolbar + */ + private MutableLiveData mCurrentState; + + private final MutableLiveData mToolbarTitle = new MutableLiveData<>(); + + /** + * The current position of the CodeEditor + */ + private final CustomMutableLiveData currentPosition = new CustomMutableLiveData<>(0); + + private final MutableLiveData mBottomSheetState = + new MutableLiveData<>(BottomSheetBehavior.STATE_COLLAPSED); + + private final MutableLiveData mDrawerState = + new MutableLiveData<>(false); + + public MutableLiveData getCurrentState() { + if (mCurrentState == null) { + mCurrentState = new MutableLiveData<>(null); + } + return mCurrentState; + } + + public void setCurrentState(@Nullable String message) { + mCurrentState.setValue(message); + } + + public LiveData getDrawerState() { + return mDrawerState; + } + + public void setDrawerState(boolean isOpen) { + mDrawerState.setValue(isOpen); + } + + public LiveData getToolbarTitle() { + return mToolbarTitle; + } + + public void setToolbarTitle(String title) { + mToolbarTitle.setValue(title); + } + + public LiveData getBottomSheetState() { + return mBottomSheetState; + } + + + public void setBottomSheetState(@BottomSheetBehavior.State int bottomSheetState) { + mBottomSheetState.setValue(bottomSheetState); + } + + public MutableLiveData isIndexing() { + if (mIndexing == null) { + mIndexing = new MutableLiveData<>(false); + } + return mIndexing; + } + + public void setIndexing(boolean indexing) { + mIndexing.setValue(indexing); + } + + public LiveData> getFiles() { + if (mFiles == null) { + mFiles = new MutableLiveData<>(new ArrayList<>()); + } + return mFiles; + } + + public void setFiles(@NonNull List files) { + if (mFiles == null) { + mFiles = new MutableLiveData<>(new ArrayList<>()); + } + mFiles.setValue(files); + } + + public LiveData getCurrentPosition() { + return currentPosition; + } + + public void setCurrentPosition(int pos) { + setCurrentPosition(pos, true); + } + + public void setCurrentPosition(int pos, boolean update) { + Integer value = currentPosition.getValue(); + if (value != null && value.equals(pos)) { + return; + } + currentPosition.setValue(pos, update); + } + + public FileEditor getCurrentFileEditor() { + List files = getFiles().getValue(); + if (files == null) { + return null; + } + + Integer currentPos = currentPosition.getValue(); + if (currentPos == null) { + return null; + } + + if (files.size() - 1 < currentPos) { + return null; + } + + return files.get(currentPos); + } + + public void clear() { + mFiles.setValue(new ArrayList<>()); + } + + + /** + * Opens this file to the editor + * @param file The fle to be opened + * @return whether the operation was successful + */ + public boolean openFile(FileEditor file) { + setDrawerState(false); + + int index = -1; + List value = getFiles().getValue(); + if (value != null) { + for (int i = 0; i < value.size(); i++) { + FileEditor editor = value.get(i); + if (file.getFile().equals(editor.getFile())) { + index = i; + } + } + } + if (index != -1) { + setCurrentPosition(index); + return true; + } + addFile(file); + return true; + } + + public void addFile(FileEditor file) { + List files = getFiles().getValue(); + if (files == null) { + files = new ArrayList<>(); + } + files.add(file); + mFiles.setValue(files); + setCurrentPosition(files.indexOf(file)); + } + + public void removeFile(@NonNull File file) { + List files = getFiles().getValue(); + if (files == null) { + return; + } + FileEditor find = null; + for (FileEditor fileEditor : files) { + if (file.equals(fileEditor.getFile())) { + find = fileEditor; + } + } + if (find != null) { + files.remove(find); + mFiles.setValue(files); + } + } + + /** + * Remove all the files except the given file + */ + public void removeOthers(File file) { + List files = getFiles().getValue(); + if (files != null) { + FileEditor find = null; + for (FileEditor fileEditor : files) { + if (file.equals(fileEditor.getFile())) { + find = fileEditor; + } + } + + if (find != null) { + files.clear(); + files.add(find); + setFiles(files); + } + } + } + + public void initializeProject(Module module) { + setIndexing(true); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileAabAction.java b/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileAabAction.java new file mode 100644 index 00000000..96e68de5 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileAabAction.java @@ -0,0 +1,29 @@ +package com.tyron.code.ui.main.action.compile; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.tyron.actions.AnActionEvent; +import com.tyron.builder.compiler.BuildType; +import com.tyron.code.R; +import com.tyron.code.ui.main.CompileCallback; +import com.tyron.code.ui.main.MainFragment; + +public class CompileAabAction extends CompileAction { + + public CompileAabAction() { + super(BuildType.AAB); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + CompileCallback callback = e.getData(MainFragment.COMPILE_CALLBACK_KEY); + callback.compile(BuildType.AAB); + } + + @Override + public String getTitle(Context context) { + return context.getString(R.string.action_menu_build_aab); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileAction.java b/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileAction.java new file mode 100644 index 00000000..f8b14b32 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileAction.java @@ -0,0 +1,47 @@ +package com.tyron.code.ui.main.action.compile; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.Presentation; +import com.tyron.builder.compiler.BuildType; +import com.tyron.code.ui.main.CompileCallback; +import com.tyron.code.ui.main.MainFragment; + +public abstract class CompileAction extends AnAction { + + protected final BuildType mBuildType; + + public CompileAction(BuildType type) { + mBuildType = type; + } + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + Context context = event.getData(CommonDataKeys.CONTEXT); + + if (!ActionPlaces.MAIN_TOOLBAR.equals(event.getPlace()) || context == null) { + presentation.setVisible(false); + return; + } + + CompileCallback data = event.getData(MainFragment.COMPILE_CALLBACK_KEY); + if (data == null) { + event.getPresentation().setVisible(false); + return; + } + + + presentation.setText(getTitle(context)); + presentation.setEnabled(true); + presentation.setVisible(true); + } + + public abstract String getTitle(Context context); +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileActionGroup.java b/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileActionGroup.java new file mode 100644 index 00000000..13e51c4f --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileActionGroup.java @@ -0,0 +1,56 @@ +package com.tyron.code.ui.main.action.compile; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + +import com.tyron.actions.ActionGroup; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; +import com.tyron.code.ui.main.CompileCallback; +import com.tyron.code.ui.main.MainFragment; + +import java.util.ArrayList; +import java.util.List; + +public class CompileActionGroup extends ActionGroup { + + public static final String ID = "compileActionGroup"; + + @Override + public void update(@NonNull AnActionEvent event) { + CompileCallback data = event.getData(MainFragment.COMPILE_CALLBACK_KEY); + if (data == null) { + event.getPresentation().setVisible(false); + return; + } + + if (!ActionPlaces.MAIN_TOOLBAR.equals(event.getPlace())) { + event.getPresentation().setVisible(false); + return; + } + + + Context context = event.getData(CommonDataKeys.CONTEXT); + if (context == null) { + event.getPresentation().setVisible(false); + return; + } + + event.getPresentation().setVisible(true); + event.getPresentation().setEnabled(true); + event.getPresentation().setText(context.getString(R.string.menu_run)); + event.getPresentation().setIcon(ContextCompat.getDrawable(context, R.drawable.round_play_arrow_24)); + } + + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return new AnAction[]{new CompileReleaseAction(), + new CompileDebugAction(), new CompileAabAction()}; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileDebugAction.java b/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileDebugAction.java new file mode 100644 index 00000000..fe53eda9 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileDebugAction.java @@ -0,0 +1,29 @@ +package com.tyron.code.ui.main.action.compile; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.tyron.actions.AnActionEvent; +import com.tyron.builder.compiler.BuildType; +import com.tyron.code.R; +import com.tyron.code.ui.main.CompileCallback; +import com.tyron.code.ui.main.MainFragment; + +public class CompileDebugAction extends CompileAction { + + public CompileDebugAction() { + super(BuildType.DEBUG); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + CompileCallback callback = e.getData(MainFragment.COMPILE_CALLBACK_KEY); + callback.compile(BuildType.DEBUG); + } + + @Override + public String getTitle(Context context) { + return context.getString(R.string.action_menu_build_debug); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileReleaseAction.java b/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileReleaseAction.java new file mode 100644 index 00000000..90249a31 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/compile/CompileReleaseAction.java @@ -0,0 +1,29 @@ +package com.tyron.code.ui.main.action.compile; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.tyron.actions.AnActionEvent; +import com.tyron.builder.compiler.BuildType; +import com.tyron.code.R; +import com.tyron.code.ui.main.CompileCallback; +import com.tyron.code.ui.main.MainFragment; + +public class CompileReleaseAction extends CompileAction { + + public CompileReleaseAction() { + super(BuildType.RELEASE); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + CompileCallback callback = e.getData(MainFragment.COMPILE_CALLBACK_KEY); + callback.compile(BuildType.RELEASE); + } + + @Override + public String getTitle(Context context) { + return context.getString(R.string.action_menu_build_release); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/debug/BaseLoadAction.java b/app/src/main/java/com/tyron/code/ui/main/action/debug/BaseLoadAction.java new file mode 100644 index 00000000..0d23feae --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/debug/BaseLoadAction.java @@ -0,0 +1,83 @@ +package com.tyron.code.ui.main.action.debug; + +import android.app.Activity; + +import androidx.annotation.NonNull; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.D8; +import com.android.tools.r8.D8Command; +import com.android.tools.r8.OutputMode; +import com.github.angads25.filepicker.model.DialogConfigs; +import com.github.angads25.filepicker.model.DialogProperties; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.tyron.actions.ActionManager; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.Presentation; +import com.tyron.builder.BuildModule; +import com.tyron.builder.project.Project; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.ui.file.FilePickerDialogFixed; + +import org.openjdk.com.sun.org.apache.bcel.internal.classfile.ClassParser; +import org.openjdk.com.sun.org.apache.bcel.internal.classfile.JavaClass; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import dalvik.system.PathClassLoader; + +public abstract class BaseLoadAction extends AnAction { + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Activity activity = e.getRequiredData(CommonDataKeys.ACTIVITY); + Project project = e.getRequiredData(CommonDataKeys.PROJECT); + + DialogProperties properties = new DialogProperties(); + properties.selection_mode = DialogConfigs.SINGLE_MODE; + properties.selection_type = DialogConfigs.FILE_SELECT; + properties.extensions = new String[]{"jar"}; + properties.root = project.getRootFile(); + + FilePickerDialogFixed dialog = new FilePickerDialogFixed(activity, properties); + dialog.show(); + dialog.setDialogSelectionListener(files -> { + try { + List javaClasses = getApplicableClasses(files[0]); + String[] names = javaClasses.stream() + .map(JavaClass::getClassName) + .toArray(String[]::new); + boolean[] checked = new boolean[names.length]; + new MaterialAlertDialogBuilder(activity) + .setTitle("Detected classes") + .setMultiChoiceItems(names, checked, (d, which, isChecked) -> { + + }) + .setPositiveButton("Load", (d, which) -> { + try { + loadClasses(files[0], javaClasses); + } catch (Exception exception) { + ApplicationLoader.showToast(exception.getMessage()); + } + }) + .show(); + } catch (IOException exception) { + ApplicationLoader.showToast(exception.getMessage()); + } + }); + } + + public abstract List getApplicableClasses(String jarFile) throws IOException; + + public abstract void loadClasses(String file, List classes) throws ClassNotFoundException, IllegalAccessException, InstantiationException, CompilationFailedException, IOException; +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/debug/CrashAction.java b/app/src/main/java/com/tyron/code/ui/main/action/debug/CrashAction.java new file mode 100644 index 00000000..1e0f8ac2 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/debug/CrashAction.java @@ -0,0 +1,32 @@ +package com.tyron.code.ui.main.action.debug; + +import androidx.annotation.NonNull; + +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.Presentation; + +/** + * Used to test whether files are saved when the app crashes. + */ +public class CrashAction extends AnAction { + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + if (!ActionPlaces.MAIN_TOOLBAR.equals(event.getPlace())) { + return; + } + + presentation.setText("Throw uncaught exception"); + presentation.setVisible(true); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + throw new RuntimeException("Application manually crashed."); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/debug/DebugActionGroup.java b/app/src/main/java/com/tyron/code/ui/main/action/debug/DebugActionGroup.java new file mode 100644 index 00000000..0fb6bde3 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/debug/DebugActionGroup.java @@ -0,0 +1,39 @@ +package com.tyron.code.ui.main.action.debug; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.actions.ActionGroup; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.Presentation; +import com.tyron.code.BuildConfig; + +public class DebugActionGroup extends ActionGroup { + + public static final String ID = "debugActionGroup"; + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + if (!BuildConfig.DEBUG) { + return; + } + + if (!ActionPlaces.MAIN_TOOLBAR.equals(event.getPlace())) { + return; + } + + presentation.setVisible(true); + presentation.setText("Debug"); + } + + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return new AnAction[]{new LoadActionJarAction(), new LoadFileEditorProviderAction(), + new CrashAction(), new LoadXmlRepositoryAction(), new RunLongRunningTaskAction()}; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/debug/LoadActionJarAction.java b/app/src/main/java/com/tyron/code/ui/main/action/debug/LoadActionJarAction.java new file mode 100644 index 00000000..f5aea043 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/debug/LoadActionJarAction.java @@ -0,0 +1,120 @@ +package com.tyron.code.ui.main.action.debug; + +import android.app.Activity; + +import androidx.annotation.NonNull; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.D8; +import com.android.tools.r8.D8Command; +import com.android.tools.r8.OutputMode; +import com.github.angads25.filepicker.model.DialogConfigs; +import com.github.angads25.filepicker.model.DialogProperties; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.tyron.actions.ActionManager; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.Presentation; +import com.tyron.builder.BuildModule; +import com.tyron.builder.project.Project; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.ui.file.FilePickerDialogFixed; + +import org.openjdk.com.sun.org.apache.bcel.internal.classfile.ClassParser; +import org.openjdk.com.sun.org.apache.bcel.internal.classfile.JavaClass; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import dalvik.system.PathClassLoader; + +public class LoadActionJarAction extends BaseLoadAction { + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(true); + presentation.setText("Load action plugin"); + } + + private void dexAndLoadJar(String file, List javaClasses) throws IOException, CompilationFailedException, ClassNotFoundException, InstantiationException, IllegalAccessException { + File jarFile = new File(file); + + Path temp_output = Files.createTempDirectory("temp_output"); + + D8Command command = D8Command.builder() + .addProgramFiles(jarFile.toPath()) + .addLibraryFiles(BuildModule.getAndroidJar().toPath()) + .addLibraryFiles(BuildModule.getLambdaStubs().toPath()) + .setOutput(temp_output, OutputMode.DexIndexed) + .build(); + D8.run(command); + + StringBuilder path = new StringBuilder(); + File[] files = temp_output.toFile().listFiles(); + for (File it : files) { + path.append(it.getAbsolutePath()); + path.append(File.pathSeparator); + } + + PathClassLoader classLoader = new PathClassLoader(path.substring(0, path.length() - 1), + getClass().getClassLoader()); + for (JavaClass javaClass : javaClasses) { + Class aClass = classLoader.loadClass(javaClass.getClassName()); + Class actionClass = aClass.asSubclass(AnAction.class); + AnAction anAction = actionClass.newInstance(); + ActionManager.getInstance().replaceAction(file, anAction); + } + } + + @Override + public List getApplicableClasses(String file) throws IOException { + List actionClasses = new ArrayList<>(); + + JarFile jarFile = new JarFile(file); + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (!entry.getName().endsWith(".class")) { + continue; + } + + ClassParser parser = new ClassParser(file, entry.getName()); + JavaClass javaClass = parser.parse(); + + if (isActionClass(javaClass)) { + actionClasses.add(javaClass); + } + } + return actionClasses; + } + + @Override + public void loadClasses(String file, List classes) throws ClassNotFoundException, + IllegalAccessException, InstantiationException, CompilationFailedException, IOException { + dexAndLoadJar(file, classes); + } + + private boolean isActionClass(JavaClass javaClass) { + JavaClass current = javaClass; + while (current != null) { + String superClass = current.getSuperclassName(); + if (superClass == null) { + return false; + } + if (superClass.equals(AnAction.class.getName())) { + return true; + } + current = javaClass.getSuperClass(); + } + return false; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/debug/LoadFileEditorProviderAction.java b/app/src/main/java/com/tyron/code/ui/main/action/debug/LoadFileEditorProviderAction.java new file mode 100644 index 00000000..1c82443f --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/debug/LoadFileEditorProviderAction.java @@ -0,0 +1,114 @@ +package com.tyron.code.ui.main.action.debug; + +import androidx.annotation.NonNull; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.D8; +import com.android.tools.r8.D8Command; +import com.android.tools.r8.OutputMode; +import com.tyron.actions.ActionManager; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.builder.BuildModule; +import com.tyron.code.ui.editor.impl.FileEditorProviderManagerImpl; +import com.tyron.fileeditor.api.FileEditorProvider; + +import org.openjdk.com.sun.org.apache.bcel.internal.classfile.ClassParser; +import org.openjdk.com.sun.org.apache.bcel.internal.classfile.JavaClass; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import dalvik.system.PathClassLoader; + +public class LoadFileEditorProviderAction extends BaseLoadAction { + + @Override + public void update(@NonNull AnActionEvent event) { + super.update(event); + + event.getPresentation().setVisible(true); + event.getPresentation().setText("Load FileEditorProvider"); + } + + @Override + public List getApplicableClasses(String file) throws IOException { + List providerClasses = new ArrayList<>(); + + JarFile jarFile = new JarFile(file); + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (!entry.getName().endsWith(".class")) { + continue; + } + + ClassParser parser = new ClassParser(file, entry.getName()); + JavaClass javaClass = parser.parse(); + + if (isFileEditorProvider(javaClass)) { + providerClasses.add(javaClass); + } + } + return providerClasses; + } + + @Override + public void loadClasses(String file, List classes) throws ClassNotFoundException, IllegalAccessException, InstantiationException, CompilationFailedException, IOException { + dexAndLoadJar(file, classes); + } + + private void dexAndLoadJar(String file, List javaClasses) throws IOException, CompilationFailedException, ClassNotFoundException, InstantiationException, IllegalAccessException { + File jarFile = new File(file); + + Path temp_output = Files.createTempDirectory("temp_output"); + + D8Command command = D8Command.builder() + .addProgramFiles(jarFile.toPath()) + .addLibraryFiles(BuildModule.getAndroidJar().toPath()) + .addLibraryFiles(BuildModule.getLambdaStubs().toPath()) + .setOutput(temp_output, OutputMode.DexIndexed) + .build(); + D8.run(command); + + StringBuilder path = new StringBuilder(); + File[] files = temp_output.toFile().listFiles(); + for (File it : files) { + path.append(it.getAbsolutePath()); + path.append(File.pathSeparator); + } + + PathClassLoader classLoader = new PathClassLoader(path.substring(0, path.length() - 1), + getClass().getClassLoader()); + for (JavaClass javaClass : javaClasses) { + Class aClass = classLoader.loadClass(javaClass.getClassName()); + Class actionClass = aClass.asSubclass(FileEditorProvider.class); + FileEditorProvider provider = actionClass.newInstance(); + FileEditorProviderManagerImpl.getInstance().registerProvider(provider); + } + } + + private boolean isFileEditorProvider(JavaClass javaClass) { + JavaClass current = javaClass; + while (current != null) { + String[] interfaces = current.getInterfaceNames(); + if (interfaces == null) { + return false; + } + for (String anInterface : interfaces) { + if (anInterface.equals(FileEditorProvider.class.getName())) { + return true; + } + } + current = javaClass.getSuperClass(); + } + return false; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/debug/LoadXmlRepositoryAction.java b/app/src/main/java/com/tyron/code/ui/main/action/debug/LoadXmlRepositoryAction.java new file mode 100644 index 00000000..61d2bf58 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/debug/LoadXmlRepositoryAction.java @@ -0,0 +1,36 @@ +package com.tyron.code.ui.main.action.debug; + +import androidx.annotation.NonNull; + +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.Presentation; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.Module; +import com.tyron.xml.completion.repository.ResourceRepository; + +import java.io.IOException; + +public class LoadXmlRepositoryAction extends AnAction { + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(true); + presentation.setText("Load xml repository"); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Project project = e.getRequiredData(CommonDataKeys.PROJECT); + Module mainModule = project.getMainModule(); + ResourceRepository repository = new ResourceRepository((AndroidModule) mainModule); + try { + repository.initialize(); + } catch (IOException ioException) { + ioException.printStackTrace(); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/debug/RunLongRunningTaskAction.java b/app/src/main/java/com/tyron/code/ui/main/action/debug/RunLongRunningTaskAction.java new file mode 100644 index 00000000..81bd3702 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/debug/RunLongRunningTaskAction.java @@ -0,0 +1,43 @@ +package com.tyron.code.ui.main.action.debug; + +import android.app.Activity; +import android.app.ProgressDialog; + +import androidx.annotation.NonNull; + +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.Presentation; +import com.tyron.completion.progress.ProgressManager; + +/** + * Runs a task in the background for five seconds. A progress dialog should appear after 2 seconds. + */ +public class RunLongRunningTaskAction extends AnAction { + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(true); + presentation.setText("Run long running task"); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Activity activity = e.getRequiredData(CommonDataKeys.ACTIVITY); + + ProgressDialog dialog = new ProgressDialog(activity); + Runnable task = () -> { + try { + Thread.sleep(5000); + } catch (InterruptedException interruptedException) { + interruptedException.printStackTrace(); + } + }; + Runnable longRunningRunnable = dialog::show; + Runnable cancelRunnable = dialog::dismiss; + + ProgressManager.getInstance().runNonCancelableAsync(task, longRunningRunnable, cancelRunnable); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/other/FormatAction.java b/app/src/main/java/com/tyron/code/ui/main/action/other/FormatAction.java new file mode 100644 index 00000000..b8c96d66 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/other/FormatAction.java @@ -0,0 +1,42 @@ +package com.tyron.code.ui.main.action.other; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; +import com.tyron.fileeditor.api.FileEditor; +import com.tyron.code.ui.editor.impl.text.rosemoe.CodeEditorFragment; + +public class FormatAction extends AnAction { + + public static final String ID = "formatAction"; + + @Override + public void update(@NonNull AnActionEvent event) { + event.getPresentation().setVisible(false); + if (!ActionPlaces.MAIN_TOOLBAR.equals(event.getPlace())) { + return; + } + + FileEditor fileEditor = event.getData(CommonDataKeys.FILE_EDITOR_KEY); + if (fileEditor == null) { + return; + } + + event.getPresentation().setVisible(true); + event.getPresentation().setText(event.getDataContext().getString(R.string.menu_format)); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + FileEditor fileEditor = e.getRequiredData(CommonDataKeys.FILE_EDITOR_KEY); + Fragment fragment = fileEditor.getFragment(); + if (fragment instanceof CodeEditorFragment) { + ((CodeEditorFragment) fragment).format(); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/other/OpenSettingsAction.java b/app/src/main/java/com/tyron/code/ui/main/action/other/OpenSettingsAction.java new file mode 100644 index 00000000..dfc7a77e --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/other/OpenSettingsAction.java @@ -0,0 +1,41 @@ +package com.tyron.code.ui.main.action.other; + +import android.app.Activity; +import android.content.Intent; + +import androidx.annotation.NonNull; + +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; +import com.tyron.code.ui.settings.SettingsActivity; + +public class OpenSettingsAction extends AnAction { + + public static final String ID = "openSettingsAction"; + + @Override + public void update(@NonNull AnActionEvent event) { + event.getPresentation().setVisible(false); + if (!ActionPlaces.MAIN_TOOLBAR.equals(event.getPlace())) { + return; + } + + Activity activity = event.getData(CommonDataKeys.ACTIVITY); + if (activity == null) { + return; + } + + event.getPresentation().setVisible(true); + event.getPresentation().setText(event.getDataContext().getString(R.string.menu_settings)); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Intent intent = new Intent(); + intent.setClass(e.getDataContext(), SettingsActivity.class); + e.getDataContext().startActivity(intent); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/project/OpenLibraryManagerAction.java b/app/src/main/java/com/tyron/code/ui/main/action/project/OpenLibraryManagerAction.java new file mode 100644 index 00000000..dd785fc8 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/project/OpenLibraryManagerAction.java @@ -0,0 +1,76 @@ +package com.tyron.code.ui.main.action.project; + +import android.app.Activity; +import android.content.Context; +import android.content.ContextWrapper; +import android.view.ContextThemeWrapper; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.Presentation; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.Module; +import com.tyron.code.R; +import com.tyron.code.ui.library.LibraryManagerFragment; + +public class OpenLibraryManagerAction extends AnAction { + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + Context context = event.getDataContext(); + + presentation.setVisible(false); + if (!ActionPlaces.MAIN_TOOLBAR.equals(event.getPlace())) { + return; + } + Project project = event.getData(CommonDataKeys.PROJECT); + if (project == null) { + return; + } + + presentation.setText(context.getString(R.string.menu_library_manager)); + presentation.setVisible(true); + presentation.setEnabled(true); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + Context context = e.getRequiredData(CommonDataKeys.CONTEXT); + context = getActivityContext(context); + + Project project = e.getRequiredData(CommonDataKeys.PROJECT); + Module mainModule = project.getMainModule(); + if (context instanceof AppCompatActivity) { + FragmentManager fragmentManager = + ((AppCompatActivity) context).getSupportFragmentManager(); + Fragment fragment = LibraryManagerFragment.newInstance(mainModule.getRootFile().getAbsolutePath()); + fragmentManager.beginTransaction() + .add(R.id.fragment_container, fragment, LibraryManagerFragment.TAG) + .addToBackStack(LibraryManagerFragment.TAG) + .commit(); + } + } + + private Context getActivityContext(Context context) { + Context current = context; + while (current != null) { + if (current instanceof Activity) { + return current; + } + if (current instanceof ContextWrapper) { + current = ((ContextWrapper) current).getBaseContext(); + } else { + current = null; + } + } + return null; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/project/ProjectActionGroup.java b/app/src/main/java/com/tyron/code/ui/main/action/project/ProjectActionGroup.java new file mode 100644 index 00000000..85d81452 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/project/ProjectActionGroup.java @@ -0,0 +1,55 @@ +package com.tyron.code.ui.main.action.project; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + +import com.tyron.actions.ActionGroup; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.code.R; + +public class ProjectActionGroup extends ActionGroup { + + public static final String ID = "projectActionGroup"; + + @Override + public void update(@NonNull AnActionEvent event) { + if (!ActionPlaces.MAIN_TOOLBAR.equals(event.getPlace())) { + event.getPresentation() + .setVisible(false); + return; + } + + Context context = event.getData(CommonDataKeys.CONTEXT); + if (context == null) { + event.getPresentation() + .setVisible(false); + return; + } + + event.getPresentation() + .setVisible(true); + event.getPresentation() + .setEnabled(true); + event.getPresentation() + .setText(context.getString(R.string.item_project)); + event.getPresentation() + .setIcon(ContextCompat.getDrawable(context, R.drawable.round_folder_24)); + } + + @Override + public boolean isPopup() { + return true; + } + + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return new AnAction[]{new SaveAction(), new RefreshProjectAction(), + new OpenLibraryManagerAction()}; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/project/RefreshProjectAction.java b/app/src/main/java/com/tyron/code/ui/main/action/project/RefreshProjectAction.java new file mode 100644 index 00000000..aba29ae4 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/project/RefreshProjectAction.java @@ -0,0 +1,52 @@ +package com.tyron.code.ui.main.action.project; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.builder.project.Project; +import com.tyron.code.R; +import com.tyron.code.ui.main.IndexCallback; +import com.tyron.code.ui.main.MainFragment; +import com.tyron.code.ui.main.MainViewModel; +import com.tyron.code.ui.project.ProjectManager; + +public class RefreshProjectAction extends AnAction { + + @Override + public void update(@NonNull AnActionEvent event) { + Context context = event.getData(CommonDataKeys.CONTEXT); + Project project = event.getData(CommonDataKeys.PROJECT); + IndexCallback callback = event.getData(MainFragment.INDEX_CALLBACK_KEY); + MainViewModel mainViewModel = event.getData(MainFragment.MAIN_VIEW_MODEL_KEY); + if (!ActionPlaces.MAIN_TOOLBAR.equals(event.getPlace()) + || context == null + || callback == null + || project == null + || mainViewModel == null) { + event.getPresentation().setVisible(false); + return; + } + event.getPresentation().setVisible(true); + event.getPresentation().setEnabled(true); + event.getPresentation().setText(context.getString(R.string.menu_refresh)); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + IndexCallback callback = e.getRequiredData(MainFragment.INDEX_CALLBACK_KEY); + Project project = ProjectManager.getInstance().getCurrentProject(); + MainViewModel viewModel = e.getRequiredData(MainFragment.MAIN_VIEW_MODEL_KEY); + Boolean indexing = viewModel.isIndexing().getValue(); + if (indexing == null) { + indexing = false; + } + if (project != null && !project.isCompiling() && !indexing) { + callback.index(project); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/main/action/project/SaveAction.java b/app/src/main/java/com/tyron/code/ui/main/action/project/SaveAction.java new file mode 100644 index 00000000..21017ac5 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/main/action/project/SaveAction.java @@ -0,0 +1,117 @@ +package com.tyron.code.ui.main.action.project; + +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.tyron.actions.ActionPlaces; +import com.tyron.actions.AnAction; +import com.tyron.actions.AnActionEvent; +import com.tyron.actions.CommonDataKeys; +import com.tyron.actions.Presentation; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.FileManager; +import com.tyron.builder.project.api.Module; +import com.tyron.code.R; +import com.tyron.code.ui.editor.Savable; +import com.tyron.code.ui.main.MainFragment; +import com.tyron.code.ui.main.MainViewModel; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.fileeditor.api.FileEditor; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class SaveAction extends AnAction { + + @Override + public void update(@NonNull AnActionEvent event) { + Presentation presentation = event.getPresentation(); + presentation.setVisible(false); + + if (!ActionPlaces.MAIN_TOOLBAR.equals(event.getPlace())) { + return; + } + + Project project = event.getData(CommonDataKeys.PROJECT); + if (project == null) { + return; + } + + MainViewModel mainViewModel = event.getData(MainFragment.MAIN_VIEW_MODEL_KEY); + if (mainViewModel == null) { + return; + } + + presentation.setVisible(true); + presentation.setText("Save"); + } + + @Override + public void actionPerformed(@NonNull AnActionEvent e) { + MainViewModel viewModel = e.getRequiredData(MainFragment.MAIN_VIEW_MODEL_KEY); + List editors = viewModel.getFiles() + .getValue(); + if (editors == null) { + return; + } + + Stream validEditors = editors.stream() + .filter(it -> it.getFragment() instanceof Savable) + .filter(it -> ((Savable) it.getFragment()).canSave()); + List filesToSave = validEditors + .map(FileEditor::getFile) + .collect(Collectors.toList()); + + Project project = e.getRequiredData(CommonDataKeys.PROJECT); + ProgressManager.getInstance() + .runNonCancelableAsync(() -> { + List exceptions = saveFiles(project, filesToSave); + if (!exceptions.isEmpty()) { + new MaterialAlertDialogBuilder(e.getDataContext()).setTitle(R.string.error) + .setPositiveButton(android.R.string.ok, null) + .setMessage(exceptions.stream() + .map(IOException::getMessage) + .collect(Collectors.joining("\n\n"))) + .show(); + } + }); + } + + @WorkerThread + private static List saveFiles(Project project, List files) { + List exceptions = new ArrayList<>(); + for (File file : files) { + Module module = project.getModule(file); + if (module == null) { + // TODO: try to save files without a module + continue; + } + + FileManager fileManager = module.getFileManager(); + Optional fileContent = fileManager.getFileContent(file); + if (fileContent.isPresent()) { + try { + FileUtils.writeStringToFile(file, fileContent.get() + .toString(), StandardCharsets.UTF_8); + Instant instant = Instant.ofEpochMilli(file.lastModified()); + + ProgressManager.getInstance() + .runLater(() -> fileManager.setLastModified(file, instant)); + } catch (IOException e) { + exceptions.add(e); + } + } + } + return exceptions; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/project/DependencyManager.java b/app/src/main/java/com/tyron/code/ui/project/DependencyManager.java new file mode 100644 index 00000000..251e3c5a --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/project/DependencyManager.java @@ -0,0 +1,263 @@ +package com.tyron.code.ui.project; + +import android.util.Log; + +import com.google.common.collect.ImmutableList; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import com.tyron.builder.log.ILogger; +import com.tyron.builder.model.Library; +import com.tyron.builder.project.api.JavaModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ApplicationLoader; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.code.util.DependencyUtils; +import com.tyron.common.util.Decompress; +import com.tyron.resolver.DependencyResolver; +import com.tyron.resolver.RepositoryModel; +import com.tyron.resolver.model.Dependency; +import com.tyron.resolver.model.Pom; +import com.tyron.resolver.repository.LocalRepository; +import com.tyron.resolver.repository.RemoteRepository; +import com.tyron.resolver.repository.Repository; +import com.tyron.resolver.repository.RepositoryManager; +import com.tyron.resolver.repository.RepositoryManagerImpl; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.zip.ZipFile; + +public class DependencyManager { + + private static final String REPOSITORIES_JSON = "repositories.json"; + + private final RepositoryManager mRepository; + private final DependencyResolver mResolver; + + public DependencyManager(JavaModule module, File cacheDir) throws IOException { + extractCommonPomsIfNeeded(); + + mRepository = new RepositoryManagerImpl(); + mRepository.setCacheDirectory(cacheDir); + for (Repository repository : getFromModule(module)) { + mRepository.addRepository(repository); + } + mRepository.initialize(); + mResolver = new DependencyResolver(mRepository); + } + + public static List getFromModule(JavaModule module) throws IOException { + File rootFile = module.getRootFile(); + File repositoriesFile = new File(rootFile, REPOSITORIES_JSON); + List repositoryModels = parseFile(repositoriesFile); + List repositories = new ArrayList<>(); + for (RepositoryModel model : repositoryModels) { + if (model.getName() == null) { + continue; + } + if (model.getUrl() == null) { + repositories.add(new LocalRepository(model.getName())); + } else { + repositories.add(new RemoteRepository(model.getName(), model.getUrl())); + } + } + return repositories; + } + + public static List parseFile(File file) throws IOException { + try { + String contents = FileUtils.readFileToString(file, StandardCharsets.UTF_8); + Type type = new TypeToken>(){}.getType(); + List models = new GsonBuilder() + .setLenient() + .create() + .fromJson(contents, type); + if (models != null) { + return models; + } + } catch (JsonSyntaxException e) { + // returning an empty list for now, should probably log this + return Collections.emptyList(); + } catch (IOException ignored) { + // add default ones + } + + List defaultRepositories = getDefaultRepositories(); + String jsonContents = new GsonBuilder() + .setPrettyPrinting() + .create() + .toJson(defaultRepositories); + FileUtils.writeStringToFile(file, jsonContents, StandardCharsets.UTF_8); + return defaultRepositories; + } + + public static List getDefaultRepositories() { + return ImmutableList.builder() + .add(new RepositoryModel("maven", "https://repo1.maven.org/maven2")) + .add(new RepositoryModel("google-maven", "https://maven.google.com")) + .add(new RepositoryModel("jitpack", "https://jitpack.io")) + .add(new RepositoryModel("jcenter", "https://jcenter.bintray.com")) + .build(); + } + + private void extractCommonPomsIfNeeded() { + File cacheDir = ApplicationLoader.applicationContext.getExternalFilesDir("cache"); + File pomsDir = new File(cacheDir, "google-maven"); + File[] children = pomsDir.listFiles(); + if (!pomsDir.exists() || children == null || children.length == 0) { + Decompress.unzipFromAssets(ApplicationLoader.applicationContext, "google-maven.zip", pomsDir.getParent()); + } + } + + public void resolve(JavaModule project, ProjectManager.TaskListener listener, ILogger logger) throws IOException { + listener.onTaskStarted("Resolving dependencies"); + + mResolver.setResolveListener(new DependencyResolver.ResolveListener() { + @Override + public void onResolve(String message) { + listener.onTaskStarted(message); + } + + @Override + public void onFailure(String message) { + logger.error(message); + } + }); + + List declaredDependencies = DependencyUtils.parseLibraries(project.getLibraryFile(), logger); + List resolvedPoms = mResolver.resolveDependencies(declaredDependencies); + + listener.onTaskStarted("Downloading dependencies"); + List files = getFiles(resolvedPoms, logger); + + listener.onTaskStarted("Checking dependencies"); + checkLibraries(project, logger, files); + } + + private void checkLibraries(JavaModule project, ILogger logger, List newLibraries) throws IOException { + Set libraries = new HashSet<>(newLibraries); + + Map fileLibsHashes = new HashMap<>(); + File[] fileLibraries = project.getLibraryDirectory().listFiles(c -> + c.getName().endsWith(".aar") || c.getName().endsWith(".jar")); + if (fileLibraries != null) { + for (File fileLibrary : fileLibraries) { + try { + ZipFile zipFile = new ZipFile(fileLibrary); + Library library = new Library(); + library.setSourceFile(fileLibrary); + fileLibsHashes.put(AndroidUtilities.calculateMD5(fileLibrary), library); + } catch (IOException e) { + String message = "File " + fileLibrary + + " is corrupt! Ignoring."; + logger.warning(message); + } + } + } + + + String librariesString = project.getSettings().getString("libraries", "[]"); + try { + List parsedLibraries = new Gson().fromJson(librariesString, new TypeToken>() { + }.getType()); + if (parsedLibraries != null) { + for (Library parsedLibrary : parsedLibraries) { + if (!libraries.contains(parsedLibrary)) { + Log.d("LibraryCheck", "Removed library" + parsedLibrary); + } else { + libraries.add(parsedLibrary); + } + } + } + } catch (Exception ignore) { + + } + + Map md5Map = new HashMap<>(); + libraries.forEach(it -> + md5Map.put(AndroidUtilities.calculateMD5(it.getSourceFile()), it)); + File buildLibs = new File(project.getBuildDirectory(), "libs"); + File[] buildLibraryDirs = buildLibs.listFiles(File::isDirectory); + if (buildLibraryDirs != null) { + for (File libraryDir : buildLibraryDirs) { + String md5Hash = libraryDir.getName(); + if (!md5Map.containsKey(md5Hash) && !fileLibsHashes.containsKey(md5Hash)) { + FileUtils.deleteDirectory(libraryDir); + Log.d("LibraryCheck", "Deleting contents of " + md5Hash); + } + } + } + + saveLibraryToProject(project, md5Map, fileLibsHashes); + } + + private void saveLibraryToProject(Module module, Map libraries, Map fileLibraries) throws IOException { + Map combined = new HashMap<>(); + combined.putAll(libraries); + combined.putAll(fileLibraries); + + if (module instanceof JavaModule) { + ((JavaModule) module).putLibraryHashes(combined); + } + + for (Map.Entry entry : combined.entrySet()) { + String hash = entry.getKey(); + Library library = entry.getValue(); + + File libraryDir = new File(module.getBuildDirectory(), "libs/" + hash); + if (!libraryDir.exists()) { + libraryDir.mkdir(); + } else { + continue; + } + + if (library.getSourceFile().getName().endsWith(".jar")) { + FileUtils.copyFileToDirectory(library.getSourceFile(), libraryDir); + + File file = new File(libraryDir, library.getSourceFile().getName()); + file.renameTo(new File(libraryDir, "classes.jar")); + } else if (library.getSourceFile().getName().endsWith(".aar")) { + Decompress.unzip(library.getSourceFile().getAbsolutePath(), + libraryDir.getAbsolutePath()); + } + } + + String librariesString = new Gson().toJson(libraries.values()); + module.getSettings().edit() + .putString("libraries", librariesString) + .apply(); + } + + public List getFiles(List resolvedPoms, + ILogger logger) { + List files = new ArrayList<>(); + for (Pom resolvedPom : resolvedPoms) { + try { + File file = mRepository.getLibrary(resolvedPom); + if (file != null) { + Library library = new Library(); + library.setSourceFile(file); + library.setDeclaration(resolvedPom.getDeclarationString()); + files.add(library); + } + } catch (IOException e) { + logger.error("Unable to download " + resolvedPom + ": " + e.getMessage()); + } + } + return files; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/project/ProjectManager.java b/app/src/main/java/com/tyron/code/ui/project/ProjectManager.java new file mode 100644 index 00000000..2d20649a --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/project/ProjectManager.java @@ -0,0 +1,271 @@ +package com.tyron.code.ui.project; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.common.base.Charsets; +import com.google.common.base.Throwables; +import com.tyron.builder.compiler.BuildType; +import com.tyron.builder.compiler.incremental.resource.IncrementalAapt2Task; +import com.tyron.builder.compiler.manifest.ManifestMergeTask; +import com.tyron.builder.exception.CompilationFailedException; +import com.tyron.builder.log.ILogger; +import com.tyron.builder.model.SourceFileObject; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.AndroidModule; +import com.tyron.builder.project.api.JavaModule; +import com.tyron.builder.project.api.Module; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.template.CodeTemplate; +import com.tyron.code.util.ProjectUtils; +import com.tyron.common.logging.IdeLog; +import com.tyron.completion.index.CompilerService; +import com.tyron.completion.java.compiler.CompilerContainer; +import com.tyron.completion.java.JavaCompilerProvider; +import com.tyron.completion.java.compiler.JavaCompilerService; +import com.tyron.completion.java.provider.CompletionEngine; +import com.tyron.completion.progress.ProgressManager; +import com.tyron.completion.xml.XmlIndexProvider; +import com.tyron.completion.xml.XmlRepository; +import com.tyron.completion.xml.task.InjectResourcesTask; +import com.tyron.viewbinding.task.InjectViewBindingTask; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.logging.Logger; + +import kotlin.collections.CollectionsKt; + +public class ProjectManager { + + private static final Logger LOG = IdeLog.getCurrentLogger(ProjectManager.class); + + public interface TaskListener { + void onTaskStarted(String message); + + void onComplete(Project project, boolean success, String message); + } + + public interface OnProjectOpenListener { + void onProjectOpen(Project project); + } + + private static volatile ProjectManager INSTANCE = null; + + public static synchronized ProjectManager getInstance() { + if (INSTANCE == null) { + INSTANCE = new ProjectManager(); + } + return INSTANCE; + } + + private final List mProjectOpenListeners = new ArrayList<>(); + private volatile Project mCurrentProject; + + private ProjectManager() { + + } + + public void addOnProjectOpenListener(OnProjectOpenListener listener) { + if (!CompletionEngine.isIndexing() && mCurrentProject != null) { + listener.onProjectOpen(mCurrentProject); + } + mProjectOpenListeners.add(listener); + } + + public void removeOnProjectOpenListener(OnProjectOpenListener listener) { + mProjectOpenListeners.remove(listener); + } + + public void openProject(Project project, + boolean downloadLibs, + TaskListener listener, + ILogger logger) { + ProgressManager.getInstance() + .runNonCancelableAsync( + () -> doOpenProject(project, downloadLibs, listener, logger)); + } + + private void doOpenProject(Project project, + boolean downloadLibs, + TaskListener mListener, + ILogger logger) { + mCurrentProject = project; + + boolean shouldReturn = false; + // Index the project after downloading dependencies so it will get added to classpath + try { + mCurrentProject.open(); + } catch (IOException exception) { + logger.warning("Failed to open project: " + exception.getMessage()); + shouldReturn = true; + } + mProjectOpenListeners.forEach(it -> it.onProjectOpen(mCurrentProject)); + + if (shouldReturn) { + mListener.onComplete(project, false, "Failed to open project."); + return; + } + + try { + mCurrentProject.setIndexing(true); + mCurrentProject.index(); + } catch (IOException exception) { + logger.warning("Failed to open project: " + exception.getMessage()); + } + + Module module = mCurrentProject.getMainModule(); + + if (module instanceof JavaModule) { + JavaModule javaModule = (JavaModule) module; + try { + downloadLibraries(javaModule, mListener, logger); + } catch (IOException e) { + logger.error(e.getMessage()); + } + } + + + if (module instanceof AndroidModule) { + mListener.onTaskStarted("Generating resource files."); + + ManifestMergeTask manifestMergeTask = + new ManifestMergeTask(project, (AndroidModule) module, logger); + IncrementalAapt2Task task = + new IncrementalAapt2Task(project, (AndroidModule) module, logger, false); + try { + manifestMergeTask.prepare(BuildType.DEBUG); + manifestMergeTask.run(); + + task.prepare(BuildType.DEBUG); + task.run(); + } catch (IOException | CompilationFailedException e) { + logger.warning("Unable to generate resource classes " + e.getMessage()); + } + } + + if (module instanceof JavaModule) { + if (module instanceof AndroidModule) { + mListener.onTaskStarted("Indexing XML files."); + + XmlIndexProvider index = CompilerService.getInstance() + .getIndex(XmlIndexProvider.KEY); + index.clear(); + + XmlRepository xmlRepository = index.get(project, module); + try { + xmlRepository.initialize((AndroidModule) module); + } catch (IOException e) { + String message = "Unable to initialize resource repository. " + + "Resource code completion might be incomplete or unavailable. \n" + + "Reason: " + e.getMessage(); + LOG.warning(message); + } + } + + mListener.onTaskStarted("Indexing"); + try { + JavaCompilerProvider provider = CompilerService.getInstance() + .getIndex(JavaCompilerProvider.KEY); + JavaCompilerService service = provider.get(project, module); + + if (module instanceof AndroidModule) { + InjectResourcesTask.inject(project, (AndroidModule) module); + InjectViewBindingTask.inject(project, (AndroidModule) module); + } + + JavaModule javaModule = ((JavaModule) module); + Collection files = javaModule.getJavaFiles().values(); + File first = CollectionsKt.firstOrNull(files); + if (first != null) { + service.compile(first.toPath()); + } + } catch (Throwable e) { + String message = + "Failure indexing project.\n" + Throwables.getStackTraceAsString(e); + mListener.onComplete(project, false, message); + } + } + + mCurrentProject.setIndexing(false); + mListener.onComplete(project, true, "Index successful"); + } + + private void downloadLibraries(JavaModule project, + TaskListener listener, + ILogger logger) throws IOException { + DependencyManager manager = new DependencyManager(project, + ApplicationLoader.applicationContext.getExternalFilesDir( + "cache")); + manager.resolve(project, listener, logger); + } + + public void closeProject(@NonNull Project project) { + if (project.equals(mCurrentProject)) { + mCurrentProject = null; + } + } + + public synchronized Project getCurrentProject() { + return mCurrentProject; + } + + public static File createFile(File directory, + String name, + CodeTemplate template) throws IOException { + if (!directory.isDirectory()) { + return null; + } + + String code = template.get() + .replace(CodeTemplate.CLASS_NAME, name); + + File classFile = new File(directory, name + template.getExtension()); + if (classFile.exists()) { + return null; + } + if (!classFile.createNewFile()) { + return null; + } + + FileUtils.writeStringToFile(classFile, code, Charsets.UTF_8); + return classFile; + } + + @Nullable + public static File createClass(File directory, + String className, + CodeTemplate template) throws IOException { + if (!directory.isDirectory()) { + return null; + } + + String packageName = ProjectUtils.getPackageName(directory); + if (packageName == null) { + return null; + } + + String code = template.get() + .replace(CodeTemplate.PACKAGE_NAME, packageName) + .replace(CodeTemplate.CLASS_NAME, className); + + File classFile = new File(directory, className + template.getExtension()); + if (classFile.exists()) { + return null; + } + if (!classFile.createNewFile()) { + return null; + } + + FileUtils.writeStringToFile(classFile, code, Charsets.UTF_8); + return classFile; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/project/ProjectManagerFragment.java b/app/src/main/java/com/tyron/code/ui/project/ProjectManagerFragment.java new file mode 100644 index 00000000..e909173c --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/project/ProjectManagerFragment.java @@ -0,0 +1,398 @@ +package com.tyron.code.ui.project; + +import android.Manifest; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.core.content.ContextCompat; +import androidx.core.view.ViewKt; +import androidx.core.widget.NestedScrollView; +import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.transition.TransitionManager; + +import com.github.angads25.filepicker.model.DialogConfigs; +import com.github.angads25.filepicker.model.DialogProperties; +import com.google.android.material.appbar.MaterialToolbar; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; +import com.google.android.material.transition.MaterialFade; +import com.google.android.material.transition.MaterialFadeThrough; +import com.google.android.material.transition.MaterialSharedAxis; +import com.tyron.builder.project.Project; +import com.tyron.code.R; +import com.tyron.code.ui.file.FilePickerDialogFixed; +import com.tyron.code.ui.main.MainFragment; +import com.tyron.code.ui.project.adapter.ProjectManagerAdapter; +import com.tyron.code.ui.settings.SettingsActivity; +import com.tyron.code.ui.wizard.WizardFragment; +import com.tyron.code.util.UiUtilsKt; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.completion.progress.ProgressManager; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.Executors; + +public class ProjectManagerFragment extends Fragment { + + public static final String TAG = ProjectManagerFragment.class.getSimpleName(); + + private SharedPreferences mPreferences; + private RecyclerView mRecyclerView; + private ProjectManagerAdapter mAdapter; + private ExtendedFloatingActionButton mCreateProjectFab; + private boolean mShowDialogOnPermissionGrant; + private ActivityResultLauncher mPermissionLauncher; + private final ActivityResultContracts.RequestMultiplePermissions mPermissionsContract = + new ActivityResultContracts.RequestMultiplePermissions(); + + private String mPreviousPath; + + private FilePickerDialogFixed mDirectoryPickerDialog; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setExitTransition(new MaterialSharedAxis(MaterialSharedAxis.X, false)); + mPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); + mPermissionLauncher = registerForActivityResult(mPermissionsContract, isGranted -> { + if (isGranted.containsValue(false)) { + new MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.project_manager_permission_denied) + .setMessage(R.string.project_manager_android11_notice) + .setPositiveButton(R.string.project_manager_button_request_again, (d, which) -> { + mShowDialogOnPermissionGrant = true; + requestPermissions(); + }) + .setNegativeButton(R.string.project_manager_button_continue, (d, which) -> { + mShowDialogOnPermissionGrant = false; + setSavePath(Environment.getExternalStorageDirectory().getAbsolutePath()); + }) + .show(); + setSavePath(Environment.getExternalStorageDirectory().getAbsolutePath()); + } else { + if (mShowDialogOnPermissionGrant) { + mShowDialogOnPermissionGrant = false; + showDirectorySelectDialog(); + } + } + }); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + MaterialToolbar toolbar = view.findViewById(R.id.toolbar); + toolbar.setTitle(R.string.app_name); + + + toolbar.inflateMenu(R.menu.project_list_fragment_menu); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // can't change project path on android R + toolbar.getMenu().removeItem(R.id.projects_path); + } + toolbar.setOnMenuItemClickListener(item -> { + int id = item.getItemId(); + + if (id == R.id.projects_path) { + checkSavePath(); + return true; + } + + if (id == R.id.menu_settings) { + Intent intent = new Intent(); + intent.setClass(requireActivity(), SettingsActivity.class); + startActivity(intent); + return true; + } + + return true; + }); + + + mCreateProjectFab = view.findViewById(R.id.create_project_fab); + mCreateProjectFab.setOnClickListener(v -> { + WizardFragment wizardFragment = new WizardFragment(); + wizardFragment.setOnProjectCreatedListener(this::openProject); + getParentFragmentManager().beginTransaction() + .replace(R.id.fragment_container, wizardFragment) + .addToBackStack(null) + .commit(); + }); + UiUtilsKt.addSystemWindowInsetToMargin(mCreateProjectFab, false, false, false, true); + + mAdapter = new ProjectManagerAdapter(); + mAdapter.setOnProjectSelectedListener(this::openProject); + mAdapter.setOnProjectLongClickListener(this::inflateProjectMenus); + mRecyclerView = view.findViewById(R.id.projects_recycler); + mRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); + mRecyclerView.setAdapter(mAdapter); + } + + private boolean inflateProjectMenus(View view, Project project) { + view.setOnCreateContextMenuListener((menu, v, menuInfo) -> { + menu.add(R.string.dialog_delete) + .setOnMenuItemClickListener(item -> { + String message = getString(R.string.dialog_confirm_delete, + project.getRootFile().getName()); + new MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.dialog_delete) + .setMessage(message) + .setPositiveButton(android.R.string.yes, + (d, which) -> deleteProject(project)) + .setNegativeButton(android.R.string.no, null) + .show(); + return true; + }); + }); + view.showContextMenu(); + return true; + } + + private void deleteProject(Project project) { + Executors.newSingleThreadExecutor().execute(() -> { + try { + FileUtils.forceDelete(project.getRootFile()); + if (getActivity() != null) { + requireActivity().runOnUiThread(() -> { + AndroidUtilities.showSimpleAlert( + requireContext(), + getString(R.string.success), + getString(R.string.delete_success)); + loadProjects(); + }); + } + } catch (IOException e) { + if (getActivity() != null) { + requireActivity().runOnUiThread(() -> + AndroidUtilities.showSimpleAlert(requireContext(), + getString(R.string.error), + e.getMessage())); + } + } + }); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.project_manager_fragment, container, false); + } + + @Override + public void onResume() { + super.onResume(); + + checkSavePath(); + } + + private void checkSavePath() { + String path = mPreferences.getString(SharedPreferenceKeys.PROJECT_SAVE_PATH, null); + if (path == null && Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + if (permissionsGranted()) { + showDirectorySelectDialog(); + } else if (shouldShowRequestPermissionRationale()) { + if (shouldShowRequestPermissionRationale()) { + new MaterialAlertDialogBuilder(requireContext()) + .setMessage(R.string.project_manager_permission_rationale) + .setPositiveButton(R.string.project_manager_button_allow, (d, which) -> { + mShowDialogOnPermissionGrant = true; + requestPermissions(); + }) + .setNegativeButton(R.string.project_manager_button_use_internal, (d, which) -> + setSavePath(Environment.getExternalStorageDirectory().getAbsolutePath())) + .setTitle(R.string.project_manager_rationale_title) + .show(); + } + } else { + requestPermissions(); + } + } else { + loadProjects(); + } + } + + private void setSavePath(String path) { + mPreferences.edit() + .putString(SharedPreferenceKeys.PROJECT_SAVE_PATH, path) + .apply(); + loadProjects(); + } + + @VisibleForTesting + String getPreviousPath() { + return mPreviousPath; + } + + @VisibleForTesting + FilePickerDialogFixed getDirectoryPickerDialog() { + return mDirectoryPickerDialog; + } + + private void showDirectorySelectDialog() { + DialogProperties properties = new DialogProperties(); + properties.selection_type = DialogConfigs.DIR_SELECT; + properties.selection_mode = DialogConfigs.SINGLE_MODE; + properties.root = Environment.getExternalStorageDirectory(); + if (mPreviousPath != null && new File(mPreviousPath).exists()) { + properties.offset = new File(mPreviousPath); + } + FilePickerDialogFixed dialogFixed = new FilePickerDialogFixed(requireContext(), properties); + dialogFixed.setTitle(R.string.project_manager_save_location_title); + dialogFixed.setDialogSelectionListener(files -> { + setSavePath(files[0]); + loadProjects(); + }); + dialogFixed.setOnDismissListener(__ -> { + mPreviousPath = dialogFixed.getCurrentPath(); + mDirectoryPickerDialog = null; + }); + dialogFixed.show(); + + mDirectoryPickerDialog = dialogFixed; + } + + private boolean permissionsGranted() { + return ContextCompat.checkSelfPermission(requireContext(), + Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission(requireContext(), + Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } + + private boolean shouldShowRequestPermissionRationale() { + return shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE) || + shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE); + } + + private void requestPermissions() { + mPermissionLauncher.launch( + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE}); + } + + private void openProject(Project project) { + MainFragment fragment = MainFragment.newInstance(project.getRootFile().getAbsolutePath()); + getParentFragmentManager().beginTransaction() + .replace(R.id.fragment_container, fragment) + .addToBackStack(null) + .commit(); + } + + private void loadProjects() { + toggleLoading(true); + + Executors.newSingleThreadExecutor().execute(() -> { + String path; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + path = requireContext().getExternalFilesDir("Projects").getAbsolutePath(); + } else { + path = mPreferences.getString(SharedPreferenceKeys.PROJECT_SAVE_PATH, + requireContext().getExternalFilesDir("Projects").getAbsolutePath()); + } + File projectDir = new File(path); + File[] directories = projectDir.listFiles(File::isDirectory); + + List projects = new ArrayList<>(); + if (directories != null) { + Arrays.sort(directories, Comparator.comparingLong(File::lastModified)); + for (File directory : directories) { + File appModule = new File(directory, "app"); + if (appModule.exists()) { + Project project = new Project(new File(directory.getAbsolutePath() + .replaceAll("%20", " "))); + // if (project.isValidProject()) { + projects.add(project); + // } + } + } + } + + if (getActivity() != null) { + requireActivity().runOnUiThread(() -> { + toggleLoading(false); + ProgressManager.getInstance().runLater(() -> { + mAdapter.submitList(projects); + toggleNullProject(projects); + }, 300); + }); + } + }); + } + + private void toggleNullProject(List projects) { + ProgressManager.getInstance().runLater(() -> { + if (getActivity() == null || isDetached()) { + return; + } + View view = getView(); + if (view == null) { + return; + } + + View recycler = view.findViewById(R.id.projects_recycler); + View empty = view.findViewById(R.id.empty_projects); + + TransitionManager.beginDelayedTransition( + (ViewGroup) recycler.getParent(), new MaterialFade()); + if (projects.size() == 0) { + recycler.setVisibility(View.GONE); + empty.setVisibility(View.VISIBLE); + } else { + recycler.setVisibility(View.VISIBLE); + empty.setVisibility(View.GONE); + } + }, 300); + } + + private void toggleLoading(boolean show) { + ProgressManager.getInstance().runLater(() -> { + if (getActivity() == null || isDetached()) { + return; + } + View view = getView(); + if (view == null) { + return; + } + View recycler = view.findViewById(R.id.projects_recycler); + View empty = view.findViewById(R.id.empty_container); + View empty_project = view.findViewById(R.id.empty_projects); + empty_project.setVisibility(View.GONE); + + TransitionManager.beginDelayedTransition((ViewGroup) recycler.getParent(), + new MaterialFade()); + if (show) { + recycler.setVisibility(View.GONE); + empty.setVisibility(View.VISIBLE); + } else { + recycler.setVisibility(View.VISIBLE); + empty.setVisibility(View.GONE); + } + }, 300); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/project/adapter/ProjectManagerAdapter.java b/app/src/main/java/com/tyron/code/ui/project/adapter/ProjectManagerAdapter.java new file mode 100644 index 00000000..2899f104 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/project/adapter/ProjectManagerAdapter.java @@ -0,0 +1,170 @@ +package com.tyron.code.ui.project.adapter; + +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.imageview.ShapeableImageView; +import com.tyron.builder.project.Project; +import com.tyron.builder.project.api.Module; +import com.tyron.code.R; + +import java.util.ArrayList; +import java.util.List; + +public class ProjectManagerAdapter extends RecyclerView.Adapter { + + private static final int TYPE_EMPTY = -1; + private static final int TYPE_ITEM = 1; + + public interface OnProjectSelectedListener { + void onProjectSelect(Project project); + } + + public interface OnProjectLongClickedListener { + boolean onLongClicked(View view, Project project); + } + + private final List mProjects = new ArrayList<>(); + private OnProjectLongClickedListener mLongClickListener; + private OnProjectSelectedListener mListener; + + public ProjectManagerAdapter() { + + } + + public void setOnProjectSelectedListener(OnProjectSelectedListener listener) { + mListener = listener; + } + + public void setOnProjectLongClickListener(OnProjectLongClickedListener listener) { + mLongClickListener = listener; + } + + public void submitList(@NonNull List projects) { + DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return mProjects.size(); + } + + @Override + public int getNewListSize() { + return projects.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return mProjects.get(oldItemPosition).equals(projects.get(newItemPosition)); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + return mProjects.get(oldItemPosition).equals(projects.get(newItemPosition)); + } + }); + mProjects.clear(); + mProjects.addAll(projects); + diffResult.dispatchUpdatesTo(this); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + FrameLayout root = new FrameLayout(parent.getContext()); + root.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + final ViewHolder holder; + if (viewType == TYPE_EMPTY) { + holder = new EmptyViewHolder(root); + } else { + holder = new ItemViewHolder(root); + } + + root.setOnClickListener(v -> { + if (mListener != null) { + int position = holder.getBindingAdapterPosition(); + if (position != RecyclerView.NO_POSITION) { + mListener.onProjectSelect(mProjects.get(position)); + } + } + }); + root.setOnLongClickListener(v -> { + if (mLongClickListener != null) { + int position = holder.getBindingAdapterPosition(); + if (position != RecyclerView.NO_POSITION) { + return mLongClickListener.onLongClicked(v, mProjects.get(position)); + } + } + return false; + }); + return holder; + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + if (holder.getItemViewType() == TYPE_ITEM) { + ((ItemViewHolder) holder).bind(mProjects.get(position)); + } + } + + @Override + public int getItemCount() { + return mProjects.size(); + } + + @Override + public int getItemViewType(int position) { + if (mProjects.isEmpty()) { + return TYPE_EMPTY; + } + + return TYPE_ITEM; + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + public ViewHolder(View view) { + super(view); + } + } + + private static class ItemViewHolder extends ViewHolder { + + public ShapeableImageView icon; + public TextView title; + + public ItemViewHolder(FrameLayout view) { + super(view); + LayoutInflater.from(view.getContext()) + .inflate(R.layout.project_item, view); + icon = view.findViewById(R.id.icon); + title = view.findViewById(R.id.title); + } + + public void bind(Project module) { + title.setText(module.getRootFile().getName()); + } + } + + private static class EmptyViewHolder extends ViewHolder { + + public final TextView text; + + public EmptyViewHolder(FrameLayout view) { + super(view); + + text = new TextView(view.getContext()); + text.setTextSize(18); + text.setText(R.string.project_manager_empty); + view.addView(text, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.CENTER)); + } + } +} diff --git a/app/src/main/java/com/tyron/code/ui/settings/AboutUsFragment.java b/app/src/main/java/com/tyron/code/ui/settings/AboutUsFragment.java new file mode 100644 index 00000000..571bcd11 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/settings/AboutUsFragment.java @@ -0,0 +1,92 @@ +package com.tyron.code.ui.settings; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; + +import androidx.annotation.DrawableRes; +import androidx.annotation.Nullable; +import androidx.core.content.res.ResourcesCompat; + +import com.danielstone.materialaboutlibrary.ConvenienceBuilder; +import com.danielstone.materialaboutlibrary.MaterialAboutFragment; +import com.danielstone.materialaboutlibrary.items.MaterialAboutActionItem; +import com.danielstone.materialaboutlibrary.model.MaterialAboutCard; +import com.danielstone.materialaboutlibrary.model.MaterialAboutList; +import com.danielstone.materialaboutlibrary.util.OpenSourceLicense; +import com.google.android.material.transition.MaterialSharedAxis; +import com.tyron.code.R; + +public class AboutUsFragment extends MaterialAboutFragment { + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setEnterTransition(new MaterialSharedAxis(MaterialSharedAxis.X, false)); + setExitTransition(new MaterialSharedAxis(MaterialSharedAxis.X, true)); + } + + @Override + protected MaterialAboutList getMaterialAboutList(Context context) { + MaterialAboutCard appCard = new MaterialAboutCard.Builder() + .addItem(ConvenienceBuilder.createAppTitleItem(context)) + .addItem(new MaterialAboutActionItem.Builder() + .subText(R.string.app_description) + .build()) + .addItem(ConvenienceBuilder.createVersionActionItem(context, + getDrawable(R.drawable.ic_round_info_24), + getString(R.string.app_version), + true)) + .addItem(ConvenienceBuilder.createEmailItem(context, + getDrawable(R.drawable.ic_round_email_24), + getString(R.string.settings_about_us_title), + false, + "contact.tyronscott@gmail.com", + "")) + .addItem(ConvenienceBuilder.createWebsiteActionItem(context, + getDrawable(R.drawable.ic_baseline_open_in_new_24), + getString(R.string.app_source_title), + false, + Uri.parse("https://github.com/tyron12233/CodeAssist"))) + .addItem(ConvenienceBuilder.createRateActionItem(context, + getDrawable(R.drawable.ic_round_star_rate_24), + getString(R.string.rate_us), + null)) + .build(); + + MaterialAboutCard communityCard = new MaterialAboutCard.Builder() + .title(R.string.community) + .addItem(ConvenienceBuilder.createWebsiteActionItem(context, + getDrawable(R.drawable.ic_icons8_discord), + "Discord", + false, + Uri.parse("https://discord.gg/pffnyE6prs"))) + .addItem(ConvenienceBuilder.createWebsiteActionItem(context, + getDrawable(R.drawable.ic_icons8_telegram_app), + "Telegram", + false, + Uri.parse("https://t.me/codeassist_app"))) + .build(); + + MaterialAboutCard licenseCard = ConvenienceBuilder.createLicenseCard(context, + getDrawable(R.drawable.ic_baseline_menu_book_24), + getString(R.string.app_name), + "2022", + "Tyron", + OpenSourceLicense.GNU_GPL_3); + + return new MaterialAboutList.Builder() + .addCard(appCard) + .addCard(communityCard) + .addCard(licenseCard) + .build(); + } + + private Drawable getDrawable(@DrawableRes int drawable) { + return ResourcesCompat.getDrawable(requireContext().getResources(), + drawable, + requireContext().getTheme()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/settings/ApplicationSettingsFragment.java b/app/src/main/java/com/tyron/code/ui/settings/ApplicationSettingsFragment.java new file mode 100644 index 00000000..2773ef74 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/settings/ApplicationSettingsFragment.java @@ -0,0 +1,67 @@ +package com.tyron.code.ui.settings; + +import android.app.UiModeManager; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatDelegate; +import androidx.core.content.SharedPreferencesKt; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; + +import com.tyron.code.R; +import com.tyron.common.SharedPreferenceKeys; + +public class ApplicationSettingsFragment extends PreferenceFragmentCompat { + + public static class ThemeProvider { + + private final Context context; + + public ThemeProvider(Context context) { + this.context = context; + } + + public int getThemeFromPreferences() { + SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); + String selectedTheme = preferences.getString("theme", "default"); + return getTheme(selectedTheme); + } + + public String getDescriptionForTheme(String selectedTheme) { + switch (selectedTheme) { + case "light": return context.getString(R.string.settings_theme_value_light); + case "night": return context.getString(R.string.settings_theme_value_dark); + default: return context.getString(R.string.settings_theme_value_default); + } + } + + private int getTheme(String selectedTheme) { + switch (selectedTheme) { + case "light": return AppCompatDelegate.MODE_NIGHT_NO; + case "dark": return AppCompatDelegate.MODE_NIGHT_YES; + default: return AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; + } + } + } + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.application_preferences, rootKey); + + Preference theme = findPreference(SharedPreferenceKeys.THEME); + assert theme != null; + theme.setOnPreferenceChangeListener((preference, newValue) -> { + if (newValue instanceof String) { + ThemeProvider provider = new ThemeProvider(requireContext()); + int newTheme = provider.getTheme((String) newValue); + AppCompatDelegate.setDefaultNightMode(newTheme); + theme.setSummaryProvider(p -> provider.getDescriptionForTheme(String.valueOf(newValue))); + return true; + } + return false; + }); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/settings/BuildSettingsFragment.java b/app/src/main/java/com/tyron/code/ui/settings/BuildSettingsFragment.java new file mode 100644 index 00000000..965697de --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/settings/BuildSettingsFragment.java @@ -0,0 +1,26 @@ +package com.tyron.code.ui.settings; + +import android.os.Bundle; +import android.text.InputType; + +import androidx.annotation.Nullable; +import androidx.preference.EditTextPreference; +import androidx.preference.PreferenceFragmentCompat; + +import com.google.android.material.transition.MaterialSharedAxis; +import com.tyron.code.R; + +public class BuildSettingsFragment extends PreferenceFragmentCompat { + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setEnterTransition(new MaterialSharedAxis(MaterialSharedAxis.X, false)); + setExitTransition(new MaterialSharedAxis(MaterialSharedAxis.X, true)); + } + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.build_preferences, rootKey); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/settings/EditorSettingsFragment.java b/app/src/main/java/com/tyron/code/ui/settings/EditorSettingsFragment.java new file mode 100644 index 00000000..4430d761 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/settings/EditorSettingsFragment.java @@ -0,0 +1,132 @@ +package com.tyron.code.ui.settings; + +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.text.Editable; +import android.text.InputType; +import android.widget.Button; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.ContextCompat; +import androidx.preference.EditTextPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.textfield.TextInputLayout; +import com.google.android.material.transition.MaterialSharedAxis; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.tyron.code.R; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.common.util.SingleTextWatcher; +import com.tyron.completion.progress.ProgressManager; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.util.Objects; + +import io.github.rosemoe.sora.langs.textmate.theme.TextMateColorScheme; +import io.github.rosemoe.sora.textmate.core.internal.theme.reader.ThemeReader; +import io.github.rosemoe.sora.textmate.core.theme.IRawTheme; +import io.github.rosemoe.sora2.text.EditorUtil; + +public class EditorSettingsFragment extends PreferenceFragmentCompat { + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setEnterTransition(new MaterialSharedAxis(MaterialSharedAxis.X, false)); + setExitTransition(new MaterialSharedAxis(MaterialSharedAxis.X, true)); + } + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.editor_preferences, rootKey); + + EditTextPreference fontSize = findPreference(SharedPreferenceKeys.FONT_SIZE); + if (fontSize != null) { + fontSize.setOnBindEditTextListener(editText -> + editText.setInputType(InputType.TYPE_CLASS_NUMBER)); + } + + Preference scheme = findPreference(SharedPreferenceKeys.SCHEME); + assert scheme != null; + scheme.setOnPreferenceClickListener(preference -> { + SharedPreferences pref = preference.getSharedPreferences(); + String path = pref.getString("scheme", ""); + File currentTheme = new File(path); + + AlertDialog dialog = new MaterialAlertDialogBuilder(requireContext()) + .setView(R.layout.base_textinput_layout) + .setTitle(R.string.change_scheme_dialog_title) + .setNeutralButton(R.string.defaultString, (d, w) -> { + pref.edit().putString(SharedPreferenceKeys.SCHEME, null).apply(); + preference.callChangeListener(null); + }) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.save, null) + .create(); + dialog.setOnShowListener(d -> { + final Button button = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + + TextInputLayout layout = dialog.findViewById(R.id.textinput_layout); + EditText editText = Objects.requireNonNull(layout).getEditText(); + assert editText != null; + + editText.setText(currentTheme.getAbsolutePath()); + editText.addTextChangedListener(new SingleTextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + File file = new File(editable.toString()); + boolean enabled = file.exists() && file.canRead() + && file.isFile(); + button.setEnabled(enabled); + } + }); + + button.setOnClickListener(v -> { + File file = new File(editText.getText().toString()); + ListenableFuture future = getColorScheme(file); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(@Nullable TextMateColorScheme result) { + pref.edit() + .putString(SharedPreferenceKeys.SCHEME, file.getAbsolutePath()) + .apply(); + preference.callChangeListener(file.getAbsolutePath()); + d.dismiss(); + } + + @Override + public void onFailure(@NonNull Throwable t) { + d.dismiss(); + new MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.error) + .setMessage(t.getMessage()) + .setPositiveButton(android.R.string.ok, null) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + }, ContextCompat.getMainExecutor(requireContext())); + }); + }); + dialog.show(); + return true; + }); + } + + public static ListenableFuture getColorScheme(@NonNull File file) { + return ProgressManager.getInstance().computeNonCancelableAsync(() -> { + IRawTheme rawTheme = ThemeReader.readThemeSync(file.getAbsolutePath(), + FileUtils.openInputStream(file)); + return Futures.immediateFuture(EditorUtil.createTheme(rawTheme)); + }); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/settings/SettingsActivity.java b/app/src/main/java/com/tyron/code/ui/settings/SettingsActivity.java new file mode 100644 index 00000000..0083272b --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/settings/SettingsActivity.java @@ -0,0 +1,96 @@ +package com.tyron.code.ui.settings; + +import android.os.Bundle; +import android.view.MenuItem; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import com.google.android.material.transition.MaterialSharedAxis; +import com.tyron.code.R; + +public class SettingsActivity extends AppCompatActivity implements + PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { + + private static final String TITLE_TAG = "settingsActivityTitle"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.settings_activity); + if (savedInstanceState == null) { + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.settings, new SettingsFragment()) + .commit(); + } else { + setTitle(savedInstanceState.getCharSequence(TITLE_TAG)); + } + getSupportFragmentManager().addOnBackStackChangedListener( + () -> { + if (getSupportFragmentManager().getBackStackEntryCount() == 0) { + setTitle(R.string.title_activity_settings); + } + }); + + setSupportActionBar(findViewById(R.id.toolbar)); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + // Save current activity title so we can set it again after a configuration change + outState.putCharSequence(TITLE_TAG, getTitle()); + } + + @Override + public boolean onSupportNavigateUp() { + if (getSupportFragmentManager().popBackStackImmediate()) { + return true; + } + return super.onSupportNavigateUp(); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + FragmentManager manager = getSupportFragmentManager(); + if (!manager.popBackStackImmediate()) { + finish(); + } + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) { + // Instantiate the new Fragment + final Bundle args = pref.getExtras(); + final Fragment fragment = getSupportFragmentManager().getFragmentFactory().instantiate( + getClassLoader(), + pref.getFragment()); + fragment.setArguments(args); + fragment.setTargetFragment(caller, 0); + // Replace the existing Fragment with the new Fragment + getSupportFragmentManager().beginTransaction() + .replace(R.id.settings, fragment) + .addToBackStack(null) + .commit(); + setTitle(pref.getTitle()); + return true; + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/settings/SettingsFragment.java b/app/src/main/java/com/tyron/code/ui/settings/SettingsFragment.java new file mode 100644 index 00000000..5000d34a --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/settings/SettingsFragment.java @@ -0,0 +1,30 @@ +package com.tyron.code.ui.settings; + +import android.os.Bundle; +import android.view.View; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.preference.PreferenceFragmentCompat; + +import com.google.android.material.transition.MaterialSharedAxis; +import com.tyron.code.R; + +public class SettingsFragment extends PreferenceFragmentCompat { + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setEnterTransition(new MaterialSharedAxis(MaterialSharedAxis.X, false)); + setExitTransition(new MaterialSharedAxis(MaterialSharedAxis.X, true)); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.root_preferences, rootKey); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/ui/theme/ThemeRepository.java b/app/src/main/java/com/tyron/code/ui/theme/ThemeRepository.java new file mode 100644 index 00000000..8991b81d --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/theme/ThemeRepository.java @@ -0,0 +1,31 @@ +package com.tyron.code.ui.theme; + +import com.tyron.code.ui.settings.EditorSettingsFragment; +import com.tyron.common.ApplicationProvider; + +import java.util.HashMap; +import java.util.Map; + +import io.github.rosemoe.sora.langs.textmate.theme.TextMateColorScheme; +import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; +import io.github.rosemoe.sora2.text.EditorUtil; + +public class ThemeRepository { + + public static final String DEFAULT_LIGHT = "code_assist_default_light"; + public static final String DEFAULT_NIGHT = "code_assist_default_night"; + + private static final Map sSchemeCache = new HashMap<>(); + + public static void putColorScheme(String key, TextMateColorScheme scheme) { + sSchemeCache.put(key, scheme); + } + + public static TextMateColorScheme getColorScheme(String key) { + return sSchemeCache.get(key); + } + + public static TextMateColorScheme getDefaultScheme(boolean light) { + return EditorUtil.getDefaultColorScheme(ApplicationProvider.getApplicationContext(), light); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/wizard/WizardFragment.java b/app/src/main/java/com/tyron/code/ui/wizard/WizardFragment.java new file mode 100644 index 00000000..2cbd8044 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/wizard/WizardFragment.java @@ -0,0 +1,728 @@ +package com.tyron.code.ui.wizard; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.text.Editable; +import android.text.InputType; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.Button; +import android.widget.LinearLayout; + +import androidx.activity.OnBackPressedCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.transition.TransitionManager; + +import com.github.angads25.filepicker.controller.adapters.FileListAdapter; +import com.github.angads25.filepicker.model.DialogConfigs; +import com.github.angads25.filepicker.model.DialogProperties; +import com.github.angads25.filepicker.model.MarkedItemList; +import com.github.angads25.filepicker.view.FilePickerDialog; +import com.google.android.material.textfield.TextInputLayout; +import com.google.android.material.transition.MaterialFadeThrough; +import com.google.android.material.transition.MaterialSharedAxis; +import com.tyron.builder.project.Project; +import com.tyron.code.ApplicationLoader; +import com.tyron.code.R; +import com.tyron.code.ui.wizard.adapter.WizardTemplateAdapter; +import com.tyron.code.util.UiUtilsKt; +import com.tyron.common.util.AndroidUtilities; +import com.tyron.common.SharedPreferenceKeys; +import com.tyron.common.util.Decompress; +import com.tyron.common.util.SingleTextWatcher; +import com.tyron.completion.progress.ProgressManager; + +import org.apache.commons.io.FileUtils; +import javax.lang.model.SourceVersion; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executors; + +@SuppressWarnings("ConstantConditions") +public class WizardFragment extends Fragment { + + public interface OnProjectCreatedListener { + void onProjectCreated(Project project); + } + + private Button mNavigateButton; + private Button mExitButton; + private RecyclerView mRecyclerView; + private LinearLayout mLoadingLayout; + private WizardTemplateAdapter mAdapter; + + private View mWizardTemplatesView; + private View mWizardDetailsView; + + private boolean mLast; + private boolean mShowDialogOnPermissionGrant = false; + private boolean mUseInternalStorage = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R; + + private WizardTemplate mCurrentTemplate; + + private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + onNavigateBack(); + } + }; + private ActivityResultLauncher mPermissionLauncher; + private final ActivityResultContracts.RequestMultiplePermissions mPermissionsContract = + new ActivityResultContracts.RequestMultiplePermissions(); + private OnProjectCreatedListener mListener; + + public void setOnProjectCreatedListener(OnProjectCreatedListener listener) { + mListener = listener; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setEnterTransition(new MaterialSharedAxis(MaterialSharedAxis.X, false)); + setExitTransition(new MaterialSharedAxis(MaterialSharedAxis.X, true)); + + mPermissionLauncher = registerForActivityResult(mPermissionsContract, isGranted -> { + if (isGranted.containsValue(false)) { + mUseInternalStorage = true; + initializeSaveLocation(); + } else { + mUseInternalStorage = false; + if (mShowDialogOnPermissionGrant) { + mShowDialogOnPermissionGrant = false; + showDirectoryPickerDialog(); + } + } + }); + } + + @Override + public void onDestroy() { + super.onDestroy(); + onBackPressedCallback.setEnabled(false); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + requireActivity().getOnBackPressedDispatcher() + .addCallback(getViewLifecycleOwner(), onBackPressedCallback); + + View view = inflater.inflate(R.layout.wizard_fragment, container, false); + LinearLayout layout = view.findViewById(R.id.setup_wizard_layout); + + View footer = view.findViewById(R.id.footer); + UiUtilsKt.addSystemWindowInsetToPadding(footer, false, true, false, true); + + mNavigateButton = layout.findViewById(R.id.wizard_next); + mNavigateButton.setVisibility(View.GONE); + mNavigateButton.setOnClickListener(this::onNavigateNext); + + mExitButton = layout.findViewById(R.id.exit_button); + mExitButton.setOnClickListener(v -> onNavigateBack()); + + mRecyclerView = layout.findViewById(R.id.template_recyclerview); + mRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), + AndroidUtilities.getRowCount(AndroidUtilities.dp(132)))); + + mLoadingLayout = layout.findViewById(R.id.loading_layout); + mWizardTemplatesView = layout.findViewById(R.id.wizard_templates_layout); + mWizardDetailsView = layout.findViewById(R.id.wizard_details_layout); + + mAdapter = new WizardTemplateAdapter(); + mRecyclerView.setAdapter(mAdapter); + + initDetailsView(); + + return view; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + loadTemplates(); + } + + private void onNavigateBack() { + if (!mLast) { + getParentFragmentManager().popBackStack(); + } else { + showTemplatesView(); + mLast = false; + } + } + + + private void onNavigateNext(View view) { + if (!mLast) { + showDetailsView(); + mLast = true; + } else { + createProjectAsync(); + } + } + + private void showTemplatesView() { + mWizardTemplatesView.setVisibility(View.GONE); + + MaterialSharedAxis sharedAxis = new MaterialSharedAxis(MaterialSharedAxis.X, false); + + TransitionManager.beginDelayedTransition((ViewGroup) requireView(), sharedAxis); + + mWizardDetailsView.setVisibility(View.GONE); + mWizardTemplatesView.setVisibility(View.VISIBLE); + mNavigateButton.setVisibility(View.GONE); + mNavigateButton.setText(R.string.wizard_next); + mExitButton.setText(R.string.wizard_exit); + } + + private TextInputLayout mNameLayout; + private TextInputLayout mSaveLocationLayout; + private TextInputLayout mPackageNameLayout; + private TextInputLayout mMinSdkLayout; + private TextInputLayout mLanguageLayout; + + private AutoCompleteTextView mLanguageText; + private AutoCompleteTextView mMinSdkText; + + private void initDetailsView() { + mNameLayout = mWizardDetailsView.findViewById(R.id.til_app_name); + mNameLayout.getEditText().addTextChangedListener(new SingleTextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + verifyClassName(editable); + } + }); + mPackageNameLayout = mWizardDetailsView.findViewById(R.id.til_package_name); + mPackageNameLayout.getEditText().addTextChangedListener(new SingleTextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + verifyPackageName(editable); + } + }); + + mSaveLocationLayout = mWizardDetailsView.findViewById(R.id.til_save_location); + mSaveLocationLayout.getEditText() + .setText(PreferenceManager.getDefaultSharedPreferences(requireContext()) + .getString(SharedPreferenceKeys.PROJECT_SAVE_PATH, + requireContext().getExternalFilesDir("Projects") + .getAbsolutePath())); + initializeSaveLocation(); + + mSaveLocationLayout.getEditText().addTextChangedListener(new SingleTextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + verifySaveLocation(editable); + } + }); + + mLanguageLayout = mWizardDetailsView.findViewById(R.id.til_language); + mLanguageText = mWizardDetailsView.findViewById(R.id.et_language); + + mMinSdkLayout = mWizardDetailsView.findViewById(R.id.til_min_sdk); + mMinSdkText = mWizardDetailsView.findViewById(R.id.et_min_sdk); + mMinSdkText.setAdapter(new ArrayAdapter<>(requireContext(), + android.R.layout.simple_list_item_1, getSdks())); + mMinSdkText.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + mMinSdkLayout.setErrorEnabled(false); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + mMinSdkLayout.setError(getString(R.string.wizard_select_min_sdk)); + } + }); + } + + private boolean isGrantedStoragePermission() { + return ContextCompat.checkSelfPermission(requireActivity(), + Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission(requireActivity(), + Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } + + private boolean shouldShowRequestPermissionRationale() { + return shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE) || + shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE); + } + + private void requestPermissions() { + mPermissionLauncher.launch(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}); + } + + private void initializeSaveLocation() { + if (mUseInternalStorage) { + mSaveLocationLayout.setHelperText(getString(R.string.wizard_scoped_storage_info)); + mSaveLocationLayout.getEditText().setText(requireContext() + .getExternalFilesDir("Projects").getAbsolutePath()); + mSaveLocationLayout.getEditText().setInputType(InputType.TYPE_NULL); + } +// mSaveLocationLayout.setEndIconOnClickListener(view -> { +// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { +// if (isGrantedStoragePermission()) { +// showDirectoryPickerDialog(); +// } else if (shouldShowRequestPermissionRationale()) { +// new MaterialAlertDialogBuilder(view.getContext()) +// .setMessage("The application needs storage permissions in order to save project files that " + +// "will not be deleted when you uninstall the app. Alternatively you can choose to " + +// "save project files into the app's internal storage.") +// .setPositiveButton("Allow", (d, which) -> { +// mShowDialogOnPermissionGrant = true; +// requestPermissions(); +// }) +// .setNegativeButton("Use internal storage", (d, which) -> { +// mUseInternalStorage = true; +// initializeSaveLocation(); +// }) +// .setTitle("Storage permissions") +// .show(); +// } else { +// mShowDialogOnPermissionGrant = true; +// requestPermissions(); +// } +// } +// }); + } + + @SuppressLint("SetTextI18n") + private void showDirectoryPickerDialog() { + DialogProperties properties = new DialogProperties(); + properties.selection_mode = DialogConfigs.SINGLE_MODE; + properties.selection_type = DialogConfigs.DIR_SELECT; + properties.root = Environment.getExternalStorageDirectory(); + properties.error_dir = requireContext().getExternalFilesDir(null); + + FilePickerDialog dialog = new FilePickerDialog(requireContext(), properties); + dialog.setDialogSelectionListener(files -> { + String file = files[0]; + mSaveLocationLayout.getEditText() + .setText(file); + }); + dialog.setOnShowListener((d) -> { + // work around to set the color of the dialog buttons to white since the color + // accent of the app is orange + Button cancel = dialog.findViewById(com.github.angads25.filepicker.R.id.cancel); + Button select = dialog.findViewById(com.github.angads25.filepicker.R.id.select); + + cancel.setTextColor(Color.WHITE); + select.setTextColor(Color.WHITE); + + String positiveButtonNameStr = getString(com.github.angads25.filepicker.R.string.choose_button_label); + try { + Field mAdapterField = dialog.getClass().getDeclaredField("mFileListAdapter"); + mAdapterField.setAccessible(true); + FileListAdapter adapter = (FileListAdapter) mAdapterField.get(dialog); + adapter.setNotifyItemCheckedListener(() -> { + int size = MarkedItemList.getFileCount(); + if (size == 0) { + select.setEnabled(false); + select.setTextColor(Color.WHITE); + select.setText(positiveButtonNameStr); + } else { + select.setEnabled(true); + select.setText(positiveButtonNameStr + " (" + size + ") "); + } + adapter.notifyDataSetChanged(); + }); + } catch (NoSuchFieldException | IllegalAccessException e) { + Log.w("WizardFragment", "Unable to get declared field", e); + } + }); + dialog.show(); + } + + private boolean validateDetails() { + + requireActivity().runOnUiThread(() -> { + verifyPackageName(mPackageNameLayout.getEditText().getText()); + verifyClassName(mNameLayout.getEditText().getText()); + verifySaveLocation(mSaveLocationLayout.getEditText().getText()); + }); + + if (mPackageNameLayout.isErrorEnabled()) { + return false; + } + + if (mSaveLocationLayout.isErrorEnabled()) { + return false; + } + + if (mPackageNameLayout.isErrorEnabled()) { + return false; + } + + if (mMinSdkLayout.isErrorEnabled()) { + return false; + } + + if (TextUtils.isEmpty(mMinSdkText.getText())) { + return false; + } + + return mCurrentTemplate != null; + } + + private void verifyClassName(Editable editable) { + String name = editable.toString(); + if (TextUtils.isEmpty(name)) { + mNameLayout.setError(getString(R.string.wizard_error_name_empty)); + return; + } else if (name.contains(File.pathSeparator) || name.contains(File.separator)) { + mNameLayout.setError(getString(R.string.wizard_error_name_illegal)); + return; + } else { + mNameLayout.setErrorEnabled(false); + } + + File file = new File( + PreferenceManager.getDefaultSharedPreferences(requireContext()) + .getString(SharedPreferenceKeys.PROJECT_SAVE_PATH, + requireContext().getExternalFilesDir("Projects").getAbsolutePath()) + + "/" + editable.toString()); + String suffix = ""; + if (file.exists()) { + suffix = "-1"; + } + String path = file.getAbsolutePath() + suffix.trim(); + mSaveLocationLayout.getEditText().setText(path); + } + + private void verifySaveLocation(Editable editable) { + if (editable.toString().length() >= 240) { + mSaveLocationLayout.setError(getString(R.string.wizard_path_exceeds)); + return; + } else { + mSaveLocationLayout.setErrorEnabled(false); + } + + File file = new File(editable.toString()); + if (file.getParentFile() == null || !file.getParentFile().canWrite()) { + mSaveLocationLayout.setError(getString(R.string.wizard_file_not_writable)); + } else { + mSaveLocationLayout.setErrorEnabled(false); + } + } + + private void verifyPackageName(Editable editable) { + String packageName = editable.toString(); + String[] packages = packageName.split("\\."); + for (String name : packages) { + if (name.isEmpty() || !SourceVersion.isName(name)) { + mPackageNameLayout.setError(getString(R.string.wizard_package_illegal)); + return; + } + } + if (packages == null) { + mPackageNameLayout.setError(getString(R.string.wizard_package_empty)); + } else if (packages.length == 1) { + mPackageNameLayout.setError(getString(R.string.wizard_package_too_short)); + } else if (packageName.endsWith(".")) { + mPackageNameLayout.setError(getString(R.string.wizard_package_illegal)); + } else if (packageName.contains(" ")) { + mPackageNameLayout.setError(getString(R.string.wizard_package_contains_spaces)); + } else if (!packageName.matches("^[a-zA-Z0-9.]+$")) { + mPackageNameLayout.setError(getString(R.string.wizard_package_illegal)); + } else { + mPackageNameLayout.setErrorEnabled(false); + } + } + + private void createProjectAsync() { + TransitionManager.beginDelayedTransition((ViewGroup) requireView(), new MaterialFadeThrough()); + mWizardTemplatesView.setVisibility(View.GONE); + mWizardDetailsView.setVisibility(View.GONE); + mLoadingLayout.setVisibility(View.VISIBLE); + + ProgressManager.getInstance().runNonCancelableAsync(() -> { + String savePath = mSaveLocationLayout.getEditText().getText().toString(); + + try { + if (validateDetails()) { + createProject(); + } else { + requireActivity().runOnUiThread(this::showDetailsView); + return; + } + + Project project = new Project(new File(savePath)); + replacePlaceholders(project.getRootFile()); + + if (getActivity() != null && mListener != null) { + requireActivity().runOnUiThread(() -> { + getParentFragmentManager().popBackStack(); + mListener.onProjectCreated(project); + }); + } + } catch (IOException e) { + requireActivity().runOnUiThread(() -> { + ApplicationLoader.showToast(e.getMessage()); + showDetailsView(); + }); + } + }); + } + + /** + * Traverses all files in a directory, including subdirectory and replaces + * placeholders with the right text. + * + * @param file Root directory to start + */ + @WorkerThread + private void replacePlaceholders(File file) throws IOException { + File[] files = file.listFiles(); + if (files != null) { + for (File child : files) { + if (child.isDirectory()) { + replacePlaceholders(child); + continue; + } + if (child.getName().endsWith(".gradle")) { + replacePlaceholder(child); + } else if (child.getName().endsWith(".java") || child.getName().endsWith(".kt")) { + replacePlaceholder(child); + } else if (child.getName().endsWith(".xml")) { + replacePlaceholder(child); + } + } + } + } + + /** + * Replaces the placeholders in a file such as $packagename, $appname + * + * @param file Input file + */ + @WorkerThread + private void replacePlaceholder(File file) throws IOException { + String string; + try { + string = FileUtils.readFileToString(file, Charset.defaultCharset()); + } catch (IOException e) { + return; + } + String targetSdk = "31"; + String minSdk = mMinSdkText.getText().toString() + .substring("API".length() + 1, "API".length() + 3); // at least 2 digits + int minSdkInt = Integer.parseInt(minSdk); + + FileUtils.writeStringToFile( + file, + string.replace("$packagename", mPackageNameLayout.getEditText().getText()) + .replace("$appname", mNameLayout.getEditText().getText()) + .replace("${targetSdkVersion}", targetSdk) + .replace("${minSdkVersion}", String.valueOf(minSdkInt)), + StandardCharsets.UTF_8 + ); + } + + @WorkerThread + private void createProject() throws IOException { + + File projectRoot = new File(mSaveLocationLayout.getEditText().getText().toString()); + if (!projectRoot.exists()) { + if (!projectRoot.mkdirs()) { + throw new IOException("Unable to create directory"); + } + } + boolean isJava = mLanguageText.getText().toString().equals("Java"); + File sourcesDir = new File(mCurrentTemplate.getPath() + + "/" + (isJava ? "java" : "kotlin")); + if (!sourcesDir.exists()) { + throw new IOException("Unable to find source file for language " + + mLanguageText.getText()); + } + + String packageNameDir = mPackageNameLayout.getEditText() + .getText().toString() + .replace(".", "/"); + File targetSourceDir = new File(projectRoot, "/app/src/main/java/" + packageNameDir); + if (!targetSourceDir.exists()) { + if (!targetSourceDir.mkdirs()) { + throw new IOException("Unable to create target directory"); + } + } + FileUtils.copyDirectory(sourcesDir, projectRoot); + FileUtils.deleteDirectory(new File(projectRoot, "app/src/main/java/$packagename")); + FileUtils.copyDirectory(new File(sourcesDir, + "app/src/main/java/$packagename"), targetSourceDir); + } + + private List getSdks() { + return Arrays.asList( + "API 16: Android 4.0 (Ice Cream Sandwich)", + "API 17: Android 4.2 (JellyBean)", + "API 18: Android 4.3 (JellyBean)", + "API 19: Android 4.4 (KitKat)", + "API 20: Android 4.4W (KitKat Wear)", + "API 21: Android 5.0 (Lollipop)", + "API 22: Android 5.1 (Lollipop)", + "API 23: Android 6.0 (Marshmallow)", + "API 24: Android 7.0 (Nougat)", + "API 25: Android 7.1 (Nougat)", + "API 26: Android 8.0 (Oreo)", + "API 27: Android 8.1 (Oreo)", + "API 28: Android 9.0 (Pie)", + "API 29: Android 10.0 (Q)", + "API 30: Android 11.0 (R)", + "API 31: Android 12.0 (S)" + ); + } + + private void showDetailsView() { + List languages = new ArrayList<>(); + if (mCurrentTemplate != null) { + if (mCurrentTemplate.isSupportsJava()) { + languages.add("Java"); + } + if (mCurrentTemplate.isSupportsKotlin()) { + languages.add("Kotlin"); + } + } + mLanguageText.setAdapter(new ArrayAdapter<>(requireContext(), + android.R.layout.simple_list_item_1, languages)); + + mLoadingLayout.setVisibility(View.GONE); + mWizardDetailsView.setVisibility(View.GONE); + + MaterialSharedAxis sharedAxis = new MaterialSharedAxis(MaterialSharedAxis.X, true); + + TransitionManager.beginDelayedTransition((ViewGroup) requireView(), sharedAxis); + + mWizardDetailsView.setVisibility(View.VISIBLE); + mWizardTemplatesView.setVisibility(View.GONE); + mNavigateButton.setText(R.string.wizard_create); + mNavigateButton.setVisibility(View.VISIBLE); + mExitButton.setText(R.string.wizard_previous); + } + + private void loadTemplates() { + TransitionManager.beginDelayedTransition((ViewGroup) requireView(), new MaterialFadeThrough()); + mLoadingLayout.setVisibility(View.VISIBLE); + mRecyclerView.setVisibility(View.GONE); + + Executors.newSingleThreadExecutor().execute(() -> { + List templates = getTemplates(); + + if (getActivity() != null) { + getActivity().runOnUiThread(() -> { + TransitionManager.beginDelayedTransition((ViewGroup) requireView(), + new MaterialFadeThrough()); + mLoadingLayout.setVisibility(View.GONE); + mRecyclerView.setVisibility(View.VISIBLE); + + mAdapter.submitList(templates); + + + mAdapter.setOnItemClickListener((item, pos) -> { + mCurrentTemplate = item; + onNavigateNext(mNavigateButton); + }); + }); + } + }); + } + + private List getTemplates() { + try { + File file = requireContext().getExternalFilesDir("templates"); + extractTemplatesMaybe(); + + File[] templateFiles = file.listFiles(); + if (templateFiles == null) { + return Collections.emptyList(); + } + if (templateFiles.length == 0) { + extractTemplatesMaybe(); + } + templateFiles = file.listFiles(); + if (templateFiles == null) { + return Collections.emptyList(); + } + + List templates = new ArrayList<>(); + for (File child : templateFiles) { + WizardTemplate template = WizardTemplate.fromFile(child); + if (template != null) { + templates.add(template); + } + } + return templates; + } catch (IOException e) { + return Collections.emptyList(); + } + } + + private void extractTemplatesMaybe() throws IOException { + File hashFile = new File(requireContext().getExternalFilesDir("templates"), "hash"); + if (!hashFile.exists()) { + extractTemplates(); + } else { + InputStream newIs = requireContext().getAssets() + .open("templates.zip"); + String newIsMd5 = AndroidUtilities.calculateMD5(newIs); + String oldMd5 = FileUtils.readFileToString(hashFile, Charset.defaultCharset()); + + if (!newIsMd5.equals(oldMd5)) { + extractTemplates(); + } else { + Log.d("WizardExtractor", "Templates are up to date"); + } + } + } + + private void extractTemplates() throws IOException { + File templatesDir = new File(requireContext().getExternalFilesDir(null), + "templates"); + if (templatesDir.exists()) { + FileUtils.deleteDirectory(templatesDir); + } + + Decompress.unzipFromAssets(requireContext(), "templates.zip", + templatesDir.getParent()); + File hashFile = new File(templatesDir, "hash"); + if (!hashFile.createNewFile()) { + throw new IOException("Unable to create hash file"); + } + FileUtils.writeStringToFile(hashFile, + AndroidUtilities.calculateMD5(requireContext().getAssets() + .open("templates.zip")), + Charset.defaultCharset()); + } +} diff --git a/app/src/main/java/com/tyron/code/ui/wizard/WizardTemplate.java b/app/src/main/java/com/tyron/code/ui/wizard/WizardTemplate.java new file mode 100644 index 00000000..90d071a6 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/wizard/WizardTemplate.java @@ -0,0 +1,95 @@ +package com.tyron.code.ui.wizard; + + +import org.apache.commons.io.FileUtils; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class WizardTemplate { + + public static WizardTemplate fromFile(File parent) { + if (!parent.exists()) { + return null; + } + + if (!parent.isDirectory()) { + return null; + } + + File infoFile = new File(parent, "info.json"); + if (!infoFile.exists()) { + return null; + } + + WizardTemplate template = new WizardTemplate(); + try { + JSONObject jsonObject = new JSONObject(FileUtils.readFileToString(infoFile, + StandardCharsets.UTF_8)); + template.setMinSdk(jsonObject.getInt("minSdk")); + template.setName(jsonObject.getString("name")); + template.setPath(parent.getAbsolutePath()); + template.setSupportsJava(jsonObject.getBoolean("supportsJava")); + template.setSupportsKotlin(jsonObject.getBoolean("supportsKotlin")); + return template; + } catch (JSONException | IOException e) { + return null; + } + } + private String name; + + private int minSdk; + + private String path; + + private boolean supportsKotlin; + + private boolean supportsJava; + + public WizardTemplate() { + + } + + public int getMinSdk() { + return minSdk; + } + + public void setMinSdk(int minSdk) { + this.minSdk = minSdk; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public boolean isSupportsJava() { + return supportsJava; + } + + public void setSupportsJava(boolean supportsJava) { + this.supportsJava = supportsJava; + } + + public boolean isSupportsKotlin() { + return supportsKotlin; + } + + public void setSupportsKotlin(boolean supportsKotlin) { + this.supportsKotlin = supportsKotlin; + } +} diff --git a/app/src/main/java/com/tyron/code/ui/wizard/adapter/WizardTemplateAdapter.java b/app/src/main/java/com/tyron/code/ui/wizard/adapter/WizardTemplateAdapter.java new file mode 100644 index 00000000..94ed70c0 --- /dev/null +++ b/app/src/main/java/com/tyron/code/ui/wizard/adapter/WizardTemplateAdapter.java @@ -0,0 +1,120 @@ +package com.tyron.code.ui.wizard.adapter; + +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.imageview.ShapeableImageView; +import com.google.android.material.shape.CornerFamily; +import com.tyron.code.R; +import com.tyron.code.ui.wizard.WizardTemplate; +import com.tyron.common.util.AndroidUtilities; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class WizardTemplateAdapter extends RecyclerView.Adapter { + + public interface OnItemClickListener { + void onItemClick(WizardTemplate item, int position); + } + + private final List mItems = new ArrayList<>(); + private OnItemClickListener mListener; + + public WizardTemplateAdapter() { + + } + + public void setOnItemClickListener(OnItemClickListener listener) { + mListener = listener; + } + + public void submitList(@NonNull List newItems) { + DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return mItems.size(); + } + + @Override + public int getNewListSize() { + return newItems.size(); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return Objects.equals(mItems.get(oldItemPosition), newItems.get(newItemPosition)); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + return Objects.equals(mItems.get(oldItemPosition), newItems.get(newItemPosition)); + } + }); + mItems.clear(); + mItems.addAll(newItems); + diffResult.dispatchUpdatesTo(this); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.wizard_template_item, parent, false); + ViewHolder holder = new ViewHolder(view); + view.setOnClickListener(view1 -> { + if (mListener != null) { + int pos = holder.getBindingAdapterPosition(); + if (pos != RecyclerView.NO_POSITION) { + mListener.onItemClick(mItems.get(pos), pos); + } + } + }); + return holder; + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.bind(mItems.get(position)); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + + public final ShapeableImageView icon; + public final TextView name; + + public ViewHolder(View view) { + super(view); + + icon = view.findViewById(R.id.template_icon); + name = view.findViewById(R.id.template_name); + } + + private void bind(WizardTemplate template) { + name.setText(template.getName()); + + File iconFile = new File(template.getPath(), "icon.png"); + if (iconFile.exists()) { + icon.setImageURI(Uri.fromFile(iconFile)); + icon.setShapeAppearanceModel(icon.getShapeAppearanceModel() + .toBuilder() + .setAllCorners(CornerFamily.ROUNDED, AndroidUtilities.dp(8)) + .build()); + } + } + } +} diff --git a/app/src/main/java/com/tyron/code/util/AllowChildInterceptDrawerLayout.java b/app/src/main/java/com/tyron/code/util/AllowChildInterceptDrawerLayout.java new file mode 100644 index 00000000..6a1f2729 --- /dev/null +++ b/app/src/main/java/com/tyron/code/util/AllowChildInterceptDrawerLayout.java @@ -0,0 +1,82 @@ +package com.tyron.code.util; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.drawerlayout.widget.DrawerLayout; + +/** + * Allows horizontally scrolling child of drawer layouts to intercept the touch event. + */ +public class AllowChildInterceptDrawerLayout extends DrawerLayout { + + private final Rect rect = new Rect(); + + public AllowChildInterceptDrawerLayout(@NonNull Context context) { + super(context); + } + + public AllowChildInterceptDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public AllowChildInterceptDrawerLayout(@NonNull Context context, + @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + View scrollingChild = findScrollingChild(this, ev.getX(), ev.getY()); + if (scrollingChild != null) { + return false; + } + return super.onInterceptTouchEvent(ev); + } + + /** + * Recursively finds the view that can scroll horizontally to the end + * @param parent The starting parent to search + * @param x The x point in the screen + * @param y The y point in the screen + * @return The scrolling view, null if no view is found + */ + private View findScrollingChild(ViewGroup parent, float x, float y) { + int n = parent.getChildCount(); + if (parent == this && n <= 1) { + return null; + } + + int start = 0; + if (parent == this) { + start = 1; + } + + for (int i = start; i < n; i++) { + View child = parent.getChildAt(i); + if (child.getVisibility() != View.VISIBLE) { + continue; + } + child.getHitRect(rect); + if (rect.contains((int) x, (int) y)) { + if (child.canScrollHorizontally(1)) { + return child; + } else if (child instanceof ViewGroup) { + View v = findScrollingChild((ViewGroup) child, x - rect.left, y - rect.top); + if (v != null) { + return v; + } + } + } + } + return null; + } + +} diff --git a/app/src/main/java/com/tyron/code/util/ApkInstaller.java b/app/src/main/java/com/tyron/code/util/ApkInstaller.java new file mode 100644 index 00000000..37354f5f --- /dev/null +++ b/app/src/main/java/com/tyron/code/util/ApkInstaller.java @@ -0,0 +1,41 @@ +package com.tyron.code.util; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.util.Log; + +import androidx.core.content.FileProvider; + +import com.tyron.code.BuildConfig; + +import java.io.File; + +public class ApkInstaller { + + private static final String TAG = ApkInstaller.class.getSimpleName(); + + public static void installApplication(Context context, String filePath) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(uriFromFile(context, new File(filePath)), "application/vnd.android.package-archive"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + try { + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + Log.e(TAG, "Error in opening the file!"); + } + } + + public static Uri uriFromFile(Context context, File file) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); + } else { + return Uri.fromFile(file); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/util/CoordinatePopupMenu.java b/app/src/main/java/com/tyron/code/util/CoordinatePopupMenu.java new file mode 100644 index 00000000..c72a8d44 --- /dev/null +++ b/app/src/main/java/com/tyron/code/util/CoordinatePopupMenu.java @@ -0,0 +1,74 @@ +package com.tyron.code.util; + +import android.content.Context; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.PopupMenu; +import androidx.core.widget.PopupMenuCompat; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class CoordinatePopupMenu extends PopupMenu { + + private static final Field sMenuPopupField; + + static { + try { + sMenuPopupField = PopupMenu.class.getDeclaredField("mPopup"); + sMenuPopupField.setAccessible(true); + } catch (Throwable e) { + throw new Error(e); + } + } + + public CoordinatePopupMenu(@NonNull Context context, @NonNull View anchor) { + super(context, anchor); + } + + public CoordinatePopupMenu(@NonNull Context context, @NonNull View anchor, int gravity) { + super(context, anchor, gravity); + } + + public CoordinatePopupMenu(@NonNull Context context, + @NonNull View anchor, + int gravity, + int popupStyleAttr, + int popupStyleRes) { + super(context, anchor, gravity, popupStyleAttr, popupStyleRes); + } + + @Override + public void show() { + super.show(); + } + + /** + * Does nothing. + * This is to prevent unknown callers from dismissing the popup menu, use + * {@link #dismissPopup()} instead + */ + @Override + public void dismiss() { + // do nothing + } + + public void dismissPopup() { + super.dismiss(); + } + + public void show(int x, int y) { + try { + Object popup = sMenuPopupField.get(this); + assert popup != null; + + Method show = popup.getClass() + .getDeclaredMethod("show", int.class, int.class); + show.invoke(popup, x, y); + } catch (Throwable e) { + // should not happen, fallback to show() just in case + show(); + } + } +} diff --git a/app/src/main/java/com/tyron/code/util/CustomMutableLiveData.java b/app/src/main/java/com/tyron/code/util/CustomMutableLiveData.java new file mode 100644 index 00000000..97293c43 --- /dev/null +++ b/app/src/main/java/com/tyron/code/util/CustomMutableLiveData.java @@ -0,0 +1,58 @@ +package com.tyron.code.util; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import java.lang.reflect.Field; + +/** + * A {@link LiveData} class which supports updating values but not notifying them. + * @param The object this live data holds + */ +public class CustomMutableLiveData extends MutableLiveData { + + public CustomMutableLiveData() { + super(); + } + + public CustomMutableLiveData(T value) { + super(value); + } + + @Override + public void setValue(T value) { + super.setValue(value); + } + + public void setValue(T value, boolean notify) { + if (notify) { + setValue(value); + } else { + setValueInternal(value); + } + } + + private void setValueInternal(T value) { + try { + Field mData = LiveData.class.getDeclaredField("mData"); + mData.setAccessible(true); + mData.set(this, value); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + private void incrementVersion() { + try { + Field mVersion = LiveData.class.getDeclaredField("mVersion"); + mVersion.setAccessible(true); + Integer o = (Integer) mVersion.get(this); + if (o == null) { + o = 0; + } + mVersion.set(this, o + 1); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } +} diff --git a/app/src/main/java/com/tyron/code/util/DependencyUtils.java b/app/src/main/java/com/tyron/code/util/DependencyUtils.java new file mode 100644 index 00000000..e2c42716 --- /dev/null +++ b/app/src/main/java/com/tyron/code/util/DependencyUtils.java @@ -0,0 +1,87 @@ +package com.tyron.code.util; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import com.tyron.builder.log.ILogger; +import com.tyron.resolver.model.Dependency; +import com.tyron.resolver.repository.RepositoryManager; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DependencyUtils { + + private static final Pattern GRADLE_IMPL = Pattern.compile("\\s*(implementation)\\s*(')([a-zA-Z0-9.'/-:\\-]+)(')"); + private static final Pattern GRADLE_IMPL_QUOT = Pattern.compile("\\s*(implementation)\\s*(\")([a-zA-Z0-9.'/-:\\-]+)(\")"); + + public static List parseGradle(RepositoryManager repositoryManager, String readString, ILogger logger) throws IOException { + // remove all comments + readString = readString.replaceAll("\\s*//.*", ""); + Matcher matcher = GRADLE_IMPL.matcher(readString); + + List deps = new ArrayList<>(); + while (matcher.find()) { + String declaration = matcher.group(3); + if (declaration != null) { + deps.add(Dependency.valueOf(declaration)); + } + } + + matcher = GRADLE_IMPL_QUOT.matcher(readString); + while (matcher.find()) { + String declaration = matcher.group(3); + if (declaration != null) { + try { + Dependency dependency = Dependency.valueOf(declaration); + deps.add(dependency); + } catch (IllegalArgumentException e) { + logger.warning("Failed to add dependency " + e.getMessage()); + } + } + } + return deps; + } + + /** + * Parses a build.gradle file and gets the dependencies out of it + * + * @param file input build.gradle file + * @return Library dependencies + */ + public static List parseGradle(RepositoryManager repository, File file, ILogger logger) throws IOException { + String readString = FileUtils.readFileToString(file, Charset.defaultCharset()); + return parseGradle(repository, readString, logger); + } + + public static List parseLibraries(File libraries, ILogger logger) { + String contents; + try { + contents = FileUtils.readFileToString(libraries, StandardCharsets.UTF_8); + } catch (IOException e) { + logger.error("Unable to read " + libraries.getName() + ": " + e.getMessage()); + return Collections.emptyList(); + } + try { + List dependencies = new Gson().fromJson(contents, + new TypeToken>() {}.getType()); + if (dependencies == null) { + dependencies = Collections.emptyList(); + } + return dependencies; + } catch (JsonSyntaxException e) { + logger.error("Unable to parse " + libraries.getName() + ": " + e.getMessage()); + return Collections.emptyList(); + } + } +} + diff --git a/app/src/main/java/com/tyron/code/util/FileUtils.java b/app/src/main/java/com/tyron/code/util/FileUtils.java new file mode 100644 index 00000000..0777e480 --- /dev/null +++ b/app/src/main/java/com/tyron/code/util/FileUtils.java @@ -0,0 +1,396 @@ +package com.tyron.code.util; + +import android.annotation.SuppressLint; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.text.TextUtils; +import android.util.Log; +import android.webkit.MimeTypeMap; +import android.widget.Toast; + +import com.tyron.code.ApplicationLoader; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class FileUtils { + private static Uri contentUri = null; + + @SuppressLint("NewApi") + public static String getPath( final Uri uri) { + // check here to KITKAT or new version + final boolean isKitKat = true; + String selection = null; + String[] selectionArgs = null; + // DocumentProvider + if (isKitKat ) { + // ExternalStorageProvider + + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + String fullPath = getPathFromExtSD(split); + if (fullPath != "") { + return fullPath; + } else { + return null; + } + } + + + // DownloadsProvider + + if (isDownloadsDocument(uri)) { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + final String id; + Cursor cursor = null; + try { + cursor = ApplicationLoader.applicationContext.getContentResolver().query(uri, new String[]{MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + String fileName = cursor.getString(0); + String path = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName; + if (!TextUtils.isEmpty(path)) { + return path; + } + } + } + finally { + if (cursor != null) + cursor.close(); + } + id = DocumentsContract.getDocumentId(uri); + if (!TextUtils.isEmpty(id)) { + if (id.startsWith("raw:")) { + return id.replaceFirst("raw:", ""); + } + String[] contentUriPrefixesToTry = new String[]{ + "content://downloads/public_downloads", + "content://downloads/my_downloads" + }; + for (String contentUriPrefix : contentUriPrefixesToTry) { + try { + final Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id)); + + + return getDataColumn(ApplicationLoader.applicationContext, contentUri, null, null); + } catch (NumberFormatException e) { + //In Android 8 and Android P the id is not a number + return uri.getPath().replaceFirst("^/document/raw:", "").replaceFirst("^raw:", ""); + } + } + + + } + } + else { + final String id = DocumentsContract.getDocumentId(uri); + + if (id.startsWith("raw:")) { + return id.replaceFirst("raw:", ""); + } + try { + contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.parseLong(id)); + } + catch (NumberFormatException e) { + e.printStackTrace(); + } + if (contentUri != null) { + + return getDataColumn(ApplicationLoader.applicationContext, contentUri, null, null); + } + } + } + + + // MediaProvider + if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + selection = "_id=?"; + selectionArgs = new String[]{split[1]}; + + + return getDataColumn(ApplicationLoader.applicationContext, contentUri, selection, + selectionArgs); + } + + if (isGoogleDriveUri(uri)) { + return getDriveFilePath(uri); + } + + if(isWhatsAppFile(uri)){ + return getFilePathForWhatsApp(uri); + } + + + if ("content".equalsIgnoreCase(uri.getScheme())) { + + if (isGooglePhotosUri(uri)) { + return uri.getLastPathSegment(); + } + if (isGoogleDriveUri(uri)) { + return getDriveFilePath(uri); + } + if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + { + + // return getFilePathFromURI(context,uri); + return copyFileToInternalStorage(uri,"userfiles"); + // return getRealPathFromURI(context,uri); + } + else + { + return getDataColumn(ApplicationLoader.applicationContext, uri, null, null); + } + + } + if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + } + else { + + if(isWhatsAppFile(uri)){ + return getFilePathForWhatsApp(uri); + } + + if ("content".equalsIgnoreCase(uri.getScheme())) { + String[] projection = { + MediaStore.Images.Media.DATA + }; + Cursor cursor = null; + try { + cursor = ApplicationLoader.applicationContext.getContentResolver() + .query(uri, projection, selection, selectionArgs, null); + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + if (cursor.moveToFirst()) { + return cursor.getString(column_index); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + + + + return null; + } + + private static boolean fileExists(String filePath) { + File file = new File(filePath); + + return file.exists(); + } + + private static String getPathFromExtSD(String[] pathData) { + final String type = pathData[0]; + final String relativePath = "/" + pathData[1]; + String fullPath = ""; + + // on my Sony devices (4.4.4 & 5.1.1), `type` is a dynamic string + // something like "71F8-2C0A", some kind of unique id per storage + // don't know any API that can get the root path of that storage based on its id. + // + // so no "primary" type, but let the check here for other devices + if ("primary".equalsIgnoreCase(type)) { + fullPath = Environment.getExternalStorageDirectory() + relativePath; + if (fileExists(fullPath)) { + return fullPath; + } + } + + // Environment.isExternalStorageRemovable() is `true` for external and internal storage + // so we cannot relay on it. + // + // instead, for each possible path, check if file exists + // we'll start with secondary storage as this could be our (physically) removable sd card + fullPath = System.getenv("SECONDARY_STORAGE") + relativePath; + if (fileExists(fullPath)) { + return fullPath; + } + + fullPath = System.getenv("EXTERNAL_STORAGE") + relativePath; + if (fileExists(fullPath)) { + return fullPath; + } + + return fullPath; + } + + private static String getDriveFilePath(Uri uri) { + Uri returnUri = uri; + Cursor returnCursor = ApplicationLoader.applicationContext.getContentResolver().query(returnUri, null, null, null, null); + /* + * Get the column indexes of the data in the Cursor, + * * move to the first row in the Cursor, get the data, + * * and display it. + * */ + int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE); + returnCursor.moveToFirst(); + String name = (returnCursor.getString(nameIndex)); + String size = (Long.toString(returnCursor.getLong(sizeIndex))); + File file = new File(ApplicationLoader.applicationContext.getCacheDir(), name); + try { + InputStream inputStream = ApplicationLoader.applicationContext.getContentResolver().openInputStream(uri); + FileOutputStream outputStream = new FileOutputStream(file); + int read = 0; + int maxBufferSize = 1024 * 1024; + int bytesAvailable = inputStream.available(); + + //int bufferSize = 1024; + int bufferSize = Math.min(bytesAvailable, maxBufferSize); + + final byte[] buffers = new byte[bufferSize]; + while ((read = inputStream.read(buffers)) != -1) { + outputStream.write(buffers, 0, read); + } + Log.e("File Size", "Size " + file.length()); + inputStream.close(); + outputStream.close(); + Log.e("File Path", "Path " + file.getPath()); + Log.e("File Size", "Size " + file.length()); + } catch (Exception e) { + Log.e("Exception", e.getMessage()); + } + return file.getPath(); + } + + /*** + * Used for Android Q+ + * @param uri + * @param newDirName if you want to create a directory, you can set this variable + * @return + */ + private static String copyFileToInternalStorage(Uri uri, String newDirName) { + Uri returnUri = uri; + + Cursor returnCursor = ApplicationLoader.applicationContext.getContentResolver().query(returnUri, new String[]{ + OpenableColumns.DISPLAY_NAME,OpenableColumns.SIZE + }, null, null, null); + + + /* + * Get the column indexes of the data in the Cursor, + * * move to the first row in the Cursor, get the data, + * * and display it. + * */ + int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE); + returnCursor.moveToFirst(); + String name = (returnCursor.getString(nameIndex)); + String size = (Long.toString(returnCursor.getLong(sizeIndex))); + + File output; + if(!newDirName.equals("")) { + File dir = new File(ApplicationLoader.applicationContext.getFilesDir() + "/" + newDirName); + if (!dir.exists()) { + dir.mkdir(); + } + output = new File(ApplicationLoader.applicationContext.getFilesDir() + "/" + newDirName + "/" + name); + } + else{ + output = new File(ApplicationLoader.applicationContext.getFilesDir() + "/" + name); + } + try { + InputStream inputStream = ApplicationLoader.applicationContext.getContentResolver().openInputStream(uri); + FileOutputStream outputStream = new FileOutputStream(output); + int read = 0; + int bufferSize = 1024; + final byte[] buffers = new byte[bufferSize]; + while ((read = inputStream.read(buffers)) != -1) { + outputStream.write(buffers, 0, read); + } + + inputStream.close(); + outputStream.close(); + + } + catch (Exception e) { + + Log.e("Exception", e.getMessage()); + } + + return output.getPath(); + } + + private static String getFilePathForWhatsApp(Uri uri){ + return copyFileToInternalStorage(uri,"whatsapp"); + } + + private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; + final String column = "_data"; + final String[] projection = {column}; + + try { + cursor = context.getContentResolver().query(uri, projection, + selection, selectionArgs, null); + + if (cursor != null && cursor.moveToFirst()) { + final int index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(index); + } + } + finally { + if (cursor != null) + cursor.close(); + } + + return null; + } + + private static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + private static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + private static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + private static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + + public static boolean isWhatsAppFile(Uri uri){ + return "com.whatsapp.provider.media".equals(uri.getAuthority()); + } + + private static boolean isGoogleDriveUri(Uri uri) { + return "com.google.android.apps.docs.storage".equals(uri.getAuthority()) || "com.google.android.apps.docs.storage.legacy".equals(uri.getAuthority()); + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/tyron/code/util/NoInsetFrameLayout.java b/app/src/main/java/com/tyron/code/util/NoInsetFrameLayout.java new file mode 100644 index 00000000..43dfd3fc --- /dev/null +++ b/app/src/main/java/com/tyron/code/util/NoInsetFrameLayout.java @@ -0,0 +1,44 @@ +package com.tyron.code.util; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.WindowInsets; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.graphics.Insets; +import androidx.core.view.WindowInsetsCompat; + +/** + * Used in situations where the layout is needed to resize in response to an input method while + * still being able to draw behind status bars + */ +public class NoInsetFrameLayout extends FrameLayout { + public NoInsetFrameLayout(@NonNull Context context) { + super(context); + } + + public NoInsetFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public NoInsetFrameLayout(@NonNull Context context, + @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public NoInsetFrameLayout(@NonNull Context context, + @Nullable AttributeSet attrs, + int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + return super.onApplyWindowInsets( + insets.replaceSystemWindowInsets(0, 0, 0, insets.getSystemWindowInsetBottom())); + } +} diff --git a/app/src/main/java/com/tyron/code/util/PopupMenuHelper.java b/app/src/main/java/com/tyron/code/util/PopupMenuHelper.java new file mode 100644 index 00000000..1be942fc --- /dev/null +++ b/app/src/main/java/com/tyron/code/util/PopupMenuHelper.java @@ -0,0 +1,38 @@ +package com.tyron.code.util; + +import androidx.appcompat.widget.ForwardingListener; + +import java.lang.reflect.Field; + +public class PopupMenuHelper { + + private PopupMenuHelper() { + + } + + private static final Field FORWARDING_FIELD; + + static { + try { + Class aClass = Class.forName("androidx.appcompat.widget.ForwardingListener"); + + FORWARDING_FIELD = aClass.getDeclaredField("mForwarding"); + FORWARDING_FIELD.setAccessible(true); + } catch (Throwable e) { + throw new Error(e); + } + } + + /** + * Helper method to set the forwarding listener of a PopupMenu to be always + * forwarding touch events + * @param forwardingListener must be an instance of ForwardingListener + */ + public static void setForwarding(ForwardingListener forwardingListener) { + try { + FORWARDING_FIELD.set(forwardingListener, true); + } catch (IllegalAccessException e) { + throw new Error(e); + } + } +} diff --git a/app/src/main/java/com/tyron/code/util/ProjectUtils.java b/app/src/main/java/com/tyron/code/util/ProjectUtils.java new file mode 100644 index 00000000..56434c23 --- /dev/null +++ b/app/src/main/java/com/tyron/code/util/ProjectUtils.java @@ -0,0 +1,133 @@ +package com.tyron.code.util; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tyron.ui.treeview.TreeNode; +import com.tyron.code.ui.file.tree.model.TreeFile; + +import java.io.File; + +public class ProjectUtils { + + /** + * Utility method to get package name from a current directory, this traverses up + * the file hierarchy until it reaches the "java" folder we can then workout the possible + * package name from that + * + * @param directory the parent file of the class + * @return null if name cannot be determined + */ + @Nullable + public static String getPackageName(File directory) { + if (!directory.isDirectory()) { + return null; + } + + File original = directory; + + while (!isJavaFolder(directory)) { + if (directory == null) { + return null; + } + directory = directory.getParentFile(); + } + + String originalPath = original.getAbsolutePath(); + String javaPath = directory.getAbsolutePath(); + + String cutPath = originalPath.replace(javaPath, ""); + return formatPackageName(cutPath); + } + + /** + * Utility method to determine if the folder is the app/src/main/java folder + * + * @param file file to check + * @return true if its the java folder + */ + private static boolean isJavaFolder(File file) { + if (file == null) { + return false; + } + if (!file.isDirectory()) { + return false; + } + + if (file.getName().equals("java")) { + File parent = file.getParentFile(); + if (parent == null) { + return false; + } else return parent.getName().equals("main"); + } + + return false; + } + + /** + * Gets the parent directory of a node, if the node is already a directory then + * it is returned + * + * @param node the node to search + * @return parent directory or itself if its already a directory + */ + public static File getDirectory(TreeNode node) { + File file = node.getContent().getFile(); + if (file.isDirectory()) { + return file; + } else { + return file.getParentFile(); + } + } + + /** + * Formats a path into a package name + * eg. com/my/test into com.my.test + * + * @param path input path + * @return formatted package name + */ + private static String formatPackageName(String path) { + if (path.startsWith("/")) { + path = path.substring(1); + } + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + + return path.replace("/", "."); + } + + public static boolean isResourceXMLDir(File dir) { + if (dir == null) { + return false; + } + File parent = dir.getParentFile(); + if (parent != null) { + return parent.getName().equals("res"); + } + return false; + } + + public static boolean isResourceXMLFile(@NonNull File file) { + if (!file.getName().endsWith(".xml")) { + return false; + } + return isResourceXMLDir(file.getParentFile()); + } + + public static boolean isLayoutXMLFile(@NonNull File file) { + if (!file.getName().endsWith(".xml")) { + return false; + } + + if (file.getParentFile() != null) { + File parent = file.getParentFile(); + if (parent.isDirectory() && parent.getName().startsWith("layout")) { + return isResourceXMLFile(file); + } + } + + return false; + } +} diff --git a/app/src/main/java/com/tyron/code/util/UiUtils.kt b/app/src/main/java/com/tyron/code/util/UiUtils.kt new file mode 100644 index 00000000..4321f5ea --- /dev/null +++ b/app/src/main/java/com/tyron/code/util/UiUtils.kt @@ -0,0 +1,65 @@ +package com.tyron.code.util + +import android.view.View +import android.view.ViewGroup +import androidx.core.view.* +import com.tyron.common.util.AndroidUtilities + +val Int.dp: Int + get() = AndroidUtilities.dpToPx(this.toFloat()) + +fun View.setMargins(left: Int? = null, top: Int? = null, right: Int? = null, bottom: Int? = null) { + val params = (layoutParams as? ViewGroup.MarginLayoutParams) + params?.setMargins( + left ?: params.leftMargin, + top ?: params.topMargin, + right ?: params.rightMargin, + bottom ?: params.bottomMargin) + layoutParams = params +} + +@JvmOverloads +fun View.addSystemWindowInsetToPadding( + left: Boolean = false, + top: Boolean = false, + right: Boolean = false, + bottom: Boolean = false +) { + val (initialLeft, initialTop, initialRight, initialBottom) = + listOf(paddingLeft, paddingTop, paddingRight, paddingBottom) + + ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + view.updatePadding( + left = initialLeft + (if (left) insets.left else 0), + top = initialTop + (if (top) insets.top else 0), + right = initialRight + (if (right) insets.right else 0), + bottom = initialBottom + (if (bottom) insets.bottom else 0) + ) + windowInsets + } +} + +@JvmOverloads +fun View.addSystemWindowInsetToMargin( + left: Boolean = false, + top: Boolean = false, + right: Boolean = false, + bottom: Boolean = false +) { + val (initialLeft, initialTop, initialRight, initialBottom) = + listOf(marginLeft, marginTop, marginRight, marginBottom) + + ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + view.updateLayoutParams { + updateMargins( + left = initialLeft + (if (left) insets.left else 0), + top = initialTop + (if (top) insets.top else 0), + right = initialRight + (if (right) insets.right else 0), + bottom = initialBottom + (if (bottom) insets.bottom else 0) + ) + } + windowInsets + } +} diff --git a/app/src/main/jniLibs/arm64-v8a/libaapt2.so b/app/src/main/jniLibs/arm64-v8a/libaapt2.so new file mode 100644 index 00000000..6a8dea1b Binary files /dev/null and b/app/src/main/jniLibs/arm64-v8a/libaapt2.so differ diff --git a/app/src/main/jniLibs/arm64-v8a/libzipalign.so b/app/src/main/jniLibs/arm64-v8a/libzipalign.so new file mode 100644 index 00000000..1ffa0065 Binary files /dev/null and b/app/src/main/jniLibs/arm64-v8a/libzipalign.so differ diff --git a/app/src/main/jniLibs/armeabi/libaapt2.so b/app/src/main/jniLibs/armeabi/libaapt2.so new file mode 100644 index 00000000..56610f04 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libaapt2.so differ diff --git a/app/src/main/jniLibs/armeabi/libzipalign.so b/app/src/main/jniLibs/armeabi/libzipalign.so new file mode 100644 index 00000000..7cb258d1 Binary files /dev/null and b/app/src/main/jniLibs/armeabi/libzipalign.so differ diff --git a/app/src/main/jniLibs/x86/libaapt2.so b/app/src/main/jniLibs/x86/libaapt2.so new file mode 100644 index 00000000..abf50ca5 Binary files /dev/null and b/app/src/main/jniLibs/x86/libaapt2.so differ diff --git a/app/src/main/jniLibs/x86/libzipalign.so b/app/src/main/jniLibs/x86/libzipalign.so new file mode 100644 index 00000000..95c43001 Binary files /dev/null and b/app/src/main/jniLibs/x86/libzipalign.so differ diff --git a/app/src/main/jniLibs/x86_64/libaapt2.so b/app/src/main/jniLibs/x86_64/libaapt2.so new file mode 100644 index 00000000..742a2f69 Binary files /dev/null and b/app/src/main/jniLibs/x86_64/libaapt2.so differ diff --git a/app/src/main/jniLibs/x86_64/libzipalign.so b/app/src/main/jniLibs/x86_64/libzipalign.so new file mode 100644 index 00000000..35119f6e Binary files /dev/null and b/app/src/main/jniLibs/x86_64/libzipalign.so differ diff --git a/app/src/main/libraries.json b/app/src/main/libraries.json new file mode 100644 index 00000000..7394d6f1 --- /dev/null +++ b/app/src/main/libraries.json @@ -0,0 +1,8 @@ +[ + { + "artifactId": "kotlin-stdlib", + "groupId": "org.jetbrains.kotlin", + "scope": "compile", + "versionName": "1.5.21" + } +] diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.jpeg b/app/src/main/res/drawable-hdpi/ic_launcher.jpeg new file mode 100644 index 00000000..982adb8d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher.jpeg differ diff --git a/app/src/main/res/drawable/ic_baseline_add_24.xml b/app/src/main/res/drawable/ic_baseline_add_24.xml new file mode 100644 index 00000000..eb232541 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_add_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_check_box_24.xml b/app/src/main/res/drawable/ic_baseline_check_box_24.xml new file mode 100644 index 00000000..298bc37e --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_check_box_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_code_24.xml b/app/src/main/res/drawable/ic_baseline_code_24.xml new file mode 100644 index 00000000..072f6204 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_code_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_crop_16_9_24.xml b/app/src/main/res/drawable/ic_baseline_crop_16_9_24.xml new file mode 100644 index 00000000..a7b4e80a --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_crop_16_9_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_edit_24.xml b/app/src/main/res/drawable/ic_baseline_edit_24.xml new file mode 100644 index 00000000..e93574bd --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_edit_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_edit_attributes_24.xml b/app/src/main/res/drawable/ic_baseline_edit_attributes_24.xml new file mode 100644 index 00000000..59d92ee5 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_edit_attributes_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_folder_open_24.xml b/app/src/main/res/drawable/ic_baseline_folder_open_24.xml new file mode 100644 index 00000000..f58b501e --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_folder_open_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_format_line_spacing_24.xml b/app/src/main/res/drawable/ic_baseline_format_line_spacing_24.xml new file mode 100644 index 00000000..3528e432 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_format_line_spacing_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_frame_24.xml b/app/src/main/res/drawable/ic_baseline_frame_24.xml new file mode 100644 index 00000000..4c8915aa --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_frame_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml new file mode 100644 index 00000000..884bee14 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml new file mode 100644 index 00000000..d3d62595 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_menu_24.xml b/app/src/main/res/drawable/ic_baseline_menu_24.xml new file mode 100644 index 00000000..4350ba96 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_menu_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_menu_book_24.xml b/app/src/main/res/drawable/ic_baseline_menu_book_24.xml new file mode 100644 index 00000000..f9029784 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_menu_book_24.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_baseline_open_in_new_24.xml b/app/src/main/res/drawable/ic_baseline_open_in_new_24.xml new file mode 100644 index 00000000..455b503a --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_open_in_new_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_style_24.xml b/app/src/main/res/drawable/ic_baseline_style_24.xml new file mode 100644 index 00000000..18adef3a --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_style_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_swipe_right_alt_24.xml b/app/src/main/res/drawable/ic_baseline_swipe_right_alt_24.xml new file mode 100644 index 00000000..7ab5f890 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_swipe_right_alt_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_text_fields_24.xml b/app/src/main/res/drawable/ic_baseline_text_fields_24.xml new file mode 100644 index 00000000..fb98d993 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_text_fields_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_vertical_24.xml b/app/src/main/res/drawable/ic_baseline_vertical_24.xml new file mode 100644 index 00000000..131945ea --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_vertical_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_icons8_discord.xml b/app/src/main/res/drawable/ic_icons8_discord.xml new file mode 100644 index 00000000..3a2379d0 --- /dev/null +++ b/app/src/main/res/drawable/ic_icons8_discord.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_icons8_telegram_app.xml b/app/src/main/res/drawable/ic_icons8_telegram_app.xml new file mode 100644 index 00000000..c09c7653 --- /dev/null +++ b/app/src/main/res/drawable/ic_icons8_telegram_app.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_vert_black_20dp.xml b/app/src/main/res/drawable/ic_more_vert_black_20dp.xml new file mode 100644 index 00000000..db671ca0 --- /dev/null +++ b/app/src/main/res/drawable/ic_more_vert_black_20dp.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_round_email_24.xml b/app/src/main/res/drawable/ic_round_email_24.xml new file mode 100644 index 00000000..8bb23889 --- /dev/null +++ b/app/src/main/res/drawable/ic_round_email_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_round_info_24.xml b/app/src/main/res/drawable/ic_round_info_24.xml new file mode 100644 index 00000000..0a8acfba --- /dev/null +++ b/app/src/main/res/drawable/ic_round_info_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_round_star_rate_24.xml b/app/src/main/res/drawable/ic_round_star_rate_24.xml new file mode 100644 index 00000000..61a30e5a --- /dev/null +++ b/app/src/main/res/drawable/ic_round_star_rate_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_stat_code.xml b/app/src/main/res/drawable/ic_stat_code.xml new file mode 100644 index 00000000..2f812a48 --- /dev/null +++ b/app/src/main/res/drawable/ic_stat_code.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/round_arrow_upward_20.xml b/app/src/main/res/drawable/round_arrow_upward_20.xml new file mode 100644 index 00000000..e8133536 --- /dev/null +++ b/app/src/main/res/drawable/round_arrow_upward_20.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/round_arrow_upward_24.xml b/app/src/main/res/drawable/round_arrow_upward_24.xml new file mode 100644 index 00000000..c40a9021 --- /dev/null +++ b/app/src/main/res/drawable/round_arrow_upward_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/round_content_copy_20.xml b/app/src/main/res/drawable/round_content_copy_20.xml new file mode 100644 index 00000000..9893f897 --- /dev/null +++ b/app/src/main/res/drawable/round_content_copy_20.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/drawable/round_content_cut_20.xml b/app/src/main/res/drawable/round_content_cut_20.xml new file mode 100644 index 00000000..b8b1e4b3 --- /dev/null +++ b/app/src/main/res/drawable/round_content_cut_20.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/main/res/drawable/round_content_paste_20.xml b/app/src/main/res/drawable/round_content_paste_20.xml new file mode 100644 index 00000000..69f86a80 --- /dev/null +++ b/app/src/main/res/drawable/round_content_paste_20.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/drawable/round_folder_20.xml b/app/src/main/res/drawable/round_folder_20.xml new file mode 100644 index 00000000..bbd32228 --- /dev/null +++ b/app/src/main/res/drawable/round_folder_20.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/round_folder_24.xml b/app/src/main/res/drawable/round_folder_24.xml new file mode 100644 index 00000000..8500b837 --- /dev/null +++ b/app/src/main/res/drawable/round_folder_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/round_insert_drive_file_20.xml b/app/src/main/res/drawable/round_insert_drive_file_20.xml new file mode 100644 index 00000000..8e6f013b --- /dev/null +++ b/app/src/main/res/drawable/round_insert_drive_file_20.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/round_insert_drive_file_24.xml b/app/src/main/res/drawable/round_insert_drive_file_24.xml new file mode 100644 index 00000000..096e3d5d --- /dev/null +++ b/app/src/main/res/drawable/round_insert_drive_file_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/round_play_arrow_20.xml b/app/src/main/res/drawable/round_play_arrow_20.xml new file mode 100644 index 00000000..a8db0591 --- /dev/null +++ b/app/src/main/res/drawable/round_play_arrow_20.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/round_play_arrow_24.xml b/app/src/main/res/drawable/round_play_arrow_24.xml new file mode 100644 index 00000000..d2ff9a09 --- /dev/null +++ b/app/src/main/res/drawable/round_play_arrow_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/round_save_20.xml b/app/src/main/res/drawable/round_save_20.xml new file mode 100644 index 00000000..f4970164 --- /dev/null +++ b/app/src/main/res/drawable/round_save_20.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/round_select_all_20.xml b/app/src/main/res/drawable/round_select_all_20.xml new file mode 100644 index 00000000..7bbd5960 --- /dev/null +++ b/app/src/main/res/drawable/round_select_all_20.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/tab_indicator.xml b/app/src/main/res/drawable/tab_indicator.xml new file mode 100644 index 00000000..ba87e982 --- /dev/null +++ b/app/src/main/res/drawable/tab_indicator.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/main/res/font/jetbrains_mono_regular.ttf b/app/src/main/res/font/jetbrains_mono_regular.ttf new file mode 100644 index 00000000..8da8aa40 Binary files /dev/null and b/app/src/main/res/font/jetbrains_mono_regular.ttf differ diff --git a/app/src/main/res/font/mono_font.xml b/app/src/main/res/font/mono_font.xml new file mode 100644 index 00000000..e8355771 --- /dev/null +++ b/app/src/main/res/font/mono_font.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/layout-sw600dp-land/main_fragment.xml b/app/src/main/res/layout-sw600dp-land/main_fragment.xml new file mode 100644 index 00000000..8df2d81d --- /dev/null +++ b/app/src/main/res/layout-sw600dp-land/main_fragment.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/add_dependency_dialog.xml b/app/src/main/res/layout/add_dependency_dialog.xml new file mode 100644 index 00000000..9dbf94ec --- /dev/null +++ b/app/src/main/res/layout/add_dependency_dialog.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/attribute_editor_dialog_fragment.xml b/app/src/main/res/layout/attribute_editor_dialog_fragment.xml new file mode 100644 index 00000000..3e64c4a3 --- /dev/null +++ b/app/src/main/res/layout/attribute_editor_dialog_fragment.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/attribute_editor_input.xml b/app/src/main/res/layout/attribute_editor_input.xml new file mode 100644 index 00000000..4912e0db --- /dev/null +++ b/app/src/main/res/layout/attribute_editor_input.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/attribute_editor_item.xml b/app/src/main/res/layout/attribute_editor_item.xml new file mode 100644 index 00000000..5bbead9b --- /dev/null +++ b/app/src/main/res/layout/attribute_editor_item.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/base_textinput_layout.xml b/app/src/main/res/layout/base_textinput_layout.xml new file mode 100644 index 00000000..911fbd28 --- /dev/null +++ b/app/src/main/res/layout/base_textinput_layout.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_editor_fragment.xml b/app/src/main/res/layout/bottom_editor_fragment.xml new file mode 100644 index 00000000..c5369cbf --- /dev/null +++ b/app/src/main/res/layout/bottom_editor_fragment.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/code_editor_fragment.xml b/app/src/main/res/layout/code_editor_fragment.xml new file mode 100644 index 00000000..6ca01b3e --- /dev/null +++ b/app/src/main/res/layout/code_editor_fragment.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/create_class_dialog.xml b/app/src/main/res/layout/create_class_dialog.xml new file mode 100644 index 00000000..95067897 --- /dev/null +++ b/app/src/main/res/layout/create_class_dialog.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/default_completion_result_item.xml b/app/src/main/res/layout/default_completion_result_item.xml new file mode 100644 index 00000000..c342f235 --- /dev/null +++ b/app/src/main/res/layout/default_completion_result_item.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/editor_container_fragment.xml b/app/src/main/res/layout/editor_container_fragment.xml new file mode 100644 index 00000000..fe7f40d7 --- /dev/null +++ b/app/src/main/res/layout/editor_container_fragment.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/editor_palette_item.xml b/app/src/main/res/layout/editor_palette_item.xml new file mode 100644 index 00000000..74207128 --- /dev/null +++ b/app/src/main/res/layout/editor_palette_item.xml @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/empty_projects_layout.xml b/app/src/main/res/layout/empty_projects_layout.xml new file mode 100644 index 00000000..1fa87dd8 --- /dev/null +++ b/app/src/main/res/layout/empty_projects_layout.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/app/src/main/res/layout/file_manager_fragment.xml b/app/src/main/res/layout/file_manager_fragment.xml new file mode 100644 index 00000000..c3663e5f --- /dev/null +++ b/app/src/main/res/layout/file_manager_fragment.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/layout/file_manager_item.xml b/app/src/main/res/layout/file_manager_item.xml new file mode 100644 index 00000000..cf7d9278 --- /dev/null +++ b/app/src/main/res/layout/file_manager_item.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_editor_fragment.xml b/app/src/main/res/layout/layout_editor_fragment.xml new file mode 100644 index 00000000..f6aeebd7 --- /dev/null +++ b/app/src/main/res/layout/layout_editor_fragment.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/library_manager_fragment.xml b/app/src/main/res/layout/library_manager_fragment.xml new file mode 100644 index 00000000..f98a4cd6 --- /dev/null +++ b/app/src/main/res/layout/library_manager_fragment.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/library_manager_item.xml b/app/src/main/res/layout/library_manager_item.xml new file mode 100644 index 00000000..2b8b0e2b --- /dev/null +++ b/app/src/main/res/layout/library_manager_item.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/loading_layout.xml b/app/src/main/res/layout/loading_layout.xml new file mode 100644 index 00000000..b4044cfa --- /dev/null +++ b/app/src/main/res/layout/loading_layout.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml new file mode 100644 index 00000000..47355200 --- /dev/null +++ b/app/src/main/res/layout/main.xml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/app/src/main/res/layout/main_fragment.xml b/app/src/main/res/layout/main_fragment.xml new file mode 100644 index 00000000..46967cb6 --- /dev/null +++ b/app/src/main/res/layout/main_fragment.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/preview_view.xml b/app/src/main/res/layout/preview_view.xml new file mode 100644 index 00000000..d70cb643 --- /dev/null +++ b/app/src/main/res/layout/preview_view.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/project_item.xml b/app/src/main/res/layout/project_item.xml new file mode 100644 index 00000000..aa6e509f --- /dev/null +++ b/app/src/main/res/layout/project_item.xml @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/project_manager_fragment.xml b/app/src/main/res/layout/project_manager_fragment.xml new file mode 100644 index 00000000..67767976 --- /dev/null +++ b/app/src/main/res/layout/project_manager_fragment.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/settings_activity.xml b/app/src/main/res/layout/settings_activity.xml new file mode 100644 index 00000000..ad4d603f --- /dev/null +++ b/app/src/main/res/layout/settings_activity.xml @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/setup_footer.xml b/app/src/main/res/layout/setup_footer.xml new file mode 100644 index 00000000..0febd77e --- /dev/null +++ b/app/src/main/res/layout/setup_footer.xml @@ -0,0 +1,39 @@ + + + +