mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-15 23:44:48 +08:00
Reformat all source file
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
---
|
||||
name: Bug
|
||||
about: Report a bug in the IDE.
|
||||
title: ""
|
||||
labels: bug
|
||||
assignees: ''
|
||||
name: Bug about: Report a bug in the IDE. title: ""
|
||||
labels: bug assignees: ''
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
@@ -20,9 +17,10 @@ Steps to reproduce the behavior:
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**Details (please complete the following information):**
|
||||
- Device: [e.g. realme 3 Pro]
|
||||
- OS: [e.g. Android 11]
|
||||
- AndroidIDE Version [e.g. v1.0.3-alpha]
|
||||
|
||||
- Device: [e.g. realme 3 Pro]
|
||||
- OS: [e.g. Android 11]
|
||||
- AndroidIDE Version [e.g. v1.0.3-alpha]
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context about the problem here. -->
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE]: "
|
||||
labels: feature
|
||||
assignees: ''
|
||||
name: Feature request about: Suggest an idea for this project title: "[FEATURE]: "
|
||||
labels: feature assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
+56
-70
@@ -2,124 +2,110 @@
|
||||
|
||||
## 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 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.
|
||||
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:
|
||||
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
|
||||
* 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
|
||||
* 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
|
||||
* 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 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.
|
||||
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.
|
||||
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 either by opening
|
||||
an issue or writing me an email at [itsaky01@gmail.com](mailto:itsaky01@gmail.com).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community
|
||||
leaders responsible for enforcement either by opening an issue or writing me an email
|
||||
at [itsaky01@gmail.com](mailto:itsaky01@gmail.com). All complaints will be reviewed and investigated
|
||||
promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
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:
|
||||
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.
|
||||
**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.
|
||||
**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.
|
||||
**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.
|
||||
**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.
|
||||
**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.
|
||||
**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.
|
||||
**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.
|
||||
**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
|
||||
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).
|
||||
Community Impact Guidelines were inspired
|
||||
by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
|
||||
+23
-9
@@ -1,8 +1,12 @@
|
||||
# Contributing
|
||||
This document outlines how to propose a change to AndrodIDE. By contributing to this project, you agree to abide the terms specified in [CODE OF CONDUCT](./CODE_OF_CONDUCT.md).
|
||||
|
||||
This document outlines how to propose a change to AndrodIDE. By contributing to this project, you
|
||||
agree to abide the terms specified in [CODE OF CONDUCT](./CODE_OF_CONDUCT.md).
|
||||
|
||||
## Propose a change
|
||||
|
||||
To contribute to this project,
|
||||
|
||||
- Fork the repo.
|
||||
- Clone the forked repo to your local machine.
|
||||
- Open the project in Android Studio.
|
||||
@@ -10,19 +14,29 @@ To contribute to this project,
|
||||
- Create a pull request.
|
||||
|
||||
## Note
|
||||
`Java 11` is required to build the project.
|
||||
|
||||
Building AndroidIDE in AndroidIDE itself is also possible. But that might require some modifications.
|
||||
`Java 11` is required to build the project.
|
||||
|
||||
If you face `AAPT2` issue while building AndroidIDE in the IDE itself, specify the `android.aapt2FromMavenOverride` property in `gradle.properties` with location of `aapt2` binary from Android SDK. However, this is an **experimental** property.
|
||||
Building AndroidIDE in AndroidIDE itself is also possible. But that might require some
|
||||
modifications.
|
||||
|
||||
If you face `AAPT2` issue while building AndroidIDE in the IDE itself, specify
|
||||
the `android.aapt2FromMavenOverride` property in `gradle.properties` with location of `aapt2` binary
|
||||
from Android SDK. However, this is an **experimental** property.
|
||||
|
||||
## Source code format
|
||||
The source code must have `2-space` indentation (soft tab).
|
||||
Java source code is formatted using `google-java-format` with `GOOGLE` style formatting.
|
||||
You can execute `./gradlew formatJavaSources` in terminal to format all Java source files using Google Java Format.
|
||||
|
||||
The source code must have `2-space` indentation (soft tab). Java source code is formatted
|
||||
using `google-java-format` with `GOOGLE` style formatting. You can
|
||||
execute `./gradlew formatJavaSources` in terminal to format all Java source files using Google Java
|
||||
Format.
|
||||
**This will commit the changes as well**.
|
||||
|
||||
## Issues & Pull requests
|
||||
Use the issue/PR templates when possible. Provide a proper title and a clear description for the issue/PR. For bug reports, provide a step-by-step procedure to reproduce the issue. Always put crash logs, build outputs, etc in `code blocks`.
|
||||
|
||||
If you want to discuss anything about the project, you can discuss it in the [Telegram group](https://t.me/androidide_discussions).
|
||||
Use the issue/PR templates when possible. Provide a proper title and a clear description for the
|
||||
issue/PR. For bug reports, provide a step-by-step procedure to reproduce the issue. Always put crash
|
||||
logs, build outputs, etc in `code blocks`.
|
||||
|
||||
If you want to discuss anything about the project, you can discuss it in
|
||||
the [Telegram group](https://t.me/androidide_discussions).
|
||||
@@ -10,6 +10,7 @@
|
||||
</p><br>
|
||||
|
||||
## Features
|
||||
|
||||
- [x] Gradle support.
|
||||
- [x] `JDK 11` and `JDK 17` available for use.
|
||||
- [x] Terminal with necessary packages.
|
||||
@@ -17,49 +18,65 @@
|
||||
- [x] SDK Manager (Available via terminal).
|
||||
- [x] API information for classes and their members (since, removed, deprecated).
|
||||
- [ ] Language servers
|
||||
- [x] Java
|
||||
- [x] XML (*)
|
||||
- [ ] Kotlin
|
||||
- [x] Java
|
||||
- [x] XML (*)
|
||||
- [ ] Kotlin
|
||||
- [ ] UI Designer
|
||||
- [x] Layout inflater
|
||||
- [x] Resolve resource references
|
||||
- [x] Auto-complete resource values when user edits attributes using the attribute editor
|
||||
- [x] Drag & Drop (*)
|
||||
- [x] Visual attribute editor (*)
|
||||
- [x] Android Widgets (*)
|
||||
- [x] Layout inflater
|
||||
- [x] Resolve resource references
|
||||
- [x] Auto-complete resource values when user edits attributes using the attribute editor
|
||||
- [x] Drag & Drop (*)
|
||||
- [x] Visual attribute editor (*)
|
||||
- [x] Android Widgets (*)
|
||||
- [ ] String Translator
|
||||
- [ ] Asset Studio (Drawable & Icon Maker)
|
||||
- [x] Git (*)
|
||||
|
||||
Items marked with `(*)` are partially implemented.
|
||||
`XML` language server supports only code completion at the moment. Git is currently available only in the terminal i.e. no GUI interface for `git`.
|
||||
`XML` language server supports only code completion at the moment. Git is currently available only
|
||||
in the terminal i.e. no GUI interface for `git`.
|
||||
|
||||
## Installation
|
||||
- Download the AndroidIDE APK from [releases](https://github.com/itsaky/AndroidIDE/releases). You can also download APKs from [GitHub actions](https://github.com/itsaky/AndroidIDE/actions).
|
||||
- Follow the instructions [here](https://github.com/itsaky/androidide-build-tools#installing-in-androidide) to install the build tools.
|
||||
|
||||
- Download the AndroidIDE APK from [releases](https://github.com/itsaky/AndroidIDE/releases). You
|
||||
can also download APKs from [GitHub actions](https://github.com/itsaky/AndroidIDE/actions).
|
||||
- Follow the
|
||||
instructions [here](https://github.com/itsaky/androidide-build-tools#installing-in-androidide) to
|
||||
install the build tools.
|
||||
|
||||
## Limitations
|
||||
- SDK Manager is already included in Android SDK and is accessible in AndroidIDE via its Terminal. But, you cannot use it to install some tools (like NDK) because those tools are not built for Android.
|
||||
|
||||
- SDK Manager is already included in Android SDK and is accessible in AndroidIDE via its Terminal.
|
||||
But, you cannot use it to install some tools (like NDK) because those tools are not built for
|
||||
Android.
|
||||
- No official NDK support because we haven't built the NDK for Android.
|
||||
|
||||
The app is still being developed actively. It's in beta stage and may not be stable. if you have any issues using the app, please let us know.
|
||||
The app is still being developed actively. It's in beta stage and may not be stable. if you have any
|
||||
issues using the app, please let us know.
|
||||
|
||||
## Contributing
|
||||
|
||||
See the [contributing guide](./CONTRIBUTING.md).
|
||||
|
||||
## Thanks to
|
||||
- [Rosemoe](https://github.com/Rosemoe) for the awesome [CodeEditor](https://github.com/Rosemoe/sora-editor)
|
||||
|
||||
- [Rosemoe](https://github.com/Rosemoe) for the
|
||||
awesome [CodeEditor](https://github.com/Rosemoe/sora-editor)
|
||||
- [Termux](https://github.com/termux) for [Terminal Emulator](https://github.com/termux/termux-app)
|
||||
- [Bogdan Melnychuk](https://github.com/bmelnychuk) for [AndroidTreeView](https://github.com/bmelnychuk/AndroidTreeView)
|
||||
- [George Fraser](https://github.com/georgewfraser) for the [Java Language Server](https://github.com/georgewfraser/java-language-server)
|
||||
- [Bogdan Melnychuk](https://github.com/bmelnychuk)
|
||||
for [AndroidTreeView](https://github.com/bmelnychuk/AndroidTreeView)
|
||||
- [George Fraser](https://github.com/georgewfraser) for
|
||||
the [Java Language Server](https://github.com/georgewfraser/java-language-server)
|
||||
|
||||
And all the developers who have contributed to this project.
|
||||
|
||||
## Contact Us
|
||||
|
||||
- [Website](https://androidide.com)
|
||||
- [Telegram](https://t.me/androidide_discussions)
|
||||
|
||||
## License
|
||||
|
||||
```
|
||||
AndroidIDE is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -75,4 +92,5 @@ You should have received a copy of the GNU General Public License
|
||||
along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
```
|
||||
|
||||
Any violations to the license can be reported either by opening an issue or writing a mail to us directly.
|
||||
Any violations to the license can be reported either by opening an issue or writing a mail to us
|
||||
directly.
|
||||
|
||||
@@ -4,14 +4,14 @@ apply plugin: 'kotlin-android'
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdk
|
||||
buildToolsVersion rootProject.ext.buildTools
|
||||
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdk
|
||||
targetSdkVersion rootProject.ext.targetSdk
|
||||
}
|
||||
|
||||
|
||||
buildFeatures.viewBinding true
|
||||
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility rootProject.ext.javaSourceVersion
|
||||
targetCompatibility rootProject.ext.javaTargetVersion
|
||||
@@ -20,7 +20,7 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
@@ -29,14 +29,14 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':common')
|
||||
implementation project (':common')
|
||||
implementation common.editor
|
||||
implementation common.utilcode
|
||||
|
||||
|
||||
implementation androidx.ktx
|
||||
implementation common.kotlin
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenCentral ()
|
||||
}
|
||||
@@ -26,88 +26,88 @@ import android.graphics.drawable.Drawable
|
||||
*/
|
||||
interface ActionItem {
|
||||
|
||||
val id: String
|
||||
var label: String
|
||||
var visible: Boolean
|
||||
var enabled: Boolean
|
||||
var icon: Drawable?
|
||||
var requiresUIThread: Boolean
|
||||
var location: Location
|
||||
val id: String
|
||||
var label: String
|
||||
var visible: Boolean
|
||||
var enabled: Boolean
|
||||
var icon: Drawable?
|
||||
var requiresUIThread: Boolean
|
||||
var location: Location
|
||||
|
||||
/**
|
||||
* Prepare the action. Subclasses can modify the visual properties of this action here.
|
||||
* @param data The data containing various information about the event.
|
||||
*/
|
||||
fun prepare(data: ActionData)
|
||||
/**
|
||||
* Prepare the action. Subclasses can modify the visual properties of this action here.
|
||||
* @param data The data containing various information about the event.
|
||||
*/
|
||||
fun prepare(data: ActionData)
|
||||
|
||||
/**
|
||||
* Execute the action. The action executed in a background thread by default.
|
||||
*
|
||||
* @param data The data containing various information about the event.
|
||||
* @return `true` if this action was executed successfully, `false` otherwise.
|
||||
*/
|
||||
fun execAction(data: ActionData): Any
|
||||
/**
|
||||
* Execute the action. The action executed in a background thread by default.
|
||||
*
|
||||
* @param data The data containing various information about the event.
|
||||
* @return `true` if this action was executed successfully, `false` otherwise.
|
||||
*/
|
||||
fun execAction(data: ActionData): Any
|
||||
|
||||
/**
|
||||
* Called just after the [execAction] method executes **successfully** (i.e. returns `true`).
|
||||
* Subclasses are free to do UI related work here as this method is called on UI thread.
|
||||
*
|
||||
* @param data The data containing various information about the event.
|
||||
*/
|
||||
fun postExec(data: ActionData, result: Any) = Unit
|
||||
/**
|
||||
* Called just after the [execAction] method executes **successfully** (i.e. returns `true`).
|
||||
* Subclasses are free to do UI related work here as this method is called on UI thread.
|
||||
*
|
||||
* @param data The data containing various information about the event.
|
||||
*/
|
||||
fun postExec(data: ActionData, result: Any) = Unit
|
||||
|
||||
/**
|
||||
* Return the show as action flags for the menu item.
|
||||
*
|
||||
* @return The show as action flags.
|
||||
*/
|
||||
fun getShowAsActionFlags(data: ActionData): Int = -1
|
||||
/**
|
||||
* Return the show as action flags for the menu item.
|
||||
*
|
||||
* @return The show as action flags.
|
||||
*/
|
||||
fun getShowAsActionFlags(data: ActionData): Int = -1
|
||||
|
||||
/**
|
||||
* Checks if the given [ActionData] has instances of the given [types].
|
||||
*
|
||||
* @param data The data to check.
|
||||
* @param types The type of objects to look for.
|
||||
* @return `true` if the [data] has the given [types], `false` otherwise.
|
||||
*/
|
||||
fun hasRequiredData(data: ActionData, vararg types: Class<*>): Boolean {
|
||||
for (type in types) {
|
||||
data.get(type) ?: return false
|
||||
}
|
||||
|
||||
return true
|
||||
/**
|
||||
* Checks if the given [ActionData] has instances of the given [types].
|
||||
*
|
||||
* @param data The data to check.
|
||||
* @param types The type of objects to look for.
|
||||
* @return `true` if the [data] has the given [types], `false` otherwise.
|
||||
*/
|
||||
fun hasRequiredData(data: ActionData, vararg types: Class<*>): Boolean {
|
||||
for (type in types) {
|
||||
data.get(type) ?: return false
|
||||
}
|
||||
|
||||
/** Marks this action item as invisible. */
|
||||
fun markInvisible() {
|
||||
visible = false
|
||||
enabled = false
|
||||
return true
|
||||
}
|
||||
|
||||
/** Marks this action item as invisible. */
|
||||
fun markInvisible() {
|
||||
visible = false
|
||||
enabled = false
|
||||
}
|
||||
|
||||
/** Location where an action item will be shown. */
|
||||
enum class Location(val id: String) {
|
||||
|
||||
/** Location marker for action items shown in editor activity's toolbar. */
|
||||
EDITOR_TOOLBAR("editor.activity.toolbar"),
|
||||
|
||||
/** Location marker for action items shown in editor's text action menu. */
|
||||
EDITOR_TEXT_ACTIONS("editor.textActions"),
|
||||
|
||||
/**
|
||||
* Location marker for action items shown in 'Code actions' submenu in editor's text action
|
||||
* menu.
|
||||
*/
|
||||
EDITOR_CODE_ACTIONS("editor.codeActions"),
|
||||
|
||||
/** Location marker for action items show in 'Find & Replace' action mode menu in editor. */
|
||||
EDITOR_SEARCH_ACTION_MODE("editor.searchActionMode");
|
||||
|
||||
override fun toString(): String {
|
||||
return id
|
||||
}
|
||||
|
||||
/** Location where an action item will be shown. */
|
||||
enum class Location(val id: String) {
|
||||
|
||||
/** Location marker for action items shown in editor activity's toolbar. */
|
||||
EDITOR_TOOLBAR("editor.activity.toolbar"),
|
||||
|
||||
/** Location marker for action items shown in editor's text action menu. */
|
||||
EDITOR_TEXT_ACTIONS("editor.textActions"),
|
||||
|
||||
/**
|
||||
* Location marker for action items shown in 'Code actions' submenu in editor's text action
|
||||
* menu.
|
||||
*/
|
||||
EDITOR_CODE_ACTIONS("editor.codeActions"),
|
||||
|
||||
/** Location marker for action items show in 'Find & Replace' action mode menu in editor. */
|
||||
EDITOR_SEARCH_ACTION_MODE("editor.searchActionMode");
|
||||
|
||||
override fun toString(): String {
|
||||
return id
|
||||
}
|
||||
|
||||
fun forId(id: String): Location {
|
||||
return values().first { it.id == id }
|
||||
}
|
||||
fun forId(id: String): Location {
|
||||
return values().first { it.id == id }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,14 +23,14 @@ package com.itsaky.androidide.actions
|
||||
*/
|
||||
interface ActionMenu : ActionItem {
|
||||
|
||||
val children: MutableSet<ActionItem>
|
||||
val children: MutableSet<ActionItem>
|
||||
|
||||
fun addAction(action: ActionItem) = children.add(action)
|
||||
fun addAction(action: ActionItem) = children.add(action)
|
||||
|
||||
fun removeAction(action: ActionItem) = children.remove(action)
|
||||
fun removeAction(action: ActionItem) = children.remove(action)
|
||||
|
||||
/** Action menus are not supposed to perform any action */
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
return false
|
||||
}
|
||||
/** Action menus are not supposed to perform any action */
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,87 +22,87 @@ import com.itsaky.androidide.actions.internal.DefaultActionsRegistry
|
||||
/** @author Akash Yadav */
|
||||
abstract class ActionsRegistry {
|
||||
|
||||
companion object {
|
||||
companion object {
|
||||
|
||||
private var instance: ActionsRegistry? = null
|
||||
private var instance: ActionsRegistry? = null
|
||||
|
||||
@JvmStatic
|
||||
fun getInstance(): ActionsRegistry {
|
||||
if (instance == null) {
|
||||
instance = DefaultActionsRegistry()
|
||||
}
|
||||
@JvmStatic
|
||||
fun getInstance(): ActionsRegistry {
|
||||
if (instance == null) {
|
||||
instance = DefaultActionsRegistry()
|
||||
}
|
||||
|
||||
return instance as ActionsRegistry
|
||||
}
|
||||
return instance as ActionsRegistry
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given action.
|
||||
*
|
||||
* @param action The action to register. Must not be `null`.
|
||||
* @return `true` if the action was registered successfully. `false` otherwise.
|
||||
*/
|
||||
abstract fun registerAction(action: ActionItem): Boolean
|
||||
|
||||
/**
|
||||
* Unregister the given action.
|
||||
*
|
||||
* @param action The action to unregister. Must not be `null`.
|
||||
* @return `true` if the action was unregistered successfully. `false` otherwise.
|
||||
*/
|
||||
abstract fun unregisterAction(action: ActionItem): Boolean
|
||||
|
||||
/**
|
||||
* Unregister the action with the given ID.
|
||||
*
|
||||
* @param id The ID of action to unregister. Must not be `null`.
|
||||
* @return `true` if the action was unregistered successfully. `false` otherwise.
|
||||
*/
|
||||
abstract fun unregisterAction(id: String): Boolean
|
||||
|
||||
/**
|
||||
* Find the action with the given ID.
|
||||
*
|
||||
* @param id The ID of the action to find.
|
||||
* @return The found action or `null`.
|
||||
*/
|
||||
abstract fun findAction(location: ActionItem.Location, id: String): ActionItem?
|
||||
|
||||
/**
|
||||
* Fill the given menu with the registered actions.
|
||||
*
|
||||
* Subclasses must first call [ActionItem.prepare] to update the action.
|
||||
*/
|
||||
abstract fun fillMenu(data: ActionData, location: ActionItem.Location, menu: Menu)
|
||||
|
||||
/** Get all the registered actions at the given location. */
|
||||
abstract fun getActions(location: ActionItem.Location): Map<String, ActionItem>
|
||||
/** Clear all the registered actions. */
|
||||
abstract fun clearActions(location: ActionItem.Location)
|
||||
|
||||
/**
|
||||
* Register a listener that will be called when any action is executed. The callbacks are called
|
||||
* irrespective of the action's execution result.
|
||||
*
|
||||
* @param listener The listener to register.
|
||||
*/
|
||||
abstract fun registerActionExecListener(listener: ActionExecListener)
|
||||
|
||||
/**
|
||||
* Unregister the given listener.
|
||||
*
|
||||
* @param listener The listener to unregister.
|
||||
*/
|
||||
abstract fun unregisterActionExecListener(listener: ActionExecListener)
|
||||
|
||||
/** Listener for listening to callbacks for action execution. */
|
||||
interface ActionExecListener {
|
||||
|
||||
/**
|
||||
* Register the given action.
|
||||
*
|
||||
* @param action The action to register. Must not be `null`.
|
||||
* @return `true` if the action was registered successfully. `false` otherwise.
|
||||
* The given action was executed.
|
||||
* @param action The action that was executed.
|
||||
* @param result The result of the action execution.
|
||||
*/
|
||||
abstract fun registerAction(action: ActionItem): Boolean
|
||||
|
||||
/**
|
||||
* Unregister the given action.
|
||||
*
|
||||
* @param action The action to unregister. Must not be `null`.
|
||||
* @return `true` if the action was unregistered successfully. `false` otherwise.
|
||||
*/
|
||||
abstract fun unregisterAction(action: ActionItem): Boolean
|
||||
|
||||
/**
|
||||
* Unregister the action with the given ID.
|
||||
*
|
||||
* @param id The ID of action to unregister. Must not be `null`.
|
||||
* @return `true` if the action was unregistered successfully. `false` otherwise.
|
||||
*/
|
||||
abstract fun unregisterAction(id: String): Boolean
|
||||
|
||||
/**
|
||||
* Find the action with the given ID.
|
||||
*
|
||||
* @param id The ID of the action to find.
|
||||
* @return The found action or `null`.
|
||||
*/
|
||||
abstract fun findAction(location: ActionItem.Location, id: String): ActionItem?
|
||||
|
||||
/**
|
||||
* Fill the given menu with the registered actions.
|
||||
*
|
||||
* Subclasses must first call [ActionItem.prepare] to update the action.
|
||||
*/
|
||||
abstract fun fillMenu(data: ActionData, location: ActionItem.Location, menu: Menu)
|
||||
|
||||
/** Get all the registered actions at the given location. */
|
||||
abstract fun getActions(location: ActionItem.Location): Map<String, ActionItem>
|
||||
/** Clear all the registered actions. */
|
||||
abstract fun clearActions(location: ActionItem.Location)
|
||||
|
||||
/**
|
||||
* Register a listener that will be called when any action is executed. The callbacks are called
|
||||
* irrespective of the action's execution result.
|
||||
*
|
||||
* @param listener The listener to register.
|
||||
*/
|
||||
abstract fun registerActionExecListener(listener: ActionExecListener)
|
||||
|
||||
/**
|
||||
* Unregister the given listener.
|
||||
*
|
||||
* @param listener The listener to unregister.
|
||||
*/
|
||||
abstract fun unregisterActionExecListener(listener: ActionExecListener)
|
||||
|
||||
/** Listener for listening to callbacks for action execution. */
|
||||
interface ActionExecListener {
|
||||
|
||||
/**
|
||||
* The given action was executed.
|
||||
* @param action The action that was executed.
|
||||
* @param result The result of the action execution.
|
||||
*/
|
||||
fun onExec(action: ActionItem, result: Any)
|
||||
}
|
||||
fun onExec(action: ActionItem, result: Any)
|
||||
}
|
||||
}
|
||||
|
||||
+119
-119
@@ -33,142 +33,142 @@ import java.util.concurrent.*
|
||||
*/
|
||||
class DefaultActionsRegistry : ActionsRegistry() {
|
||||
|
||||
private val log = ILogger.newInstance("DefaultActionsRegistry")
|
||||
private val actions: MutableMap<String, MutableMap<String, ActionItem>> = mutableMapOf()
|
||||
private val listeners = mutableSetOf<ActionExecListener>()
|
||||
private val log = ILogger.newInstance("DefaultActionsRegistry")
|
||||
private val actions: MutableMap<String, MutableMap<String, ActionItem>> = mutableMapOf()
|
||||
private val listeners = mutableSetOf<ActionExecListener>()
|
||||
|
||||
override fun getActions(location: ActionItem.Location): MutableMap<String, ActionItem> {
|
||||
if (actions[location.id] == null) {
|
||||
actions[location.id] = mutableMapOf()
|
||||
}
|
||||
|
||||
return actions[location.id]!!
|
||||
override fun getActions(location: ActionItem.Location): MutableMap<String, ActionItem> {
|
||||
if (actions[location.id] == null) {
|
||||
actions[location.id] = mutableMapOf()
|
||||
}
|
||||
|
||||
override fun registerAction(action: ActionItem): Boolean {
|
||||
val actionsAtLocation = getActions(action.location)
|
||||
actionsAtLocation[action.id] = action
|
||||
return actions[location.id]!!
|
||||
}
|
||||
|
||||
override fun registerAction(action: ActionItem): Boolean {
|
||||
val actionsAtLocation = getActions(action.location)
|
||||
actionsAtLocation[action.id] = action
|
||||
return true
|
||||
}
|
||||
|
||||
override fun unregisterAction(action: ActionItem): Boolean {
|
||||
val actions = getActions(action.location)
|
||||
val older = actions.remove(action.id)
|
||||
if (older != null) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun unregisterAction(id: String): Boolean {
|
||||
for (locations in this.actions.values) {
|
||||
val older = locations.remove(id)
|
||||
if (older != null) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
override fun unregisterAction(action: ActionItem): Boolean {
|
||||
val actions = getActions(action.location)
|
||||
val older = actions.remove(action.id)
|
||||
if (older != null) {
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
override fun findAction(location: ActionItem.Location, id: String): ActionItem? =
|
||||
getActions(location)[id]
|
||||
|
||||
override fun clearActions(location: ActionItem.Location) = getActions(location).clear()
|
||||
|
||||
override fun registerActionExecListener(listener: ActionExecListener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
override fun unregisterActionExecListener(listener: ActionExecListener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
override fun fillMenu(data: ActionData, location: ActionItem.Location, menu: Menu) {
|
||||
val actions = getActions(location)
|
||||
|
||||
for (action in actions.values) {
|
||||
action.prepare(data)
|
||||
|
||||
if (!action.visible) {
|
||||
continue
|
||||
}
|
||||
addActionToMenu(menu, action, data)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addActionToMenu(menu: Menu, action: ActionItem, data: ActionData) {
|
||||
val item: MenuItem =
|
||||
if (action is ActionMenu) {
|
||||
val sub = menu.addSubMenu(action.label)
|
||||
|
||||
for (subItem in action.children) {
|
||||
subItem.prepare(data)
|
||||
if (subItem.visible) {
|
||||
addActionToMenu(sub, subItem, data)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
sub.item
|
||||
} else {
|
||||
menu.add(action.label)
|
||||
}
|
||||
|
||||
item.isEnabled = action.enabled
|
||||
item.icon = action.icon
|
||||
|
||||
if (item.icon != null) {
|
||||
item.icon.alpha = if (action.enabled) 255 else 76
|
||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
|
||||
} else {
|
||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
|
||||
}
|
||||
|
||||
override fun unregisterAction(id: String): Boolean {
|
||||
for (locations in this.actions.values) {
|
||||
val older = locations.remove(id)
|
||||
if (older != null) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
if (action.getShowAsActionFlags(data) != -1) {
|
||||
item.setShowAsAction(action.getShowAsActionFlags(data))
|
||||
}
|
||||
|
||||
override fun findAction(location: ActionItem.Location, id: String): ActionItem? =
|
||||
getActions(location)[id]
|
||||
|
||||
override fun clearActions(location: ActionItem.Location) = getActions(location).clear()
|
||||
|
||||
override fun registerActionExecListener(listener: ActionExecListener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
override fun unregisterActionExecListener(listener: ActionExecListener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
override fun fillMenu(data: ActionData, location: ActionItem.Location, menu: Menu) {
|
||||
val actions = getActions(location)
|
||||
|
||||
for (action in actions.values) {
|
||||
action.prepare(data)
|
||||
|
||||
if (!action.visible) {
|
||||
continue
|
||||
}
|
||||
addActionToMenu(menu, action, data)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addActionToMenu(menu: Menu, action: ActionItem, data: ActionData) {
|
||||
val item: MenuItem =
|
||||
if (action is ActionMenu) {
|
||||
val sub = menu.addSubMenu(action.label)
|
||||
|
||||
for (subItem in action.children) {
|
||||
subItem.prepare(data)
|
||||
if (subItem.visible) {
|
||||
addActionToMenu(sub, subItem, data)
|
||||
}
|
||||
}
|
||||
|
||||
sub.item
|
||||
} else {
|
||||
menu.add(action.label)
|
||||
}
|
||||
|
||||
item.isEnabled = action.enabled
|
||||
item.icon = action.icon
|
||||
|
||||
if (item.icon != null) {
|
||||
item.icon.alpha = if (action.enabled) 255 else 76
|
||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
|
||||
if (action !is ActionMenu) {
|
||||
item.setOnMenuItemClickListener {
|
||||
if (action.requiresUIThread) {
|
||||
ThreadUtils.runOnUiThread {
|
||||
val result = action.execAction(data)
|
||||
action.postExec(data, result)
|
||||
notifyActionExec(action, result)
|
||||
}
|
||||
} else {
|
||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
|
||||
execInBackground(action, data, it)
|
||||
}
|
||||
|
||||
if (action.getShowAsActionFlags(data) != -1) {
|
||||
item.setShowAsAction(action.getShowAsActionFlags(data))
|
||||
}
|
||||
|
||||
if (action !is ActionMenu) {
|
||||
item.setOnMenuItemClickListener {
|
||||
if (action.requiresUIThread) {
|
||||
ThreadUtils.runOnUiThread {
|
||||
val result = action.execAction(data)
|
||||
action.postExec(data, result)
|
||||
notifyActionExec(action, result)
|
||||
}
|
||||
} else {
|
||||
execInBackground(action, data, it)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun execInBackground(action: ActionItem, data: ActionData, it: MenuItem) {
|
||||
val start = System.currentTimeMillis()
|
||||
CompletableFuture.supplyAsync { action.execAction(data) }
|
||||
.whenComplete { result, error ->
|
||||
if (result == null || (result is Boolean && !result) || error != null) {
|
||||
log.error(
|
||||
"An error occurred when performing action '${it.title}'. Action failed in ${System.currentTimeMillis() - start}ms",
|
||||
error)
|
||||
} else {
|
||||
log.info(
|
||||
"Action '${it.title}' completed in ${System.currentTimeMillis() - start}ms")
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread {
|
||||
action.postExec(data, result ?: false)
|
||||
notifyActionExec(action, result ?: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyActionExec(action: ActionItem, result: Any) {
|
||||
for (listener in listeners) {
|
||||
listener.onExec(action, result)
|
||||
private fun execInBackground(action: ActionItem, data: ActionData, it: MenuItem) {
|
||||
val start = System.currentTimeMillis()
|
||||
CompletableFuture.supplyAsync { action.execAction(data) }
|
||||
.whenComplete { result, error ->
|
||||
if (result == null || (result is Boolean && !result) || error != null) {
|
||||
log.error(
|
||||
"An error occurred when performing action '${it.title}'. Action failed in ${System.currentTimeMillis() - start}ms",
|
||||
error
|
||||
)
|
||||
} else {
|
||||
log.info("Action '${it.title}' completed in ${System.currentTimeMillis() - start}ms")
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread {
|
||||
action.postExec(data, result ?: false)
|
||||
notifyActionExec(action, result ?: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyActionExec(action: ActionItem, result: Any) {
|
||||
for (listener in listeners) {
|
||||
listener.onExec(action, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="@color/colorPrimary" />
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/coordinator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
@@ -19,7 +17,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/colorPrimary"
|
||||
app:popupOverlay="@style/ThemeOverlay.MaterialComponents.Light"/>
|
||||
app:popupOverlay="@style/ThemeOverlay.MaterialComponents.Light" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
@@ -28,10 +26,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
||||
|
||||
|
||||
<!-- Your content below appbar right here -->
|
||||
|
||||
|
||||
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,11 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_item1"
|
||||
android:name="Menu Item"/>
|
||||
android:name="Menu Item" />
|
||||
|
||||
</menu>
|
||||
@@ -38,12 +38,6 @@ public class AboutActivity extends StudioActivity {
|
||||
public static final String JDK_SOURCE = "https://github.com/itsaky/OpenJDK-Android";
|
||||
private ActivityAboutBinding binding;
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityAboutBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -64,6 +58,12 @@ public class AboutActivity extends StudioActivity {
|
||||
items.website.setOnClickListener(v -> getApp().openWebsite());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityAboutBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void setupTranslations() {
|
||||
final var translations = createTranslationsList();
|
||||
final var sb = new StringBuilder();
|
||||
|
||||
@@ -26,16 +26,9 @@ import com.itsaky.androidide.fragments.CrashReportFragment;
|
||||
|
||||
public class CrashHandlerActivity extends StudioActivity {
|
||||
|
||||
private ActivityCrashHandlerBinding binding;
|
||||
|
||||
public static final String REPORT_ACTION = "com.itsaky.androidide.REPORT_CRASH";
|
||||
public static final String TRACE_KEY = "crash_trace";
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityCrashHandlerBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
private ActivityCrashHandlerBinding binding;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -57,6 +50,12 @@ public class CrashHandlerActivity extends StudioActivity {
|
||||
.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityCrashHandlerBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
@@ -189,6 +189,12 @@ public class DesignerActivity extends StudioActivity
|
||||
.show();
|
||||
}
|
||||
|
||||
private void finishWithError() {
|
||||
final var intent = new Intent();
|
||||
setResult(-123, intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void finishWithResult(String result) {
|
||||
final var intent = new Intent();
|
||||
intent.putExtra(KEY_GENERATED_CODE, result);
|
||||
@@ -196,12 +202,6 @@ public class DesignerActivity extends StudioActivity
|
||||
finish();
|
||||
}
|
||||
|
||||
private void finishWithError() {
|
||||
final var intent = new Intent();
|
||||
setResult(-123, intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragStarted(View view) {
|
||||
mBinding.getRoot().closeDrawer(GravityCompat.START);
|
||||
|
||||
@@ -429,12 +429,6 @@ public class EditorActivity extends StudioActivity
|
||||
return openFile(file, null);
|
||||
}
|
||||
|
||||
public void hideViewOptions() {
|
||||
if (mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
}
|
||||
|
||||
public CodeEditorView openFile(File file, com.itsaky.lsp.models.Range selection) {
|
||||
if (selection == null) {
|
||||
selection = com.itsaky.lsp.models.Range.NONE;
|
||||
@@ -536,6 +530,12 @@ public class EditorActivity extends StudioActivity
|
||||
return mFileOptionsFragment;
|
||||
}
|
||||
|
||||
public void hideViewOptions() {
|
||||
if (mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
}
|
||||
|
||||
public ActivityEditorBinding getBinding() {
|
||||
return mBinding;
|
||||
}
|
||||
@@ -790,17 +790,6 @@ public class EditorActivity extends StudioActivity
|
||||
return saveAll(notify, false);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private CompletableFuture<ArrayList<String>> getResourceDirPaths() {
|
||||
return getResourceDirs()
|
||||
.thenApply(
|
||||
files ->
|
||||
files.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(File::getAbsolutePath)
|
||||
.collect(Collectors.toCollection(ArrayList::new)));
|
||||
}
|
||||
|
||||
public boolean saveAll(boolean notify, boolean canProcessResources) {
|
||||
SaveResult result = saveAllResult();
|
||||
|
||||
@@ -819,10 +808,6 @@ public class EditorActivity extends StudioActivity
|
||||
return result.gradleSaved;
|
||||
}
|
||||
|
||||
private CompletableFuture<List<File>> getResourceDirs() {
|
||||
return ProjectManager.INSTANCE.getApplicationResDirectories();
|
||||
}
|
||||
|
||||
public SaveResult saveAllResult() {
|
||||
SaveResult result = new SaveResult();
|
||||
for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
|
||||
@@ -832,15 +817,6 @@ public class EditorActivity extends StudioActivity
|
||||
return result;
|
||||
}
|
||||
|
||||
public void notifySyncNeeded() {
|
||||
if (mBuildService != null && !mBuildService.isBuildInProgress()) {
|
||||
getSyncBanner()
|
||||
.setNegative(android.R.string.cancel, null)
|
||||
.setPositive(android.R.string.ok, v -> initializeProject())
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
public void saveResult(int index, SaveResult result) {
|
||||
if (index >= 0 && index < mViewModel.getOpenedFileCount()) {
|
||||
var frag = getEditorAtIndex(index);
|
||||
@@ -879,6 +855,15 @@ public class EditorActivity extends StudioActivity
|
||||
ThreadUtils.runOnUiThread(() -> mViewModel.setFilesModified(finalModified));
|
||||
}
|
||||
|
||||
public void notifySyncNeeded() {
|
||||
if (mBuildService != null && !mBuildService.isBuildInProgress()) {
|
||||
getSyncBanner()
|
||||
.setNegative(android.R.string.cancel, null)
|
||||
.setPositive(android.R.string.ok, v -> initializeProject())
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
public MaterialBanner getSyncBanner() {
|
||||
return mBinding
|
||||
.syncBanner
|
||||
@@ -925,6 +910,18 @@ public class EditorActivity extends StudioActivity
|
||||
setStatus(text, Gravity.CENTER);
|
||||
}
|
||||
|
||||
public void setStatus(final CharSequence text, @GravityInt int gravity) {
|
||||
try {
|
||||
runOnUiThread(
|
||||
() -> {
|
||||
mBinding.bottomSheet.statusText.setGravity(gravity);
|
||||
mBinding.bottomSheet.statusText.setText(text);
|
||||
});
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Failed to update status text", th);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onProjectInitialized() {
|
||||
ProjectManager.INSTANCE.notifyProjectUpdate();
|
||||
ThreadUtils.runOnUiThread(
|
||||
@@ -941,16 +938,47 @@ public class EditorActivity extends StudioActivity
|
||||
});
|
||||
}
|
||||
|
||||
public void setStatus(final CharSequence text, @GravityInt int gravity) {
|
||||
private void initialSetup() {
|
||||
getApp()
|
||||
.getPrefManager()
|
||||
.setOpenedProject(Objects.requireNonNull(ProjectManager.INSTANCE.getProjectDirPath()));
|
||||
|
||||
try {
|
||||
runOnUiThread(
|
||||
() -> {
|
||||
mBinding.bottomSheet.statusText.setGravity(gravity);
|
||||
mBinding.bottomSheet.statusText.setText(text);
|
||||
});
|
||||
//noinspection ConstantConditions
|
||||
final var rootProject = ProjectManager.INSTANCE.getRootProject();
|
||||
|
||||
var projectName = rootProject.getName().get();
|
||||
if (projectName.isEmpty()) {
|
||||
projectName = new File(ProjectManager.INSTANCE.getProjectDirPath()).getName();
|
||||
getSupportActionBar().setSubtitle(projectName);
|
||||
} else {
|
||||
getSupportActionBar().setSubtitle(projectName);
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Failed to update status text", th);
|
||||
// ignored
|
||||
}
|
||||
|
||||
getResourceDirs()
|
||||
.thenAccept(
|
||||
dirs -> {
|
||||
dirs.removeIf(Objects::isNull);
|
||||
ValuesTableFactory.setupWithResDirectories(dirs.toArray(new File[0]));
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<List<File>> getResourceDirs() {
|
||||
return ProjectManager.INSTANCE.getApplicationResDirectories();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private CompletableFuture<ArrayList<String>> getResourceDirPaths() {
|
||||
return getResourceDirs()
|
||||
.thenApply(
|
||||
files ->
|
||||
files.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(File::getAbsolutePath)
|
||||
.collect(Collectors.toCollection(ArrayList::new)));
|
||||
}
|
||||
|
||||
public void assembleDebug(boolean installApk) {
|
||||
@@ -977,6 +1005,14 @@ public class EditorActivity extends StudioActivity
|
||||
}));
|
||||
}
|
||||
|
||||
public void appendBuildOut(final String str) {
|
||||
final var frag = bottomSheetTabAdapter.getBuildOutputFragment();
|
||||
|
||||
if (frag != null) {
|
||||
frag.appendOutput(str);
|
||||
}
|
||||
}
|
||||
|
||||
public Consumer<TaskExecutionResult> installableTaskResultConsumer(@NonNull String variantName) {
|
||||
return result -> {
|
||||
if (result != null && result.isSuccessful()) {
|
||||
@@ -1026,14 +1062,6 @@ public class EditorActivity extends StudioActivity
|
||||
};
|
||||
}
|
||||
|
||||
public void appendBuildOut(final String str) {
|
||||
final var frag = bottomSheetTabAdapter.getBuildOutputFragment();
|
||||
|
||||
if (frag != null) {
|
||||
frag.appendOutput(str);
|
||||
}
|
||||
}
|
||||
|
||||
public void install(@NonNull File apk) {
|
||||
runOnUiThread(
|
||||
() -> {
|
||||
@@ -1079,14 +1107,14 @@ public class EditorActivity extends StudioActivity
|
||||
execTasks(null, "lintRelease");
|
||||
}
|
||||
|
||||
public void cleanAndRebuild() {
|
||||
execTasks(null, "clean", "build");
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
////////////// PRIVATE APIS /////////////////////
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
public void cleanAndRebuild() {
|
||||
execTasks(null, "clean", "build");
|
||||
}
|
||||
|
||||
public AlertDialog getFindInProjectDialog() {
|
||||
return mFindInProjectDialog == null ? createFindInProjectDialog() : mFindInProjectDialog;
|
||||
}
|
||||
@@ -1232,18 +1260,6 @@ public class EditorActivity extends StudioActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchOnResumeToEditors() {
|
||||
CompletableFuture.runAsync(
|
||||
() -> {
|
||||
for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
|
||||
final var editor = getEditorAtIndex(i);
|
||||
if (editor != null) {
|
||||
editor.onResume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showCompilerModuleInstallError(Throwable error) {
|
||||
final var stacktrace = ThrowableUtils.getFullStackTrace(error);
|
||||
final var builder = DialogUtils.newMaterialDialogBuilder(this);
|
||||
@@ -1260,6 +1276,18 @@ public class EditorActivity extends StudioActivity
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void dispatchOnResumeToEditors() {
|
||||
CompletableFuture.runAsync(
|
||||
() -> {
|
||||
for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) {
|
||||
final var editor = getEditorAtIndex(i);
|
||||
if (editor != null) {
|
||||
editor.onResume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void dispatchOnPauseToEditors() {
|
||||
CompletableFuture.runAsync(
|
||||
() -> {
|
||||
@@ -1544,34 +1572,6 @@ public class EditorActivity extends StudioActivity
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void initialSetup() {
|
||||
getApp()
|
||||
.getPrefManager()
|
||||
.setOpenedProject(Objects.requireNonNull(ProjectManager.INSTANCE.getProjectDirPath()));
|
||||
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
final var rootProject = ProjectManager.INSTANCE.getRootProject();
|
||||
|
||||
var projectName = rootProject.getName().get();
|
||||
if (projectName.isEmpty()) {
|
||||
projectName = new File(ProjectManager.INSTANCE.getProjectDirPath()).getName();
|
||||
getSupportActionBar().setSubtitle(projectName);
|
||||
} else {
|
||||
getSupportActionBar().setSubtitle(projectName);
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
// ignored
|
||||
}
|
||||
|
||||
getResourceDirs()
|
||||
.thenAccept(
|
||||
dirs -> {
|
||||
dirs.removeIf(Objects::isNull);
|
||||
ValuesTableFactory.setupWithResDirectories(dirs.toArray(new File[0]));
|
||||
});
|
||||
}
|
||||
|
||||
private void setupDiagnosticInfo() {
|
||||
GradientDrawable gd = new GradientDrawable();
|
||||
gd.setShape(GradientDrawable.RECTANGLE);
|
||||
|
||||
@@ -27,7 +27,6 @@ import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import com.itsaky.androidide.app.StudioActivity;
|
||||
import com.itsaky.androidide.databinding.ActivityMainBinding;
|
||||
import com.itsaky.androidide.fragments.MainFragment;
|
||||
@@ -42,19 +41,12 @@ import java.io.File;
|
||||
public class MainActivity extends StudioActivity {
|
||||
private ActivityMainBinding binding;
|
||||
|
||||
public void openProject(@NonNull File root) {
|
||||
ProjectManager.INSTANCE.setProjectPath(root.getAbsolutePath());
|
||||
startActivity(new Intent(this, EditorActivity.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
openLastProject();
|
||||
}
|
||||
|
||||
private void showMoveRequired() {}
|
||||
|
||||
private void openLastProject() {
|
||||
binding
|
||||
.getRoot()
|
||||
@@ -90,12 +82,6 @@ public class MainActivity extends StudioActivity {
|
||||
.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void askProjectOpenPermission(File root) {
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
|
||||
builder.setTitle(R.string.title_confirm_open_project);
|
||||
@@ -106,6 +92,11 @@ public class MainActivity extends StudioActivity {
|
||||
builder.show();
|
||||
}
|
||||
|
||||
public void openProject(@NonNull File root) {
|
||||
ProjectManager.INSTANCE.setProjectPath(root.getAbsolutePath());
|
||||
startActivity(new Intent(this, EditorActivity.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStorageGranted() {}
|
||||
|
||||
@@ -115,9 +106,17 @@ public class MainActivity extends StudioActivity {
|
||||
finishAffinity();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
private void showMoveRequired() {}
|
||||
}
|
||||
|
||||
@@ -34,12 +34,6 @@ public class PreferencesActivity extends StudioActivity {
|
||||
private ActivityPreferencesBinding binding;
|
||||
private IDEPreferences mPref;
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityPreferencesBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -53,6 +47,12 @@ public class PreferencesActivity extends StudioActivity {
|
||||
loadFragment(getPrefsFragment());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityPreferencesBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private IDEPreferences getPrefsFragment() {
|
||||
return mPref == null ? mPref = new IDEPreferences() : mPref;
|
||||
}
|
||||
|
||||
@@ -49,14 +49,14 @@ public class SplashActivity extends StudioActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStorageAlreadyGranted() {
|
||||
super.onStorageAlreadyGranted();
|
||||
protected void onStorageGranted() {
|
||||
super.onStorageGranted();
|
||||
proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStorageGranted() {
|
||||
super.onStorageGranted();
|
||||
protected void onStorageAlreadyGranted() {
|
||||
super.onStorageAlreadyGranted();
|
||||
proceed();
|
||||
}
|
||||
|
||||
|
||||
@@ -70,21 +70,196 @@ public class TerminalActivity extends StudioActivity
|
||||
private static final ILogger LOG = ILogger.newInstance("TerminalActivity");
|
||||
private static final byte[] SOURCES_LIST_CONTENT =
|
||||
"deb https://androidide.com/packages/apt/termux-main/ stable main".getBytes();
|
||||
private static final String KEY_FONT_SIZE = "terminal_fontSize";
|
||||
private ActivityTerminalBinding binding;
|
||||
private TerminalView terminal;
|
||||
private TerminalSession session;
|
||||
private boolean isVisible = false;
|
||||
private KeyListener listener;
|
||||
|
||||
private int MIN_FONT_SIZE;
|
||||
private int MAX_FONT_SIZE;
|
||||
private int DEFAULT_FONT_SIZE;
|
||||
private static final String KEY_FONT_SIZE = "terminal_fontSize";
|
||||
|
||||
public boolean isVisible() {
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityTerminalBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
public boolean onKeyUp(int keyCode, KeyEvent e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(TerminalSession changedSession) {
|
||||
terminal.onScreenUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTitleChanged(TerminalSession changedSession) {}
|
||||
|
||||
@Override
|
||||
public void onSessionFinished(TerminalSession finishedSession) {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCopyTextToClipboard(TerminalSession session, String text) {
|
||||
ClipboardUtils.copyText("AndroidIDE Terminal", text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPasteTextFromClipboard(TerminalSession session) {
|
||||
String clip = ClipboardUtils.getText().toString();
|
||||
if (clip.trim().length() > 0 && terminal != null && terminal.mEmulator != null) {
|
||||
terminal.mEmulator.paste(clip);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBell(TerminalSession session) {}
|
||||
|
||||
@Override
|
||||
public void onColorsChanged(TerminalSession session) {}
|
||||
|
||||
@Override
|
||||
public void onTerminalCursorStateChange(boolean state) {}
|
||||
|
||||
@Override
|
||||
public Integer getTerminalCursorStyle() {
|
||||
return TerminalEmulator.DEFAULT_TERMINAL_CURSOR_STYLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float onScale(float scale) {
|
||||
if (scale < 0.9f || scale > 1.1f) {
|
||||
boolean increase = scale > 1.f;
|
||||
changeFontSize(increase);
|
||||
return 1.0f;
|
||||
}
|
||||
return scale;
|
||||
}
|
||||
|
||||
private void changeFontSize(final boolean increase) {
|
||||
int fontSize = getFontSize();
|
||||
fontSize += (increase ? 1 : -1) * 2;
|
||||
fontSize = Math.max(MIN_FONT_SIZE, Math.min(fontSize, MAX_FONT_SIZE));
|
||||
setFontSize(fontSize, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSingleTapUp(MotionEvent e) {
|
||||
KeyboardUtils.showSoftInput(terminal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldBackButtonBeMappedToEscape() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnforceCharBasedInput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldUseCtrlSpaceWorkaround() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminalViewSelected() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyModeChanged(boolean copyMode) {}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session) {
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER && !session.isRunning()) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongPress(MotionEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readControlKey() {
|
||||
Boolean state = binding.virtualKeyTable.readSpecialButton(SpecialButton.CTRL, true);
|
||||
return state != null && state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readAltKey() {
|
||||
Boolean state = binding.virtualKeyTable.readSpecialButton(SpecialButton.ALT, true);
|
||||
return state != null && state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readShiftKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readFnKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEmulatorSet() {
|
||||
setTerminalCursorBlinkingState(true);
|
||||
|
||||
if (session != null) {
|
||||
binding
|
||||
.getRoot()
|
||||
.setBackgroundColor(
|
||||
session.getEmulator().mColors.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logError(String tag, String message) {
|
||||
LOG.error(tag + ":", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(String tag, String message) {
|
||||
LOG.warn(tag + ":", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(String tag, String message) {
|
||||
LOG.info(tag + ":", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logDebug(String tag, String message) {
|
||||
LOG.debug(tag + ":", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logVerbose(String tag, String message) {
|
||||
LOG.verbose(tag + ":", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logStackTraceWithMessage(String tag, String message, Exception e) {
|
||||
LOG.error(tag + ":", message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logStackTrace(String tag, Exception e) {
|
||||
LOG.error(tag + ":", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -140,6 +315,12 @@ public class TerminalActivity extends StudioActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityTerminalBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void showInstallationError(Throwable throwable) {
|
||||
|
||||
binding.virtualKeyTable.setVisibility(View.GONE);
|
||||
@@ -198,41 +379,6 @@ public class TerminalActivity extends StudioActivity
|
||||
return listener == null ? listener = new KeyListener(terminal) : listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
setTerminalCursorBlinkingState(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
isVisible = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
isVisible = false;
|
||||
setTerminalCursorBlinkingState(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
private void setTerminalCursorBlinkingState(boolean start) {
|
||||
if (terminal != null && terminal.mEmulator != null) {
|
||||
terminal.setTerminalCursorBlinkerState(start, true);
|
||||
}
|
||||
}
|
||||
|
||||
private TerminalSession createSession(final String workingDirectory) {
|
||||
final Map<String, String> environment = getEnvironment();
|
||||
final String[] env = new String[environment.size()];
|
||||
@@ -322,6 +468,60 @@ public class TerminalActivity extends StudioActivity
|
||||
return sizes;
|
||||
}
|
||||
|
||||
public void setFontSize(int value, boolean apply) {
|
||||
getApp().getPrefManager().putString(KEY_FONT_SIZE, String.valueOf(value));
|
||||
|
||||
if (apply) {
|
||||
terminal.setTextSize(getFontSize());
|
||||
}
|
||||
}
|
||||
|
||||
public int getFontSize() {
|
||||
int fontSize;
|
||||
try {
|
||||
fontSize =
|
||||
Integer.parseInt(
|
||||
getApp()
|
||||
.getPrefManager()
|
||||
.getString(KEY_FONT_SIZE, String.valueOf(DEFAULT_FONT_SIZE)));
|
||||
} catch (NumberFormatException | ClassCastException e) {
|
||||
fontSize = DEFAULT_FONT_SIZE;
|
||||
}
|
||||
|
||||
return Math.min(Math.max(fontSize, MIN_FONT_SIZE), MAX_FONT_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
setTerminalCursorBlinkingState(true);
|
||||
}
|
||||
|
||||
private void setTerminalCursorBlinkingState(boolean start) {
|
||||
if (terminal != null && terminal.mEmulator != null) {
|
||||
terminal.setTerminalCursorBlinkerState(start, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
isVisible = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
isVisible = false;
|
||||
setTerminalCursorBlinkingState(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
private static final class KeyListener implements VirtualKeysView.IVirtualKeysView {
|
||||
|
||||
private final TerminalView terminal;
|
||||
@@ -404,205 +604,4 @@ public class TerminalActivity extends StudioActivity
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session) {
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER && !session.isRunning()) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(TerminalSession changedSession) {
|
||||
terminal.onScreenUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTitleChanged(TerminalSession changedSession) {}
|
||||
|
||||
@Override
|
||||
public void onSessionFinished(TerminalSession finishedSession) {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCopyTextToClipboard(TerminalSession session, String text) {
|
||||
ClipboardUtils.copyText("AndroidIDE Terminal", text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPasteTextFromClipboard(TerminalSession session) {
|
||||
String clip = ClipboardUtils.getText().toString();
|
||||
if (clip.trim().length() > 0 && terminal != null && terminal.mEmulator != null) {
|
||||
terminal.mEmulator.paste(clip);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBell(TerminalSession session) {}
|
||||
|
||||
@Override
|
||||
public void onColorsChanged(TerminalSession session) {}
|
||||
|
||||
@Override
|
||||
public void onTerminalCursorStateChange(boolean state) {}
|
||||
|
||||
@Override
|
||||
public Integer getTerminalCursorStyle() {
|
||||
return TerminalEmulator.DEFAULT_TERMINAL_CURSOR_STYLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float onScale(float scale) {
|
||||
if (scale < 0.9f || scale > 1.1f) {
|
||||
boolean increase = scale > 1.f;
|
||||
changeFontSize(increase);
|
||||
return 1.0f;
|
||||
}
|
||||
return scale;
|
||||
}
|
||||
|
||||
private void changeFontSize(final boolean increase) {
|
||||
int fontSize = getFontSize();
|
||||
fontSize += (increase ? 1 : -1) * 2;
|
||||
fontSize = Math.max(MIN_FONT_SIZE, Math.min(fontSize, MAX_FONT_SIZE));
|
||||
setFontSize(fontSize, true);
|
||||
}
|
||||
|
||||
public int getFontSize() {
|
||||
int fontSize;
|
||||
try {
|
||||
fontSize =
|
||||
Integer.parseInt(
|
||||
getApp()
|
||||
.getPrefManager()
|
||||
.getString(KEY_FONT_SIZE, String.valueOf(DEFAULT_FONT_SIZE)));
|
||||
} catch (NumberFormatException | ClassCastException e) {
|
||||
fontSize = DEFAULT_FONT_SIZE;
|
||||
}
|
||||
|
||||
return Math.min(Math.max(fontSize, MIN_FONT_SIZE), MAX_FONT_SIZE);
|
||||
}
|
||||
|
||||
public void setFontSize(int value, boolean apply) {
|
||||
getApp().getPrefManager().putString(KEY_FONT_SIZE, String.valueOf(value));
|
||||
|
||||
if (apply) {
|
||||
terminal.setTextSize(getFontSize());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSingleTapUp(MotionEvent e) {
|
||||
KeyboardUtils.showSoftInput(terminal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldBackButtonBeMappedToEscape() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnforceCharBasedInput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldUseCtrlSpaceWorkaround() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminalViewSelected() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyModeChanged(boolean copyMode) {}
|
||||
|
||||
@Override
|
||||
public boolean onLongPress(MotionEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readControlKey() {
|
||||
Boolean state = binding.virtualKeyTable.readSpecialButton(SpecialButton.CTRL, true);
|
||||
return state != null && state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readAltKey() {
|
||||
Boolean state = binding.virtualKeyTable.readSpecialButton(SpecialButton.ALT, true);
|
||||
return state != null && state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readFnKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readShiftKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEmulatorSet() {
|
||||
setTerminalCursorBlinkingState(true);
|
||||
|
||||
if (session != null) {
|
||||
binding
|
||||
.getRoot()
|
||||
.setBackgroundColor(
|
||||
session.getEmulator().mColors.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logError(String tag, String message) {
|
||||
LOG.error(tag + ":", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(String tag, String message) {
|
||||
LOG.warn(tag + ":", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(String tag, String message) {
|
||||
LOG.info(tag + ":", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logDebug(String tag, String message) {
|
||||
LOG.debug(tag + ":", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logVerbose(String tag, String message) {
|
||||
LOG.verbose(tag + ":", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logStackTraceWithMessage(String tag, String message, Exception e) {
|
||||
LOG.error(tag + ":", message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logStackTrace(String tag, Exception e) {
|
||||
LOG.error(tag + ":", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,30 +26,30 @@ import com.itsaky.androidide.EditorActivity
|
||||
*/
|
||||
abstract class BaseBuildAction : EditorActivityAction() {
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
val context = getActivity(data)
|
||||
if (context == null) {
|
||||
visible = false
|
||||
return
|
||||
} else {
|
||||
visible = true
|
||||
}
|
||||
|
||||
if (isBuildInProgress(context)) {
|
||||
enabled = false
|
||||
return
|
||||
} else {
|
||||
enabled = true
|
||||
}
|
||||
override fun prepare(data: ActionData) {
|
||||
val context = getActivity(data)
|
||||
if (context == null) {
|
||||
visible = false
|
||||
return
|
||||
} else {
|
||||
visible = true
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
val context = getActivity(data) ?: return
|
||||
context.invalidateOptionsMenu()
|
||||
if (isBuildInProgress(context)) {
|
||||
enabled = false
|
||||
return
|
||||
} else {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
fun shouldPrepare() = visible && enabled
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
val context = getActivity(data) ?: return
|
||||
context.invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
private fun isBuildInProgress(activity: EditorActivity): Boolean =
|
||||
activity.buildService == null || activity.buildService.isBuildInProgress
|
||||
fun shouldPrepare() = visible && enabled
|
||||
|
||||
private fun isBuildInProgress(activity: EditorActivity): Boolean =
|
||||
activity.buildService == null || activity.buildService.isBuildInProgress
|
||||
}
|
||||
|
||||
@@ -25,38 +25,38 @@ import com.itsaky.androidide.views.editor.IDEEditor
|
||||
/** @author Akash Yadav */
|
||||
abstract class BaseEditorAction : ActionItem {
|
||||
|
||||
override var label: String = ""
|
||||
override var visible: Boolean = true
|
||||
override var enabled: Boolean = true
|
||||
override var icon: Drawable? = null
|
||||
override var requiresUIThread: Boolean =
|
||||
true // all editor actions must be executed on UI thread
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
|
||||
override var label: String = ""
|
||||
override var visible: Boolean = true
|
||||
override var enabled: Boolean = true
|
||||
override var icon: Drawable? = null
|
||||
override var requiresUIThread: Boolean = true // all editor actions must be executed on UI thread
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
getEditor(data)
|
||||
?: kotlin.run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
override fun prepare(data: ActionData) {
|
||||
getEditor(data)
|
||||
?: kotlin.run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
fun getEditor(data: ActionData): IDEEditor? {
|
||||
return data.get(IDEEditor::class.java)
|
||||
}
|
||||
fun getEditor(data: ActionData): IDEEditor? {
|
||||
return data.get(IDEEditor::class.java)
|
||||
}
|
||||
|
||||
fun getContext(data: ActionData): Context? {
|
||||
val editor = getEditor(data) ?: return null
|
||||
return editor.context
|
||||
}
|
||||
fun getContext(data: ActionData): Context? {
|
||||
val editor = getEditor(data) ?: return null
|
||||
return editor.context
|
||||
}
|
||||
|
||||
fun tintDrawable(context: Context, drawable: Drawable): Drawable {
|
||||
drawable.setTint(
|
||||
ContextCompat.getColor(context, com.itsaky.androidide.R.color.primaryIconColor))
|
||||
return drawable
|
||||
}
|
||||
fun tintDrawable(context: Context, drawable: Drawable): Drawable {
|
||||
drawable.setTint(
|
||||
ContextCompat.getColor(context, com.itsaky.androidide.R.color.primaryIconColor)
|
||||
)
|
||||
return drawable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,22 +24,22 @@ import com.itsaky.androidide.EditorActivity
|
||||
/** @author Akash Yadav */
|
||||
abstract class EditorActivityAction : ActionItem {
|
||||
|
||||
override var enabled: Boolean = true
|
||||
override var visible: Boolean = true
|
||||
override var icon: Drawable? = null
|
||||
override var label: String = ""
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TOOLBAR
|
||||
override var enabled: Boolean = true
|
||||
override var visible: Boolean = true
|
||||
override var icon: Drawable? = null
|
||||
override var label: String = ""
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TOOLBAR
|
||||
|
||||
override var requiresUIThread: Boolean = false
|
||||
override var requiresUIThread: Boolean = false
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
if (!hasRequiredData(data, Context::class.java)) {
|
||||
markInvisible()
|
||||
}
|
||||
override fun prepare(data: ActionData) {
|
||||
if (!hasRequiredData(data, Context::class.java)) {
|
||||
markInvisible()
|
||||
}
|
||||
}
|
||||
|
||||
fun getActivity(data: ActionData): EditorActivity? {
|
||||
val context = data.get(Context::class.java) ?: return null
|
||||
return context as EditorActivity
|
||||
}
|
||||
fun getActivity(data: ActionData): EditorActivity? {
|
||||
val context = data.get(Context::class.java) ?: return null
|
||||
return context as EditorActivity
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,5 @@ package com.itsaky.androidide.actions
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class EditorActivityActionMenu : EditorActivityAction(), ActionMenu {
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
}
|
||||
|
||||
@@ -23,24 +23,24 @@ import com.itsaky.androidide.views.editor.IDEEditor
|
||||
/** @author Akash Yadav */
|
||||
abstract class EditorRelatedAction : EditorActivityAction() {
|
||||
|
||||
override var requiresUIThread: Boolean = true
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
val editor =
|
||||
getEditor(data)
|
||||
?: run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
override fun prepare(data: ActionData) {
|
||||
val editor =
|
||||
getEditor(data)
|
||||
?: run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
|
||||
val file = editor.file
|
||||
val file = editor.file
|
||||
|
||||
visible = file != null
|
||||
enabled = visible
|
||||
}
|
||||
visible = file != null
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
fun getEditor(data: ActionData): IDEEditor? = data.get(IDEEditor::class.java)
|
||||
fun getEditor(data: ActionData): IDEEditor? = data.get(IDEEditor::class.java)
|
||||
|
||||
fun getEditorView(data: ActionData): CodeEditorView? = data[CodeEditorView::class.java]
|
||||
fun getEditorView(data: ActionData): CodeEditorView? = data[CodeEditorView::class.java]
|
||||
}
|
||||
|
||||
@@ -26,17 +26,17 @@ import com.itsaky.androidide.actions.BaseBuildAction
|
||||
/** @author Akash Yadav */
|
||||
class AssembleDebugAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build_debug)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_debug)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build_debug)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_debug)
|
||||
}
|
||||
|
||||
override val id: String = "editor_assembleDebug"
|
||||
override val id: String = "editor_assembleDebug"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.assembleDebug(true)
|
||||
return true
|
||||
}
|
||||
activity.assembleDebug(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,17 +26,17 @@ import com.itsaky.androidide.actions.BaseBuildAction
|
||||
/** @author Akash Yadav */
|
||||
class AssembleReleaseAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build_release)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_release)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build_release)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_release)
|
||||
}
|
||||
|
||||
override val id: String = "editor_assembleRelease"
|
||||
override val id: String = "editor_assembleRelease"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.assembleRelease()
|
||||
return true
|
||||
}
|
||||
activity.assembleRelease()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,17 +26,17 @@ import com.itsaky.androidide.actions.BaseBuildAction
|
||||
/** @author Akash Yadav */
|
||||
class BuildAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
|
||||
}
|
||||
|
||||
override val id: String = "editor_build"
|
||||
override val id: String = "editor_build"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.build()
|
||||
return true
|
||||
}
|
||||
activity.build()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,18 +27,18 @@ import com.itsaky.androidide.actions.BaseBuildAction
|
||||
/** @author Akash Yadav */
|
||||
class BuildActionMenu() : BaseBuildAction(), ActionMenu {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build_tasks)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build_tasks)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
|
||||
|
||||
addAction(BuildAction(context))
|
||||
addAction(AssembleDebugAction(context))
|
||||
addAction(AssembleReleaseAction(context))
|
||||
addAction(CreateAABAction(context))
|
||||
addAction(CleanAction(context))
|
||||
addAction(RebuildAction(context))
|
||||
}
|
||||
addAction(BuildAction(context))
|
||||
addAction(AssembleDebugAction(context))
|
||||
addAction(AssembleReleaseAction(context))
|
||||
addAction(CreateAABAction(context))
|
||||
addAction(CleanAction(context))
|
||||
addAction(RebuildAction(context))
|
||||
}
|
||||
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
override val id: String = "editor_buildTasks"
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
override val id: String = "editor_buildTasks"
|
||||
}
|
||||
|
||||
@@ -27,40 +27,41 @@ import com.itsaky.androidide.utils.ILogger
|
||||
/** @author Akash Yadav */
|
||||
class CancelBuildAction() : EditorActivityAction() {
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.title_cancel_build)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_stop_daemons)
|
||||
}
|
||||
|
||||
override val id: String = "editor_stopGradleDaemons"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
log.info("Sending build cancellation request...")
|
||||
context.buildService.cancelCurrentBuild().whenComplete { result, error ->
|
||||
if (error != null) {
|
||||
log.error("Failed to send build cancellation request", error)
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
if (!result.wasEnqueued) {
|
||||
log.warn(
|
||||
"Unable to enqueue cancellation request",
|
||||
result.failureReason,
|
||||
result.failureReason!!.message)
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
log.info("Build cancellation request was successfully enqueued...")
|
||||
}
|
||||
return true
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.title_cancel_build)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_stop_daemons)
|
||||
}
|
||||
|
||||
override val id: String = "editor_stopGradleDaemons"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
log.info("Sending build cancellation request...")
|
||||
context.buildService.cancelCurrentBuild().whenComplete { result, error ->
|
||||
if (error != null) {
|
||||
log.error("Failed to send build cancellation request", error)
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
if (!result.wasEnqueued) {
|
||||
log.warn(
|
||||
"Unable to enqueue cancellation request",
|
||||
result.failureReason,
|
||||
result.failureReason!!.message
|
||||
)
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
log.info("Build cancellation request was successfully enqueued...")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,16 +26,16 @@ import com.itsaky.androidide.actions.BaseBuildAction
|
||||
/** @author Akash Yadav */
|
||||
class CleanAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.clean_project)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_clean)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.clean_project)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_clean)
|
||||
}
|
||||
|
||||
override val id: String = "editor_clean"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
override val id: String = "editor_clean"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.clean()
|
||||
return true
|
||||
}
|
||||
context.clean()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,16 +26,16 @@ import com.itsaky.androidide.actions.BaseBuildAction
|
||||
/** @author Akash Yadav */
|
||||
class CreateAABAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.create_aab)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_bundle)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.create_aab)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_bundle)
|
||||
}
|
||||
|
||||
override val id: String = "editor_createAAB"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
override val id: String = "editor_createAAB"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.bundle()
|
||||
return true
|
||||
}
|
||||
context.bundle()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.R.string
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
|
||||
/**
|
||||
* Triggers a project sync request.
|
||||
@@ -32,15 +31,15 @@ import com.itsaky.androidide.actions.EditorActivityAction
|
||||
*/
|
||||
class ProjectSyncAction() : BaseBuildAction() {
|
||||
|
||||
override val id: String = "action_editor_syncProject"
|
||||
override var requiresUIThread = true
|
||||
override val id: String = "action_editor_syncProject"
|
||||
override var requiresUIThread = true
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(string.title_sync_project)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_sync)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(string.title_sync_project)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_sync)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
return getActivity(data)!!.initializeProject()
|
||||
}
|
||||
override fun execAction(data: ActionData): Any {
|
||||
return getActivity(data)!!.initializeProject()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,16 +30,16 @@ import com.itsaky.androidide.actions.BaseBuildAction
|
||||
*/
|
||||
class QuickRunAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.quick_run_debug)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_outline)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.quick_run_debug)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_outline)
|
||||
}
|
||||
|
||||
override val id: String = "editor_quick_run"
|
||||
override val id: String = "editor_quick_run"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
context.assembleDebug(true)
|
||||
return true
|
||||
}
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
context.assembleDebug(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,16 +26,16 @@ import com.itsaky.androidide.actions.BaseBuildAction
|
||||
/** @author Akash Yadav */
|
||||
class RebuildAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.clean_amp_build)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_rebuild)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.clean_amp_build)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_rebuild)
|
||||
}
|
||||
|
||||
override val id: String = "editor_cleanAndBuild"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
override val id: String = "editor_cleanAndBuild"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.cleanAndRebuild()
|
||||
return true
|
||||
}
|
||||
context.cleanAndRebuild()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,15 +28,15 @@ import com.itsaky.androidide.actions.lint.LintTaskMenu
|
||||
/** @author Akash Yadav */
|
||||
class RunActionMenu() : BaseBuildAction(), ActionMenu {
|
||||
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.run)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.run)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
|
||||
|
||||
addAction(BuildActionMenu(context))
|
||||
addAction(LintTaskMenu(context))
|
||||
}
|
||||
addAction(BuildActionMenu(context))
|
||||
addAction(LintTaskMenu(context))
|
||||
}
|
||||
|
||||
override val id: String = "editor_runMenu"
|
||||
override val id: String = "editor_runMenu"
|
||||
}
|
||||
|
||||
@@ -25,18 +25,18 @@ import com.itsaky.androidide.actions.BaseEditorAction
|
||||
/** @author Akash Yadav */
|
||||
class CopyAction() : BaseEditorAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.copy)
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.copy)
|
||||
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeCopyDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
}
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeCopyDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
}
|
||||
|
||||
override val id: String = "ideEditor_copy"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.copyText()
|
||||
return true
|
||||
}
|
||||
override val id: String = "ideEditor_copy"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.copyText()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,36 +18,35 @@
|
||||
package com.itsaky.androidide.actions.editor
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseEditorAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class CutAction() : BaseEditorAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(android.R.string.cut)
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(android.R.string.cut)
|
||||
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeCutDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeCutDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
visible = getEditor(data)?.isEditable ?: false
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
visible = getEditor(data)?.isEditable ?: false
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
override val id: String = "ideEditor_cut"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.cutText()
|
||||
return true
|
||||
}
|
||||
override val id: String = "ideEditor_cut"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.cutText()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,15 +30,15 @@ import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ExpandSelectionAction() : EditorRelatedAction() {
|
||||
override val id: String = "editor_expandSelection"
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
|
||||
override val id: String = "editor_expandSelection"
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.action_expand_selection)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_expand_selection)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.action_expand_selection)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_expand_selection)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
return getEditor(data)!!.expandSelection()
|
||||
}
|
||||
override fun execAction(data: ActionData): Any {
|
||||
return getEditor(data)!!.expandSelection()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,37 +18,36 @@
|
||||
package com.itsaky.androidide.actions.editor
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseEditorAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class PasteAction() : BaseEditorAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(android.R.string.paste)
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(android.R.string.paste)
|
||||
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModePasteDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModePasteDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
}
|
||||
|
||||
override val id: String = "ideEditor_paste"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
override val id: String = "ideEditor_paste"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
visible = getEditor(data)?.isEditable ?: false
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.pasteText()
|
||||
return true
|
||||
}
|
||||
visible = getEditor(data)?.isEditable ?: false
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.pasteText()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,26 +18,25 @@
|
||||
package com.itsaky.androidide.actions.editor
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseEditorAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class SelectAllAction() : BaseEditorAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(android.R.string.selectAll)
|
||||
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeSelectAllDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
}
|
||||
|
||||
override val id: String = "ideEditor_selectAll"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.selectAll()
|
||||
return true
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(android.R.string.selectAll)
|
||||
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeSelectAllDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
}
|
||||
|
||||
override val id: String = "ideEditor_selectAll"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.selectAll()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,19 +27,19 @@ import com.itsaky.androidide.actions.ActionData
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class GoToNextAction() : SearchActionModeAction() {
|
||||
override val id: String = "editor.find.next"
|
||||
override val id: String = "editor.find.next"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.next)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_next)
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.next)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_next)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)!!
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)!!
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return editor.searcher.gotoNext()
|
||||
}
|
||||
return editor.searcher.gotoNext()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,19 +28,19 @@ import com.itsaky.androidide.actions.ActionData
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class GoToPreviousAction() : SearchActionModeAction() {
|
||||
override val id: String = "editor.find.previous"
|
||||
override val id: String = "editor.find.previous"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(string.previous)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_previous)
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(string.previous)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_previous)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)!!
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)!!
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return editor.searcher.gotoPrevious()
|
||||
}
|
||||
return editor.searcher.gotoPrevious()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,55 +33,55 @@ import com.itsaky.androidide.utils.ILogger
|
||||
*/
|
||||
class ReplaceAction() : SearchActionModeAction() {
|
||||
|
||||
override val id: String = "editor.search.replace"
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
override val id: String = "editor.search.replace"
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.replace)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_replace)
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.replace)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_replace)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val context = data[Context::class.java]!!
|
||||
val editor = getEditor(data)!!
|
||||
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val context = data[Context::class.java]!!
|
||||
val editor = getEditor(data)!!
|
||||
val binding = LayoutEditorFindReplaceBinding.inflate(LayoutInflater.from(context))
|
||||
val builder = DialogUtils.newMaterialDialogBuilder(context)
|
||||
builder.setTitle(R.string.replace)
|
||||
builder.setView(binding.root)
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
builder.setPositiveButton(R.string.replace) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
val input = binding.replacementInput.editText
|
||||
if (input == null) {
|
||||
log.error("Unable to perform replace action. Input field is null")
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
val binding = LayoutEditorFindReplaceBinding.inflate(LayoutInflater.from(context))
|
||||
val builder = DialogUtils.newMaterialDialogBuilder(context)
|
||||
builder.setTitle(R.string.replace)
|
||||
builder.setView(binding.root)
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
builder.setPositiveButton(R.string.replace) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
val input = binding.replacementInput.editText
|
||||
if (input == null) {
|
||||
log.error("Unable to perform replace action. Input field is null")
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
editor.searcher.replaceThis(input.text.toString())
|
||||
}
|
||||
builder.setNeutralButton(R.string.replaceAll) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
val input = binding.replacementInput.editText
|
||||
if (input == null) {
|
||||
log.error("Unable to perform replace action. Input field is null")
|
||||
return@setNeutralButton
|
||||
}
|
||||
|
||||
editor.searcher.replaceAllAsync(input.text.toString()).whenComplete { _, error ->
|
||||
if (error != null) {
|
||||
log.error("Unable to replace all matched text", error)
|
||||
} else {
|
||||
editor.post { data[ActionMode::class.java]?.finish() }
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.show()
|
||||
|
||||
return true
|
||||
editor.searcher.replaceThis(input.text.toString())
|
||||
}
|
||||
builder.setNeutralButton(R.string.replaceAll) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
val input = binding.replacementInput.editText
|
||||
if (input == null) {
|
||||
log.error("Unable to perform replace action. Input field is null")
|
||||
return@setNeutralButton
|
||||
}
|
||||
|
||||
editor.searcher.replaceAllAsync(input.text.toString()).whenComplete { _, error ->
|
||||
if (error != null) {
|
||||
log.error("Unable to replace all matched text", error)
|
||||
} else {
|
||||
editor.post { data[ActionMode::class.java]?.finish() }
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.show()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,5 +23,5 @@ import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class SearchActionModeAction : EditorRelatedAction() {
|
||||
override var location: Location = ActionItem.Location.EDITOR_SEARCH_ACTION_MODE
|
||||
override var location: Location = ActionItem.Location.EDITOR_SEARCH_ACTION_MODE
|
||||
}
|
||||
|
||||
@@ -25,22 +25,22 @@ import com.itsaky.androidide.actions.EditorActivityAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class DaemonStatusAction() : EditorActivityAction() {
|
||||
override val id: String = "editor_daemonStatus"
|
||||
override var requiresUIThread: Boolean = true
|
||||
override val id: String = "editor_daemonStatus"
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.gradle_daemon_status)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_info)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.gradle_daemon_status)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_info)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
context.showDaemonStatus()
|
||||
return true
|
||||
}
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
context.showDaemonStatus()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,26 +27,26 @@ import com.itsaky.androidide.actions.EditorActivityAction
|
||||
/** @author Akash Yadav */
|
||||
class FileTreeAction() : EditorActivityAction() {
|
||||
|
||||
override val id: String = "editor_fileTree"
|
||||
override val id: String = "editor_fileTree"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.msg_file_tree)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_folder)
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.msg_file_tree)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_folder)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
if (!context.binding.root.isDrawerOpen(GravityCompat.END)) {
|
||||
context.binding.root.openDrawer(GravityCompat.END)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
if (!context.binding.root.isDrawerOpen(GravityCompat.END)) {
|
||||
context.binding.root.openDrawer(GravityCompat.END)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,19 +28,19 @@ import com.itsaky.androidide.actions.EditorActivityAction
|
||||
/** @author Akash Yadav */
|
||||
class FindActionMenu() : EditorActivityAction(), ActionMenu {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.menu_find)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search)
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.menu_find)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search)
|
||||
|
||||
addAction(FindInFileAction(context))
|
||||
addAction(FindInProjectAction(context))
|
||||
}
|
||||
addAction(FindInFileAction(context))
|
||||
addAction(FindInProjectAction(context))
|
||||
}
|
||||
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
override val id: String = "editor_findActions"
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
override val id: String = "editor_findActions"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = children.size > 0
|
||||
}
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = children.size > 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,17 +26,17 @@ import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
/** @author Akash Yadav */
|
||||
class FindInFileAction() : EditorRelatedAction() {
|
||||
|
||||
override val id: String = "editor_findInFile"
|
||||
override var requiresUIThread: Boolean = true
|
||||
override val id: String = "editor_findInFile"
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.menu_find_file)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_file)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.menu_find_file)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_file)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditorView(data) ?: return false
|
||||
editor.beginSearch()
|
||||
return true
|
||||
}
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditorView(data) ?: return false
|
||||
editor.beginSearch()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,36 +26,36 @@ import com.itsaky.androidide.actions.EditorActivityAction
|
||||
/** @author Akash Yadav */
|
||||
class FindInProjectAction() : EditorActivityAction() {
|
||||
|
||||
override var requiresUIThread: Boolean = true
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.menu_find_project)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_project)
|
||||
}
|
||||
|
||||
override val id: String = "editor_findInProject"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
getActivity(data)
|
||||
?: run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
val dialog = context.findInProjectDialog
|
||||
|
||||
return if (dialog != null) {
|
||||
dialog.show()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.menu_find_project)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_project)
|
||||
}
|
||||
|
||||
override val id: String = "editor_findInProject"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
getActivity(data)
|
||||
?: run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
val dialog = context.findInProjectDialog
|
||||
|
||||
return if (dialog != null) {
|
||||
dialog.show()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,36 +27,35 @@ import com.itsaky.androidide.handlers.FileOptionsHandler
|
||||
/** @author Akash Yadav */
|
||||
class PreviewLayoutAction() : EditorRelatedAction() {
|
||||
|
||||
override val id: String = "editor_previewLayout"
|
||||
override val id: String = "editor_previewLayout"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.title_preview_layout)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_preview_layout)
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.title_preview_layout)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_preview_layout)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
val editor = getEditor(data)!!
|
||||
val file = editor.file!!
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
val isXml = file.name.endsWith(".xml")
|
||||
|
||||
val editor = getEditor(data)!!
|
||||
val file = editor.file!!
|
||||
visible =
|
||||
isXml &&
|
||||
file.parentFile != null &&
|
||||
Regex(FileOptionsHandler.LAYOUT_RES_PATH_REGEX).matches(file.parentFile!!.absolutePath)
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
val isXml = file.name.endsWith(".xml")
|
||||
|
||||
visible =
|
||||
isXml &&
|
||||
file.parentFile != null &&
|
||||
Regex(FileOptionsHandler.LAYOUT_RES_PATH_REGEX)
|
||||
.matches(file.parentFile!!.absolutePath)
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val activity = getActivity(data)!!
|
||||
activity.previewLayout()
|
||||
return true
|
||||
}
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val activity = getActivity(data)!!
|
||||
activity.previewLayout()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,17 +30,17 @@ import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class FormatCodeAction() : EditorRelatedAction() {
|
||||
override val id: String = "editor_file_formatCode"
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
|
||||
override val id: String = "editor_file_formatCode"
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.title_format_code)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_format_code)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.title_format_code)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_format_code)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)!!
|
||||
editor.formatCodeAsync()
|
||||
return true
|
||||
}
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)!!
|
||||
editor.formatCodeAsync()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,57 +30,57 @@ import com.itsaky.toaster.Toaster
|
||||
/** @author Akash Yadav */
|
||||
class SaveFileAction() : EditorRelatedAction() {
|
||||
|
||||
private val log = ILogger.newInstance("SaveFileAction")
|
||||
private val log = ILogger.newInstance("SaveFileAction")
|
||||
|
||||
override val id: String = "editor_saveFile"
|
||||
override val id: String = "editor_saveFile"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.save)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_save)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.save)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_save)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
override fun prepare(data: ActionData) {
|
||||
|
||||
val context =
|
||||
getActivity(data)
|
||||
?: run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
|
||||
visible = context.viewModel.openedFiles.isNotEmpty()
|
||||
enabled = context.areFilesModified()
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
return try {
|
||||
// Cannot use context.saveAll() because this.execAction is called on non-UI thread
|
||||
// and saveAll call will result in UI actions
|
||||
val result = context.saveAllResult()
|
||||
|
||||
if (result.xmlSaved) {
|
||||
ProjectManager.generateSources(context.buildService)
|
||||
}
|
||||
|
||||
if (result.gradleSaved) {
|
||||
context.notifySyncNeeded()
|
||||
}
|
||||
true
|
||||
} catch (error: Throwable) {
|
||||
log.error("Failed to save file", error)
|
||||
false
|
||||
val context =
|
||||
getActivity(data)
|
||||
?: run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result is Boolean && result) {
|
||||
StudioApp.getInstance().toast(R.string.all_saved, Toaster.Type.SUCCESS)
|
||||
} else {
|
||||
log.error("Failed to save file")
|
||||
TODO("Create message in strings.xml")
|
||||
}
|
||||
visible = context.viewModel.openedFiles.isNotEmpty()
|
||||
enabled = context.areFilesModified()
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
return try {
|
||||
// Cannot use context.saveAll() because this.execAction is called on non-UI thread
|
||||
// and saveAll call will result in UI actions
|
||||
val result = context.saveAllResult()
|
||||
|
||||
if (result.xmlSaved) {
|
||||
ProjectManager.generateSources(context.buildService)
|
||||
}
|
||||
|
||||
if (result.gradleSaved) {
|
||||
context.notifySyncNeeded()
|
||||
}
|
||||
true
|
||||
} catch (error: Throwable) {
|
||||
log.error("Failed to save file", error)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result is Boolean && result) {
|
||||
StudioApp.getInstance().toast(R.string.all_saved, Toaster.Type.SUCCESS)
|
||||
} else {
|
||||
log.error("Failed to save file")
|
||||
TODO("Create message in strings.xml")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,16 +26,16 @@ import com.itsaky.androidide.actions.BaseBuildAction
|
||||
/** @author Akash Yadav */
|
||||
class LintAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_lint)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_lint)
|
||||
}
|
||||
|
||||
override val id: String = "editor_lint"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
override val id: String = "editor_lint"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.lint()
|
||||
return true
|
||||
}
|
||||
context.lint()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,16 +26,16 @@ import com.itsaky.androidide.actions.BaseBuildAction
|
||||
/** @author Akash Yadav */
|
||||
class LintDebugAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint_debug)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_debug)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint_debug)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_debug)
|
||||
}
|
||||
|
||||
override val id: String = "editor_lintDebug"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
override val id: String = "editor_lintDebug"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.lintDebug()
|
||||
return true
|
||||
}
|
||||
context.lintDebug()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,16 +26,16 @@ import com.itsaky.androidide.actions.BaseBuildAction
|
||||
/** @author Akash Yadav */
|
||||
class LintReleaseAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint_release)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_release)
|
||||
}
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint_release)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_release)
|
||||
}
|
||||
|
||||
override val id: String = "editor_lintRelease"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
override val id: String = "editor_lintRelease"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.lintRelease()
|
||||
return true
|
||||
}
|
||||
context.lintRelease()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,15 +27,15 @@ import com.itsaky.androidide.actions.BaseBuildAction
|
||||
/** @author Akash Yadav */
|
||||
class LintTaskMenu() : BaseBuildAction(), ActionMenu {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint_tasks)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_lint)
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint_tasks)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_lint)
|
||||
|
||||
addAction(LintAction(context))
|
||||
addAction(LintDebugAction(context))
|
||||
addAction(LintReleaseAction(context))
|
||||
}
|
||||
addAction(LintAction(context))
|
||||
addAction(LintDebugAction(context))
|
||||
addAction(LintReleaseAction(context))
|
||||
}
|
||||
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
override val id: String = "editor_lintTasks"
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
override val id: String = "editor_lintTasks"
|
||||
}
|
||||
|
||||
@@ -29,35 +29,35 @@ import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
/** @author Akash Yadav */
|
||||
class RedoAction() : EditorRelatedAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.redo)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_redo)
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.redo)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_redo)
|
||||
}
|
||||
|
||||
override val id: String = "editor_redo"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
override val id: String = "editor_redo"
|
||||
val editor = getEditor(data)!!
|
||||
enabled = editor.canRedo()
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data)!!
|
||||
editor.redo()
|
||||
return true
|
||||
}
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
val editor = getEditor(data)!!
|
||||
enabled = editor.canRedo()
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data)!!
|
||||
editor.redo()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getShowAsActionFlags(data: ActionData): Int {
|
||||
return if (KeyboardUtils.isSoftInputVisible(data.get(Context::class.java) as Activity)) {
|
||||
MenuItem.SHOW_AS_ACTION_IF_ROOM
|
||||
} else {
|
||||
MenuItem.SHOW_AS_ACTION_NEVER
|
||||
}
|
||||
override fun getShowAsActionFlags(data: ActionData): Int {
|
||||
return if (KeyboardUtils.isSoftInputVisible(data.get(Context::class.java) as Activity)) {
|
||||
MenuItem.SHOW_AS_ACTION_IF_ROOM
|
||||
} else {
|
||||
MenuItem.SHOW_AS_ACTION_NEVER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,31 +26,31 @@ import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
/** @author Akash Yadav */
|
||||
class UndoAction() : EditorRelatedAction() {
|
||||
|
||||
override val id: String = "editor_undo"
|
||||
override val id: String = "editor_undo"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.undo)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_undo)
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.undo)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_undo)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
val editor = getEditor(data)!!
|
||||
enabled = editor.canUndo()
|
||||
}
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
val editor = getEditor(data)!!
|
||||
enabled = editor.canUndo()
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)
|
||||
return if (editor != null) {
|
||||
editor.undo()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)
|
||||
return if (editor != null) {
|
||||
editor.undo()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,20 @@ public class AttrListAdapter extends IconTextAdapter<Attr> {
|
||||
this.attrs = attrs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return attrs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull VH holder, int position) {
|
||||
super.onBindViewHolder(holder, position);
|
||||
|
||||
if (clickConsumer != null) {
|
||||
holder.binding.getRoot().setOnClickListener(v -> clickConsumer.accept(getItemAt(position)));
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Attr getItemAt(int index) {
|
||||
@@ -62,18 +76,4 @@ public class AttrListAdapter extends IconTextAdapter<Attr> {
|
||||
public String getItemText(int index) {
|
||||
return getItemAt(index).name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return attrs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull VH holder, int position) {
|
||||
super.onBindViewHolder(holder, position);
|
||||
|
||||
if (clickConsumer != null) {
|
||||
holder.binding.getRoot().setOnClickListener(v -> clickConsumer.accept(getItemAt(position)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,29 @@ public class EditorBottomSheetTabAdapter extends FragmentStateAdapter {
|
||||
return getFragmentById(getItemId(index));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Fragment getFragmentById(long itemId) {
|
||||
final var fragments = getFragments();
|
||||
if (fragments != null) {
|
||||
return fragments.get(itemId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private LongSparseArray<Fragment> getFragments() {
|
||||
try {
|
||||
final var field = FragmentStateAdapter.class.getDeclaredField("mFragments");
|
||||
field.setAccessible(true);
|
||||
return (LongSparseArray<Fragment>) field.get(this);
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Unable to reflect fragment list from adapter.");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
@@ -98,21 +121,6 @@ public class EditorBottomSheetTabAdapter extends FragmentStateAdapter {
|
||||
return findFragmentByClass(SimpleOutputFragment.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AppLogFragment getLogFragment() {
|
||||
return findFragmentByClass(AppLogFragment.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DiagnosticsListFragment getDiagnosticsFragment() {
|
||||
return findFragmentByClass(DiagnosticsListFragment.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SearchResultFragment getSearchResultFragment() {
|
||||
return findFragmentByClass(SearchResultFragment.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <T extends Fragment> T findFragmentByClass(Class<T> clazz) {
|
||||
final var name = clazz.getName();
|
||||
@@ -126,26 +134,18 @@ public class EditorBottomSheetTabAdapter extends FragmentStateAdapter {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Fragment getFragmentById(long itemId) {
|
||||
final var fragments = getFragments();
|
||||
if (fragments != null) {
|
||||
return fragments.get(itemId);
|
||||
}
|
||||
|
||||
return null;
|
||||
public AppLogFragment getLogFragment() {
|
||||
return findFragmentByClass(AppLogFragment.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private LongSparseArray<Fragment> getFragments() {
|
||||
try {
|
||||
final var field = FragmentStateAdapter.class.getDeclaredField("mFragments");
|
||||
field.setAccessible(true);
|
||||
return (LongSparseArray<Fragment>) field.get(this);
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Unable to reflect fragment list from adapter.");
|
||||
}
|
||||
public DiagnosticsListFragment getDiagnosticsFragment() {
|
||||
return findFragmentByClass(DiagnosticsListFragment.class);
|
||||
}
|
||||
|
||||
return null;
|
||||
@Nullable
|
||||
public SearchResultFragment getSearchResultFragment() {
|
||||
return findFragmentByClass(SearchResultFragment.class);
|
||||
}
|
||||
|
||||
public <T extends Fragment> int findIndexOfFragmentByClass(@NonNull Class<T> tClass) {
|
||||
|
||||
@@ -98,14 +98,6 @@ public abstract class IconTextAdapter<E> extends RecyclerView.Adapter<IconTextAd
|
||||
@NonNull
|
||||
public abstract String getItemText(int index);
|
||||
|
||||
public interface OnBindListener<T> {
|
||||
default boolean onBind(T item, VH holder, int position) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default void postBind(T item, VH holder, int position) {}
|
||||
}
|
||||
|
||||
public static class VH extends RecyclerView.ViewHolder {
|
||||
|
||||
public final LayoutSimpleIconTextBinding binding;
|
||||
@@ -115,4 +107,12 @@ public abstract class IconTextAdapter<E> extends RecyclerView.Adapter<IconTextAd
|
||||
this.binding = binding;
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnBindListener<T> {
|
||||
default boolean onBind(T item, VH holder, int position) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default void postBind(T item, VH holder, int position) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,118 +41,122 @@ import com.itsaky.androidide.utils.TypefaceUtils
|
||||
*/
|
||||
class LogLinesAdapter : RecyclerView.Adapter<VH>() {
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
private val lines: MutableList<LogWrapper> = mutableListOf()
|
||||
var simpleFormatting = true
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
private val lines: MutableList<LogWrapper> = mutableListOf()
|
||||
var simpleFormatting = true
|
||||
|
||||
companion object {
|
||||
const val MAX_LINES = 10 * 1000
|
||||
companion object {
|
||||
const val MAX_LINES = 10 * 1000
|
||||
}
|
||||
|
||||
class VH(val text: TextView) : RecyclerView.ViewHolder(text)
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun clear() {
|
||||
this.lines.clear()
|
||||
this.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun add(line: LogLine) {
|
||||
val wrapped = wrap(line)
|
||||
this.lines.add(wrapped)
|
||||
notifyItemInserted(this.lines.lastIndex)
|
||||
|
||||
if (this.lines.size > MAX_LINES) {
|
||||
this.lines.removeAt(0)
|
||||
this.notifyItemRemoved(0)
|
||||
}
|
||||
}
|
||||
|
||||
class VH(val text: TextView) : RecyclerView.ViewHolder(text)
|
||||
fun getLine(index: Int) = this.lines[index].log
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun clear() {
|
||||
this.lines.clear()
|
||||
this.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun add(line: LogLine) {
|
||||
val wrapped = wrap(line)
|
||||
this.lines.add(wrapped)
|
||||
notifyItemInserted(this.lines.lastIndex)
|
||||
|
||||
if (this.lines.size > MAX_LINES) {
|
||||
this.lines.removeAt(0)
|
||||
this.notifyItemRemoved(0)
|
||||
fun allAsString() =
|
||||
StringBuilder()
|
||||
.apply {
|
||||
for (line in lines) {
|
||||
append(lineString(line))
|
||||
append("\n")
|
||||
}
|
||||
}
|
||||
.toString()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
||||
val dp1 = SizeUtils.dp2px(1f)
|
||||
val text = TextView(parent.context)
|
||||
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f)
|
||||
text.setPaddingRelative(dp1, dp1 / 2, dp1, dp1 / 2)
|
||||
text.setTextIsSelectable(true)
|
||||
text.maxLines = 1
|
||||
text.typeface = TypefaceUtils.jetbrainsMono()
|
||||
return VH(text)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: VH, position: Int) {
|
||||
val line = lines[position]
|
||||
if (!line.visible) {
|
||||
holder.itemView.visibility = View.GONE
|
||||
return
|
||||
} else {
|
||||
holder.itemView.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
fun getLine(index: Int) = this.lines[index].log
|
||||
val spanned = line.spannable
|
||||
holder.text.text = spanned
|
||||
}
|
||||
|
||||
fun allAsString() =
|
||||
StringBuilder()
|
||||
.apply {
|
||||
for (line in lines) {
|
||||
append(lineString(line))
|
||||
append("\n")
|
||||
}
|
||||
}
|
||||
.toString()
|
||||
override fun getItemCount() = this.lines.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
||||
val dp1 = SizeUtils.dp2px(1f)
|
||||
val text = TextView(parent.context)
|
||||
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f)
|
||||
text.setPaddingRelative(dp1, dp1 / 2, dp1, dp1 / 2)
|
||||
text.setTextIsSelectable(true)
|
||||
text.maxLines = 1
|
||||
text.typeface = TypefaceUtils.jetbrainsMono()
|
||||
return VH(text)
|
||||
private fun wrap(line: LogLine): LogWrapper {
|
||||
val spannable = SpannableString(lineString(line))
|
||||
val color =
|
||||
when (line.priority) {
|
||||
ERROR -> -0xbbcca
|
||||
WARNING -> -0x14c5
|
||||
INFO -> -0xb350b0
|
||||
else -> -0xa0a0b
|
||||
}
|
||||
spannable.setSpan(
|
||||
ForegroundColorSpan(color),
|
||||
0,
|
||||
spannable.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
return LogWrapper(line, spannable).apply { visible = true }
|
||||
}
|
||||
|
||||
private fun lineString(logLine: LogLine) =
|
||||
if (simpleFormatting) {
|
||||
logLine.toSimpleString()
|
||||
} else {
|
||||
logLine.toString()
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: VH, position: Int) {
|
||||
val line = lines[position]
|
||||
if (!line.visible) {
|
||||
holder.itemView.visibility = View.GONE
|
||||
return
|
||||
} else {
|
||||
holder.itemView.visibility = View.VISIBLE
|
||||
}
|
||||
class LogWrapper(val log: LogLine, val spannable: Spannable) : LogLine() {
|
||||
|
||||
val spanned = line.spannable
|
||||
holder.text.text = spanned
|
||||
var visible = true
|
||||
|
||||
override fun toString() = this.log.toString()
|
||||
override fun toSimpleString(): String = this.log.toSimpleString()
|
||||
override fun formattedTagAndMessage(): String = this.log.formattedTagAndMessage()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as LogWrapper
|
||||
|
||||
if (log != other.log) return false
|
||||
if (spannable != other.spannable) return false
|
||||
if (visible != other.visible) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getItemCount() = this.lines.size
|
||||
|
||||
private fun wrap(line: LogLine): LogWrapper {
|
||||
val spannable = SpannableString(lineString(line))
|
||||
val color =
|
||||
when (line.priority) {
|
||||
ERROR -> -0xbbcca
|
||||
WARNING -> -0x14c5
|
||||
INFO -> -0xb350b0
|
||||
else -> -0xa0a0b
|
||||
}
|
||||
spannable.setSpan(
|
||||
ForegroundColorSpan(color), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
return LogWrapper(line, spannable).apply { visible = true }
|
||||
}
|
||||
|
||||
private fun lineString(logLine: LogLine) =
|
||||
if (simpleFormatting) {
|
||||
logLine.toSimpleString()
|
||||
} else {
|
||||
logLine.toString()
|
||||
}
|
||||
|
||||
class LogWrapper(val log: LogLine, val spannable: Spannable) : LogLine() {
|
||||
|
||||
var visible = true
|
||||
|
||||
override fun toString() = this.log.toString()
|
||||
override fun toSimpleString(): String = this.log.toSimpleString()
|
||||
override fun formattedTagAndMessage(): String = this.log.formattedTagAndMessage()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as LogWrapper
|
||||
|
||||
if (log != other.log) return false
|
||||
if (spannable != other.spannable) return false
|
||||
if (visible != other.visible) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = log.hashCode()
|
||||
result = 31 * result + spannable.hashCode()
|
||||
result = 31 * result + visible.hashCode()
|
||||
return result
|
||||
}
|
||||
override fun hashCode(): Int {
|
||||
var result = log.hashCode()
|
||||
result = 31 * result + spannable.hashCode()
|
||||
result = 31 * result + visible.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,10 @@ public class SimpleIconTextAdapter extends IconTextAdapter<IconTextListItem> {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
} @NonNull
|
||||
@Override
|
||||
public IconTextListItem getItemAt(int index) {
|
||||
return items.get(index);
|
||||
@@ -53,8 +56,5 @@ public class SimpleIconTextAdapter extends IconTextAdapter<IconTextListItem> {
|
||||
return getItemAt(index).getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,18 @@ import java.util.List;
|
||||
|
||||
public class SymbolInputAdapter extends RecyclerView.Adapter<SymbolInputAdapter.VH> {
|
||||
|
||||
private static final List<Character> pairs;
|
||||
|
||||
static {
|
||||
pairs = new ArrayList<>();
|
||||
pairs.add('}');
|
||||
pairs.add(')');
|
||||
pairs.add(']');
|
||||
pairs.add('"');
|
||||
pairs.add('\'');
|
||||
pairs.add('>');
|
||||
}
|
||||
|
||||
private final IDEEditor editor;
|
||||
private Symbol[] symbols;
|
||||
|
||||
@@ -73,15 +85,6 @@ public class SymbolInputAdapter extends RecyclerView.Adapter<SymbolInputAdapter.
|
||||
return symbols == null ? 0 : symbols.length;
|
||||
}
|
||||
|
||||
public static class VH extends RecyclerView.ViewHolder {
|
||||
LayoutSymbolItemBinding binding;
|
||||
|
||||
public VH(LayoutSymbolItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
}
|
||||
}
|
||||
|
||||
void insertSymbol(String text, int selectionOffset) {
|
||||
if (selectionOffset < 0 || selectionOffset > text.length()) {
|
||||
return;
|
||||
@@ -108,15 +111,12 @@ public class SymbolInputAdapter extends RecyclerView.Adapter<SymbolInputAdapter.
|
||||
}
|
||||
}
|
||||
|
||||
private static final List<Character> pairs;
|
||||
public static class VH extends RecyclerView.ViewHolder {
|
||||
LayoutSymbolItemBinding binding;
|
||||
|
||||
static {
|
||||
pairs = new ArrayList<>();
|
||||
pairs.add('}');
|
||||
pairs.add(')');
|
||||
pairs.add(']');
|
||||
pairs.add('"');
|
||||
pairs.add('\'');
|
||||
pairs.add('>');
|
||||
public VH(LayoutSymbolItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package com.itsaky.androidide.adapters;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -13,23 +11,17 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.blankj.utilcode.util.ConvertUtils;
|
||||
import com.google.android.material.imageview.ShapeableImageView;
|
||||
import com.google.android.material.shape.CornerFamily;
|
||||
|
||||
import com.itsaky.androidide.databinding.WizardTemplateItemBinding;
|
||||
import com.itsaky.androidide.models.ProjectTemplate;
|
||||
import java.io.File;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class WizardTemplateAdapter extends RecyclerView.Adapter<WizardTemplateAdapter.ViewHolder> {
|
||||
|
||||
public interface OnItemClickListener {
|
||||
void onItemClick(ProjectTemplate item, int position);
|
||||
}
|
||||
|
||||
private final List<ProjectTemplate> mItems = new ArrayList<>();
|
||||
private OnItemClickListener mListener;
|
||||
|
||||
public WizardTemplateAdapter() {}
|
||||
|
||||
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||
@@ -117,4 +109,8 @@ public class WizardTemplateAdapter extends RecyclerView.Adapter<WizardTemplateAd
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnItemClickListener {
|
||||
void onItemClick(ProjectTemplate item, int position);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,10 +88,6 @@ public class XMLAttributeListAdapter extends RecyclerView.Adapter<XMLAttributeLi
|
||||
return attributes.size();
|
||||
}
|
||||
|
||||
public interface OnClickListener {
|
||||
void onClick(LayoutAttrEditorSheetItemBinding binding, XMLAttribute attribute);
|
||||
}
|
||||
|
||||
static class VH extends RecyclerView.ViewHolder {
|
||||
LayoutAttrEditorSheetItemBinding binding;
|
||||
|
||||
@@ -100,4 +96,8 @@ public class XMLAttributeListAdapter extends RecyclerView.Adapter<XMLAttributeLi
|
||||
this.binding = binding;
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnClickListener {
|
||||
void onClick(LayoutAttrEditorSheetItemBinding binding, XMLAttribute attribute);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,12 +34,48 @@ import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
public abstract class StudioActivity extends AppCompatActivity {
|
||||
private boolean toRequestStorage = true;
|
||||
|
||||
public static final String TAG = "StudioActivity";
|
||||
public static final int REQCODE_STORAGE = 1009;
|
||||
|
||||
protected static ILogger LOG = ILogger.newInstance("StudioActivity");
|
||||
private boolean toRequestStorage = true;
|
||||
|
||||
public void loadFrament(Fragment frag, View view) {
|
||||
loadFragment(frag, view.getId());
|
||||
}
|
||||
|
||||
public void loadFragment(Fragment fragment, int id) {
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
transaction.replace(id, fragment);
|
||||
transaction.commit();
|
||||
}
|
||||
|
||||
public StudioApp getApp() {
|
||||
return (StudioApp) getApplication();
|
||||
}
|
||||
|
||||
public int getColorPrimary() {
|
||||
return getTypedValueForAttr(R.attr.colorPrimary).data;
|
||||
}
|
||||
|
||||
public TypedValue getTypedValueForAttr(@AttrRes int attrRes) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
getTheme().resolveAttribute(attrRes, typedValue, true);
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
public int getColorPrimaryDark() {
|
||||
return getTypedValueForAttr(R.attr.colorPrimaryDark).data;
|
||||
}
|
||||
|
||||
public int getColorAccent() {
|
||||
return getTypedValueForAttr(R.attr.colorAccent).data;
|
||||
}
|
||||
|
||||
public int dpToPx(int dp) {
|
||||
return (int)
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -65,10 +101,6 @@ public abstract class StudioActivity extends AppCompatActivity {
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
protected void doNotRequestStorage() {
|
||||
this.toRequestStorage = false;
|
||||
}
|
||||
|
||||
protected void requestStorage() {
|
||||
if (isStoragePermissionGranted()) {
|
||||
onStorageGranted();
|
||||
@@ -82,43 +114,7 @@ public abstract class StudioActivity extends AppCompatActivity {
|
||||
REQCODE_STORAGE);
|
||||
}
|
||||
|
||||
public void loadFrament(Fragment frag, View view) {
|
||||
loadFragment(frag, view.getId());
|
||||
}
|
||||
|
||||
public void loadFragment(Fragment fragment, int id) {
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
transaction.replace(id, fragment);
|
||||
transaction.commit();
|
||||
}
|
||||
|
||||
public StudioApp getApp() {
|
||||
return (StudioApp) getApplication();
|
||||
}
|
||||
|
||||
public TypedValue getTypedValueForAttr(@AttrRes int attrRes) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
getTheme().resolveAttribute(attrRes, typedValue, true);
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
public int getColorPrimary() {
|
||||
return getTypedValueForAttr(R.attr.colorPrimary).data;
|
||||
}
|
||||
|
||||
public int getColorPrimaryDark() {
|
||||
return getTypedValueForAttr(R.attr.colorPrimaryDark).data;
|
||||
}
|
||||
|
||||
public int getColorAccent() {
|
||||
return getTypedValueForAttr(R.attr.colorAccent).data;
|
||||
}
|
||||
|
||||
public int dpToPx(int dp) {
|
||||
return (int)
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
|
||||
}
|
||||
protected void onStorageGranted() {}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(
|
||||
@@ -134,11 +130,13 @@ public abstract class StudioActivity extends AppCompatActivity {
|
||||
|
||||
protected void onStorageAlreadyGranted() {}
|
||||
|
||||
protected void onStorageGranted() {}
|
||||
|
||||
protected void onStorageDenied() {}
|
||||
|
||||
protected void onSetContentView() {}
|
||||
|
||||
protected abstract View bindLayout();
|
||||
|
||||
protected void doNotRequestStorage() {
|
||||
this.toRequestStorage = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,110 +94,16 @@ public class IDEDocumentsProvider extends DocumentsProvider {
|
||||
return new File(Environment.DEFAULT_HOME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the document id given a file. This document id must be consistent across time as other
|
||||
* applications may save the ID and use it to reference documents later.
|
||||
*
|
||||
* <p>The reverse of @{link #getFileForDocId}.
|
||||
*/
|
||||
private static String getDocIdForFile(File file) {
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
/** Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}). */
|
||||
private static File getFileForDocId(String docId) throws FileNotFoundException {
|
||||
final File f = new File(docId);
|
||||
if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found");
|
||||
return f;
|
||||
}
|
||||
|
||||
private static String getMimeType(File file) {
|
||||
if (file.isDirectory()) {
|
||||
return Document.MIME_TYPE_DIR;
|
||||
} else {
|
||||
final String name = file.getName();
|
||||
final int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
final String extension = name.substring(lastDot + 1).toLowerCase(Locale.ROOT);
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mime != null) return mime;
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection) {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||
final String applicationName = getContext().getString(R.string.app_name);
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
LOG.debug("queryRoots() before all add");
|
||||
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
|
||||
LOG.debug("queryRoots() before all add, 1");
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
|
||||
LOG.debug("queryRoots() before all add, 2");
|
||||
row.add(Root.COLUMN_SUMMARY, null);
|
||||
LOG.debug("queryRoots() before all add, 3");
|
||||
row.add(
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD);
|
||||
LOG.debug("queryRoots() before all add, 4");
|
||||
row.add(Root.COLUMN_TITLE, applicationName);
|
||||
LOG.debug("queryRoots() before all add, 5");
|
||||
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
||||
LOG.debug("queryRoots() before all add, 6");
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
||||
LOG.debug("queryRoots() before all add, 7");
|
||||
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
LOG.debug("queryRoots() before all add");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
includeFile(result, documentId, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
|
||||
throws FileNotFoundException {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
final File parent = getFileForDocId(parentDocumentId);
|
||||
for (File file : parent.listFiles()) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(
|
||||
final String documentId, String mode, CancellationSignal signal)
|
||||
throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
final int accessMode = ParcelFileDescriptor.parseMode(mode);
|
||||
return ParcelFileDescriptor.open(file, accessMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openDocumentThumbnail(
|
||||
String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
final ParcelFileDescriptor pfd =
|
||||
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
return new AssetFileDescriptor(pfd, 0, file.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChildDocument(String parentDocumentId, String documentId) {
|
||||
return documentId.startsWith(parentDocumentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createDocument(String parentDocumentId, String mimeType, String displayName)
|
||||
throws FileNotFoundException {
|
||||
@@ -231,9 +137,62 @@ public class IDEDocumentsProvider extends DocumentsProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||
File file = getFileForDocId(documentId);
|
||||
return getMimeType(file);
|
||||
public Cursor queryRoots(String[] projection) {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||
final String applicationName = getContext().getString(R.string.app_name);
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
LOG.debug("queryRoots() before all add");
|
||||
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
|
||||
LOG.debug("queryRoots() before all add, 1");
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
|
||||
LOG.debug("queryRoots() before all add, 2");
|
||||
row.add(Root.COLUMN_SUMMARY, null);
|
||||
LOG.debug("queryRoots() before all add, 3");
|
||||
row.add(
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD);
|
||||
LOG.debug("queryRoots() before all add, 4");
|
||||
row.add(Root.COLUMN_TITLE, applicationName);
|
||||
LOG.debug("queryRoots() before all add, 5");
|
||||
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
||||
LOG.debug("queryRoots() before all add, 6");
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
||||
LOG.debug("queryRoots() before all add, 7");
|
||||
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
LOG.debug("queryRoots() before all add");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the document id given a file. This document id must be consistent across time as other
|
||||
* applications may save the ID and use it to reference documents later.
|
||||
*
|
||||
* <p>The reverse of @{link #getFileForDocId}.
|
||||
*/
|
||||
private static String getDocIdForFile(File file) {
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
includeFile(result, documentId, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
|
||||
throws FileNotFoundException {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
final File parent = getFileForDocId(parentDocumentId);
|
||||
for (File file : parent.listFiles()) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -277,8 +236,27 @@ public class IDEDocumentsProvider extends DocumentsProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChildDocument(String parentDocumentId, String documentId) {
|
||||
return documentId.startsWith(parentDocumentId);
|
||||
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||
File file = getFileForDocId(documentId);
|
||||
return getMimeType(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(
|
||||
final String documentId, String mode, CancellationSignal signal)
|
||||
throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
final int accessMode = ParcelFileDescriptor.parseMode(mode);
|
||||
return ParcelFileDescriptor.open(file, accessMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openDocumentThumbnail(
|
||||
String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
final ParcelFileDescriptor pfd =
|
||||
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
return new AssetFileDescriptor(pfd, 0, file.length());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,4 +295,26 @@ public class IDEDocumentsProvider extends DocumentsProvider {
|
||||
row.add(Document.COLUMN_FLAGS, flags);
|
||||
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
}
|
||||
|
||||
/** Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}). */
|
||||
private static File getFileForDocId(String docId) throws FileNotFoundException {
|
||||
final File f = new File(docId);
|
||||
if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found");
|
||||
return f;
|
||||
}
|
||||
|
||||
private static String getMimeType(File file) {
|
||||
if (file.isDirectory()) {
|
||||
return Document.MIME_TYPE_DIR;
|
||||
} else {
|
||||
final String name = file.getName();
|
||||
final int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
final String extension = name.substring(lastDot + 1).toLowerCase(Locale.ROOT);
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mime != null) return mime;
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,6 @@
|
||||
|
||||
package com.itsaky.androidide.fragments;
|
||||
|
||||
import abhishekti7.unicorn.filepicker.UnicornFilePicker;
|
||||
import abhishekti7.unicorn.filepicker.ui.FilePickerActivity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Environment;
|
||||
@@ -41,31 +38,12 @@ import com.itsaky.toaster.Toaster;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import abhishekti7.unicorn.filepicker.UnicornFilePicker;
|
||||
import abhishekti7.unicorn.filepicker.ui.FilePickerActivity;
|
||||
|
||||
public class BaseFragment extends Fragment {
|
||||
|
||||
private FileChoserCallback dirCallback;
|
||||
|
||||
protected StudioActivity getStudioActivity() {
|
||||
return (StudioActivity) getActivity();
|
||||
}
|
||||
|
||||
protected void pickDirectory(FileChoserCallback dirCallback) {
|
||||
this.dirCallback = dirCallback;
|
||||
UnicornFilePicker.from(this)
|
||||
.addConfigBuilder()
|
||||
.addItemDivider(false)
|
||||
.selectMultipleFiles(false)
|
||||
.setRootDirectory(Environment.getExternalStorageDirectory().getAbsolutePath())
|
||||
.showHiddenFiles(true)
|
||||
.showOnlyDirectory(true)
|
||||
.theme(R.style.AppTheme_FilePicker)
|
||||
.build();
|
||||
Intent intent = new Intent(requireActivity(), FilePickerActivity.class);
|
||||
|
||||
// .forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE);
|
||||
startForResult.launch(intent);
|
||||
}
|
||||
|
||||
ActivityResultLauncher<Intent> startForResult =
|
||||
registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
@@ -95,6 +73,27 @@ public class BaseFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
protected StudioActivity getStudioActivity() {
|
||||
return (StudioActivity) getActivity();
|
||||
}
|
||||
|
||||
protected void pickDirectory(FileChoserCallback dirCallback) {
|
||||
this.dirCallback = dirCallback;
|
||||
UnicornFilePicker.from(this)
|
||||
.addConfigBuilder()
|
||||
.addItemDivider(false)
|
||||
.selectMultipleFiles(false)
|
||||
.setRootDirectory(Environment.getExternalStorageDirectory().getAbsolutePath())
|
||||
.showHiddenFiles(true)
|
||||
.showOnlyDirectory(true)
|
||||
.theme(R.style.AppTheme_FilePicker)
|
||||
.build();
|
||||
Intent intent = new Intent(requireActivity(), FilePickerActivity.class);
|
||||
|
||||
// .forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE);
|
||||
startForResult.launch(intent);
|
||||
}
|
||||
|
||||
interface FileChoserCallback {
|
||||
void picketDictionary(File file);
|
||||
}
|
||||
|
||||
@@ -70,6 +70,14 @@ public class DiagnosticsListFragment extends Fragment {
|
||||
unsavedAdapter = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unsavedAdapter = null;
|
||||
list = null;
|
||||
emptyView = null;
|
||||
}
|
||||
|
||||
public void setAdapter(@NonNull DiagnosticsAdapter adapter) {
|
||||
if (list != null) {
|
||||
list.setAdapter(adapter);
|
||||
@@ -84,12 +92,4 @@ public class DiagnosticsListFragment extends Fragment {
|
||||
list.setVisibility(error ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unsavedAdapter = null;
|
||||
list = null;
|
||||
emptyView = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,14 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
mFileTreeView = null;
|
||||
mFileActionListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
@@ -201,12 +209,6 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
__ -> finalWhenDone.run());
|
||||
}
|
||||
|
||||
private void updateChevron(@NonNull TreeNode node) {
|
||||
if (node.getViewHolder() instanceof FileTreeViewHolder) {
|
||||
((FileTreeViewHolder) node.getViewHolder()).updateChevron(!node.isExpanded());
|
||||
}
|
||||
}
|
||||
|
||||
private void getNodeFromFiles(File[] files, TreeNode parent) {
|
||||
Arrays.sort(files, new FileTreeCallable.SortFileName());
|
||||
Arrays.sort(files, new FileTreeCallable.SortFolder());
|
||||
@@ -217,6 +219,12 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChevron(@NonNull TreeNode node) {
|
||||
if (node.getViewHolder() instanceof FileTreeViewHolder) {
|
||||
((FileTreeViewHolder) node.getViewHolder()).updateChevron(!node.isExpanded());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(TreeNode node, Object value) {
|
||||
if (mFileActionListener != null) {
|
||||
@@ -332,14 +340,6 @@ public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
mFileTreeView = null;
|
||||
mFileActionListener = null;
|
||||
}
|
||||
|
||||
public interface FileActionListener {
|
||||
CodeEditorView openFile(File file);
|
||||
|
||||
|
||||
@@ -24,30 +24,30 @@ import com.itsaky.androidide.utils.ILogger
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class IDELogFragment : LogViewFragment() {
|
||||
private var logListener: ILogger.LogListener? =
|
||||
ILogger.LogListener { priority, tag, message ->
|
||||
if (message.contains("\n")) {
|
||||
val split = message.split("\n").toTypedArray()
|
||||
for (line in split) {
|
||||
logLine(priority, tag, line)
|
||||
}
|
||||
} else {
|
||||
logLine(priority, tag, message)
|
||||
}
|
||||
private var logListener: ILogger.LogListener? =
|
||||
ILogger.LogListener { priority, tag, message ->
|
||||
if (message.contains("\n")) {
|
||||
val split = message.split("\n").toTypedArray()
|
||||
for (line in split) {
|
||||
logLine(priority, tag, line)
|
||||
}
|
||||
|
||||
init {
|
||||
ILogger.addLogListener(this.logListener)
|
||||
} else {
|
||||
logLine(priority, tag, message)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isSimpleFormattingEnabled() = true
|
||||
override fun getLogType() = "ide_logs"
|
||||
init {
|
||||
ILogger.addLogListener(this.logListener)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
ILogger.removeLogListener(logListener)
|
||||
logListener = null
|
||||
}
|
||||
override fun isSimpleFormattingEnabled() = true
|
||||
override fun getLogType() = "ide_logs"
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
ILogger.removeLogListener(logListener)
|
||||
logListener = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,6 +55,6 @@ class IDELogFragment : LogViewFragment() {
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class AppLogFragment : LogViewFragment() {
|
||||
override fun isSimpleFormattingEnabled() = false
|
||||
override fun getLogType() = "app_logs"
|
||||
override fun isSimpleFormattingEnabled() = false
|
||||
override fun getLogType() = "app_logs"
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ abstract class LogViewFragment : Fragment() {
|
||||
if (!lineString.endsWith("\n")) {
|
||||
lineString += "\n"
|
||||
}
|
||||
|
||||
|
||||
ThreadUtils.runOnUiThread { this.binding!!.editor.append(lineString) }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +1,22 @@
|
||||
package com.itsaky.androidide.fragments;
|
||||
|
||||
import abhishekti7.unicorn.filepicker.UnicornFilePicker;
|
||||
import abhishekti7.unicorn.filepicker.ui.FilePickerActivity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
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 com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import com.itsaky.androidide.EditorActivity;
|
||||
import com.itsaky.androidide.PreferencesActivity;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.TerminalActivity;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.FragmentMainBinding;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MainFragment extends BaseFragment
|
||||
implements WizardFragment.OnProjectCreatedListener, View.OnClickListener {
|
||||
@@ -97,13 +85,13 @@ public class MainFragment extends BaseFragment
|
||||
.commit();
|
||||
}
|
||||
|
||||
private void gotoSettings() {
|
||||
startActivity(new Intent(requireActivity(), PreferencesActivity.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openProject(@NonNull File root) {
|
||||
ProjectManager.INSTANCE.setProjectPath(root.getAbsolutePath());
|
||||
startActivity(new Intent(requireActivity(), EditorActivity.class));
|
||||
}
|
||||
|
||||
private void gotoSettings() {
|
||||
startActivity(new Intent(requireActivity(), PreferencesActivity.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,12 @@ public abstract class NonEditableEditorFragment extends Fragment {
|
||||
editor.setColorScheme(new SchemeAndroidIDE());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public IDEEditor getEditor() {
|
||||
if (binding == null) {
|
||||
@@ -71,10 +77,4 @@ public abstract class NonEditableEditorFragment extends Fragment {
|
||||
}
|
||||
return binding.editor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,14 @@ public class SearchResultFragment extends Fragment {
|
||||
unsavedAdapter = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unsavedAdapter = null;
|
||||
list = null;
|
||||
emptyView = null;
|
||||
}
|
||||
|
||||
public void setAdapter(@NonNull final SearchListAdapter adapter) {
|
||||
if (list == null) {
|
||||
unsavedAdapter = adapter;
|
||||
@@ -82,12 +90,4 @@ public class SearchResultFragment extends Fragment {
|
||||
list.setVisibility(error ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
unsavedAdapter = null;
|
||||
list = null;
|
||||
emptyView = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import androidx.transition.TransitionManager;
|
||||
|
||||
import com.google.android.material.transition.MaterialFadeThrough;
|
||||
import com.google.android.material.transition.MaterialSharedAxis;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.WizardTemplateAdapter;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
@@ -68,9 +67,6 @@ public class WizardFragment extends BaseFragment {
|
||||
private OnProjectCreatedListener mListener;
|
||||
|
||||
private boolean mLast;
|
||||
private int minSdkIndex;
|
||||
private int targetSdkIndex;
|
||||
|
||||
private final OnBackPressedCallback onBackPressedCallback =
|
||||
new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
@@ -78,6 +74,8 @@ public class WizardFragment extends BaseFragment {
|
||||
onNavigateBack();
|
||||
}
|
||||
};
|
||||
private int minSdkIndex;
|
||||
private int targetSdkIndex;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -171,12 +169,6 @@ public class WizardFragment extends BaseFragment {
|
||||
initDetailsView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
onBackPressedCallback.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
@@ -190,6 +182,28 @@ public class WizardFragment extends BaseFragment {
|
||||
mProgressSheet = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
onBackPressedCallback.setEnabled(false);
|
||||
}
|
||||
|
||||
public void onSuccess(File root) {
|
||||
if (mProgressSheet != null && mProgressSheet.isShowing()) {
|
||||
mProgressSheet.dismiss();
|
||||
}
|
||||
StudioApp.getInstance().toast(R.string.project_created_successfully, Toaster.Type.SUCCESS);
|
||||
|
||||
if (mListener != null) {
|
||||
getParentFragmentManager().popBackStack();
|
||||
mListener.openProject(root);
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnProjectCreatedListener(OnProjectCreatedListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
private String getSelectedItem(int pos, AutoCompleteTextView view) {
|
||||
return view.getAdapter().getItem(pos).toString();
|
||||
}
|
||||
@@ -516,18 +530,6 @@ public class WizardFragment extends BaseFragment {
|
||||
setSaveLocation();
|
||||
}
|
||||
|
||||
public void onSuccess(File root) {
|
||||
if (mProgressSheet != null && mProgressSheet.isShowing()) {
|
||||
mProgressSheet.dismiss();
|
||||
}
|
||||
StudioApp.getInstance().toast(R.string.project_created_successfully, Toaster.Type.SUCCESS);
|
||||
|
||||
if (mListener != null) {
|
||||
getParentFragmentManager().popBackStack();
|
||||
mListener.openProject(root);
|
||||
}
|
||||
}
|
||||
|
||||
private void createProgressSheet() {
|
||||
mProgressSheet = new ProgressSheet();
|
||||
mProgressSheet.setShowShadow(false);
|
||||
@@ -554,8 +556,4 @@ public class WizardFragment extends BaseFragment {
|
||||
public interface OnProjectCreatedListener {
|
||||
void openProject(File project);
|
||||
}
|
||||
|
||||
public void setOnProjectCreatedListener(OnProjectCreatedListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,15 +41,6 @@ public abstract class AbstractReferenceEditor extends BaseValueEditorFragment {
|
||||
private static final ILogger LOG = ILogger.newInstance("ReferenceEditor");
|
||||
public TextWatcherAdapter resInputWatcher;
|
||||
|
||||
@Override
|
||||
protected void notifyValueChanged(@NonNull String newValue) {
|
||||
try {
|
||||
super.notifyValueChanged(newValue);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unable to update resource value to '" + newValue + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setupReferenceInput(MaterialAutoCompleteTextView referenceInput) {
|
||||
Objects.requireNonNull(referenceInput);
|
||||
|
||||
@@ -78,6 +69,15 @@ public abstract class AbstractReferenceEditor extends BaseValueEditorFragment {
|
||||
.whenComplete((list, error) -> handleAutoCompleteResult(list, error, referenceInput));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyValueChanged(@NonNull String newValue) {
|
||||
try {
|
||||
super.notifyValueChanged(newValue);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unable to update resource value to '" + newValue + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleAutoCompleteResult(
|
||||
List<String> list, Throwable error, AutoCompleteTextView referenceInput) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -77,28 +76,6 @@ public class ColorEditor extends AbstractReferenceEditor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> computeReferenceItems() {
|
||||
final var list = new ArrayList<String>();
|
||||
final var tables = ValuesTableFactory.getAllTables();
|
||||
for (var entry : tables.entrySet()) {
|
||||
final var dimens = entry.getValue().getTable("color");
|
||||
if (dimens != null) {
|
||||
list.addAll(dimens.keySet().stream().map("@color/"::concat).collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(
|
||||
FrameworkValues.listColors().stream()
|
||||
.map("@android:color/"::concat)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
final var resTable = StudioApp.getInstance().getResourceTable();
|
||||
list.addAll(resTable.listResourceNames("color"));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private Dialog getColorPickerDialog() {
|
||||
|
||||
if (colorPickerDialog == null) {
|
||||
@@ -122,4 +99,26 @@ public class ColorEditor extends AbstractReferenceEditor {
|
||||
|
||||
return colorPickerDialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> computeReferenceItems() {
|
||||
final var list = new ArrayList<String>();
|
||||
final var tables = ValuesTableFactory.getAllTables();
|
||||
for (var entry : tables.entrySet()) {
|
||||
final var dimens = entry.getValue().getTable("color");
|
||||
if (dimens != null) {
|
||||
list.addAll(dimens.keySet().stream().map("@color/"::concat).collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(
|
||||
FrameworkValues.listColors().stream()
|
||||
.map("@android:color/"::concat)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
final var resTable = StudioApp.getInstance().getResourceTable();
|
||||
list.addAll(resTable.listResourceNames("color"));
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,13 @@ public class FlagEditor extends FixedValueEditor {
|
||||
// Does noting...
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Chip newChip(String title, boolean checked) {
|
||||
final var chip = super.newChip(title, checked);
|
||||
chip.setOnCheckedChangeListener(this::onCheckChangedMultiple);
|
||||
return chip;
|
||||
}
|
||||
|
||||
protected void onCheckChangedMultiple(CompoundButton compoundButton, boolean b) {
|
||||
final var items = new ArrayList<String>();
|
||||
for (var id : this.chipGroup.getCheckedChipIds()) {
|
||||
@@ -78,11 +85,4 @@ public class FlagEditor extends FixedValueEditor {
|
||||
|
||||
notifyValueChanged(TextUtils.join("|", items));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Chip newChip(String title, boolean checked) {
|
||||
final var chip = super.newChip(title, checked);
|
||||
chip.setOnCheckedChangeListener(this::onCheckChangedMultiple);
|
||||
return chip;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -31,6 +31,11 @@ import com.itsaky.androidide.managers.PreferenceManager;
|
||||
|
||||
public abstract class BasePreferenceFragment extends PreferenceFragmentCompat {
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle p1, String p2) {
|
||||
setPreferencesFromResource(com.itsaky.androidide.R.xml.ide_prefs, p2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
@@ -43,11 +48,6 @@ public abstract class BasePreferenceFragment extends PreferenceFragmentCompat {
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle p1, String p2) {
|
||||
setPreferencesFromResource(com.itsaky.androidide.R.xml.ide_prefs, p2);
|
||||
}
|
||||
|
||||
protected PreferenceManager getPrefManager() {
|
||||
return StudioApp.getInstance().getPrefManager();
|
||||
}
|
||||
|
||||
@@ -120,15 +120,6 @@ public class BuildPreferences extends BasePreferenceFragment
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(final Preference preference, final Object newValue) {
|
||||
final var key = preference.getKey();
|
||||
if (KEY_TP_FIX.equals(key)) {
|
||||
getPrefManager().putBoolean(KEY_TP_FIX, ((boolean) newValue));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void changeGradleDist() {
|
||||
final var builder = DialogUtils.newMaterialDialogBuilder(getContext());
|
||||
final var binding = LayoutDialogTextInputBinding.inflate(getLayoutInflater());
|
||||
@@ -228,4 +219,13 @@ public class BuildPreferences extends BasePreferenceFragment
|
||||
? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait))
|
||||
: progressSheet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(final Preference preference, final Object newValue) {
|
||||
final var key = preference.getKey();
|
||||
if (KEY_TP_FIX.equals(key)) {
|
||||
getPrefManager().putBoolean(KEY_TP_FIX, ((boolean) newValue));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,20 +123,6 @@ public class IDEPreferences extends BasePreferenceFragment
|
||||
: mBuildPreferences;
|
||||
}
|
||||
|
||||
private void showChangelog() {
|
||||
final var intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(
|
||||
Uri.parse(
|
||||
BaseApplication.GITHUB_URL.concat("/releases/tag/v").concat(BuildConfig.VERSION_NAME)));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
try {
|
||||
startActivity(intent);
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Unable to start activity to show changelog", th);
|
||||
StudioApp.getInstance().toast("Unable to start activity", Toaster.Type.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(@NonNull Preference preference) {
|
||||
final String key = preference.getKey();
|
||||
@@ -156,4 +142,18 @@ public class IDEPreferences extends BasePreferenceFragment
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showChangelog() {
|
||||
final var intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(
|
||||
Uri.parse(
|
||||
BaseApplication.GITHUB_URL.concat("/releases/tag/v").concat(BuildConfig.VERSION_NAME)));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
try {
|
||||
startActivity(intent);
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Unable to start activity to show changelog", th);
|
||||
StudioApp.getInstance().toast("Unable to start activity", Toaster.Type.ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,19 +102,6 @@ public class AttrEditorSheet extends BottomSheetDialogFragment
|
||||
setupViewData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
mDeletionFailedListener = (OnViewDeletionFailedListener) context;
|
||||
}
|
||||
|
||||
public AttrEditorSheet setLayout(File layout) {
|
||||
Objects.requireNonNull(layout);
|
||||
|
||||
this.layout = layout;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void setupViewData() {
|
||||
binding.actionsList.setAdapter(new SimpleIconTextAdapter(VIEW_ACTIONS).setOnBindListener(this));
|
||||
|
||||
@@ -132,10 +119,6 @@ public class AttrEditorSheet extends BottomSheetDialogFragment
|
||||
this::onAttrClick));
|
||||
}
|
||||
|
||||
public void setSelectedView(IView view) {
|
||||
this.selectedView = view;
|
||||
}
|
||||
|
||||
private void onAttrClick(
|
||||
LayoutAttrEditorSheetItemBinding binding, @NonNull XMLAttribute attribute) {
|
||||
|
||||
@@ -158,6 +141,42 @@ public class AttrEditorSheet extends BottomSheetDialogFragment
|
||||
getValueEditorSheet(attribute).show(getChildFragmentManager(), "attr_value_editor_sheet");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public AttrValueEditorSheet getValueEditorSheet(@NonNull XMLAttribute attribute) {
|
||||
|
||||
if (mValueEditorSheet == null) {
|
||||
mValueEditorSheet = AttrValueEditorSheet.newInstance(attribute);
|
||||
}
|
||||
|
||||
mValueEditorSheet.setAttribute(attribute);
|
||||
return mValueEditorSheet;
|
||||
}
|
||||
|
||||
private boolean dismissIfSelectedNull() {
|
||||
if (selectedView == null) {
|
||||
dismiss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
mDeletionFailedListener = (OnViewDeletionFailedListener) context;
|
||||
}
|
||||
|
||||
public AttrEditorSheet setLayout(File layout) {
|
||||
Objects.requireNonNull(layout);
|
||||
|
||||
this.layout = layout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setSelectedView(IView view) {
|
||||
this.selectedView = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postBind(
|
||||
IconTextListItem item, @NonNull SimpleIconTextAdapter.VH holder, int position) {
|
||||
@@ -276,11 +295,6 @@ public class AttrEditorSheet extends BottomSheetDialogFragment
|
||||
return new ArrayList<>(attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Attr attr) {
|
||||
addNewAttribute(attr);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private AttributeListSheet getAttrListSheet() {
|
||||
if (mAttrListSheet == null) {
|
||||
@@ -291,15 +305,15 @@ public class AttrEditorSheet extends BottomSheetDialogFragment
|
||||
return mAttrListSheet;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public AttrValueEditorSheet getValueEditorSheet(@NonNull XMLAttribute attribute) {
|
||||
@Override
|
||||
public void accept(Attr attr) {
|
||||
addNewAttribute(attr);
|
||||
}
|
||||
|
||||
if (mValueEditorSheet == null) {
|
||||
mValueEditorSheet = AttrValueEditorSheet.newInstance(attribute);
|
||||
}
|
||||
|
||||
mValueEditorSheet.setAttribute(attribute);
|
||||
return mValueEditorSheet;
|
||||
private void addNewAttribute(@NonNull Attr attr) {
|
||||
final XMLAttribute attribute = new XMLAttribute(attr.namespace, attr.name, "", false);
|
||||
attribute.setAttr(attr);
|
||||
showValueEditorSheet(attribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -320,20 +334,6 @@ public class AttrEditorSheet extends BottomSheetDialogFragment
|
||||
}
|
||||
}
|
||||
|
||||
private void addNewAttribute(@NonNull Attr attr) {
|
||||
final XMLAttribute attribute = new XMLAttribute(attr.namespace, attr.name, "", false);
|
||||
attribute.setAttr(attr);
|
||||
showValueEditorSheet(attribute);
|
||||
}
|
||||
|
||||
private boolean dismissIfSelectedNull() {
|
||||
if (selectedView == null) {
|
||||
dismiss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener can be used to get notified when we fail to remove a view from its parent. This is
|
||||
* used in {@link com.itsaky.androidide.DesignerActivity DesignerActivity}.
|
||||
|
||||
@@ -53,12 +53,6 @@ public class AttributeListSheet extends BottomSheetDialogFragment {
|
||||
private Consumer<Attr> clickConsumer;
|
||||
private List<Attr> mItems;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setStyle(DialogFragment.STYLE_NORMAL, R.style.AppTheme_SheetAboveKeyboard);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@@ -104,6 +98,16 @@ public class AttributeListSheet extends BottomSheetDialogFragment {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
update(mItems);
|
||||
}
|
||||
|
||||
public void update(List<Attr> items) {
|
||||
if (binding != null) {
|
||||
binding.attrList.setAdapter(new AttrListAdapter(clickConsumer, items));
|
||||
}
|
||||
}
|
||||
|
||||
public void setItems(@Nullable List<Attr> items) {
|
||||
if (items == null) {
|
||||
items = new ArrayList<>();
|
||||
@@ -120,13 +124,9 @@ public class AttributeListSheet extends BottomSheetDialogFragment {
|
||||
this.clickConsumer = (Consumer<Attr>) getParentFragment();
|
||||
}
|
||||
|
||||
public void update() {
|
||||
update(mItems);
|
||||
}
|
||||
|
||||
public void update(List<Attr> items) {
|
||||
if (binding != null) {
|
||||
binding.attrList.setAdapter(new AttrListAdapter(clickConsumer, items));
|
||||
}
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setStyle(DialogFragment.STYLE_NORMAL, R.style.AppTheme_SheetAboveKeyboard);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -47,6 +47,8 @@ public abstract class BaseBottomSheetFragment extends BottomSheetDialogFragment
|
||||
return mDialog;
|
||||
}
|
||||
|
||||
protected void onShow() {}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
@@ -97,10 +99,6 @@ public abstract class BaseBottomSheetFragment extends BottomSheetDialogFragment
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return mDialog != null && mDialog.isShowing();
|
||||
}
|
||||
|
||||
protected boolean shouldHideTitle() {
|
||||
return !titleEnabled;
|
||||
}
|
||||
@@ -123,6 +121,12 @@ public abstract class BaseBottomSheetFragment extends BottomSheetDialogFragment
|
||||
return this;
|
||||
}
|
||||
|
||||
protected abstract void bind(LinearLayout container);
|
||||
|
||||
public boolean isShowing() {
|
||||
return mDialog != null && mDialog.isShowing();
|
||||
}
|
||||
|
||||
public void setShowShadow(boolean enabled) {
|
||||
this.shadowEnabled = enabled;
|
||||
}
|
||||
@@ -131,10 +135,6 @@ public abstract class BaseBottomSheetFragment extends BottomSheetDialogFragment
|
||||
this.titleEnabled = enabled;
|
||||
}
|
||||
|
||||
protected void onShow() {}
|
||||
|
||||
protected abstract void bind(LinearLayout container);
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
@@ -42,13 +42,6 @@ public class OptionsListFragment extends BaseBottomSheetFragment {
|
||||
private RecyclerView mList;
|
||||
private OnOptionsClickListener listener;
|
||||
|
||||
@Override
|
||||
protected void bind(@NonNull LinearLayout container) {
|
||||
mList = new RecyclerView(requireContext());
|
||||
container.removeAllViews();
|
||||
container.addView(mList, new LinearLayout.LayoutParams(-1, -1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
@@ -64,6 +57,18 @@ public class OptionsListFragment extends BaseBottomSheetFragment {
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return getString(com.itsaky.androidide.R.string.file_options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bind(@NonNull LinearLayout container) {
|
||||
mList = new RecyclerView(requireContext());
|
||||
container.removeAllViews();
|
||||
container.addView(mList, new LinearLayout.LayoutParams(-1, -1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
@@ -90,11 +95,6 @@ public class OptionsListFragment extends BaseBottomSheetFragment {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return getString(com.itsaky.androidide.R.string.file_options);
|
||||
}
|
||||
|
||||
public static interface OnOptionsClickListener {
|
||||
void onOptionsClick(SheetOption option);
|
||||
}
|
||||
|
||||
@@ -41,12 +41,6 @@ public class ProgressSheet extends BaseBottomSheetFragment {
|
||||
private boolean subMessageEnabled = false;
|
||||
private boolean welcomeTextEnabled = false;
|
||||
|
||||
@Override
|
||||
protected void bind(LinearLayout container) {
|
||||
binding = LayoutProgressSheetBinding.inflate(LayoutInflater.from(getContext()));
|
||||
container.addView(binding.getRoot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
@@ -85,6 +79,12 @@ public class ProgressSheet extends BaseBottomSheetFragment {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bind(LinearLayout container) {
|
||||
binding = LayoutProgressSheetBinding.inflate(LayoutInflater.from(getContext()));
|
||||
container.addView(binding.getRoot());
|
||||
}
|
||||
|
||||
public ProgressSheet setWelcomeTextEnabled(boolean enabled) {
|
||||
this.welcomeTextEnabled = enabled;
|
||||
return this;
|
||||
|
||||
@@ -40,34 +40,6 @@ public class TextSheetFragment extends BaseBottomSheetFragment {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bind(@NonNull LinearLayout container) {
|
||||
final IDEEditor e = getEditor();
|
||||
container.setPadding(0, 0, 0, 0);
|
||||
container.setPaddingRelative(0, 0, 0, 0);
|
||||
LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(-1, 0);
|
||||
p.weight = 1.0f;
|
||||
if (e.getParent() != null && e.getParent() instanceof ViewGroup) {
|
||||
((ViewGroup) e.getParent()).removeView(e);
|
||||
}
|
||||
container.addView(getEditor(), p);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return getString(R.string.build_output);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShow() {
|
||||
super.onShow();
|
||||
editor.setText(getOutputBuilder());
|
||||
}
|
||||
|
||||
private void scrollToBottom() {
|
||||
if (getActivity() != null) getActivity().runOnUiThread(() -> editor.goToEnd());
|
||||
}
|
||||
|
||||
public TextSheetFragment setTitleText(int res) {
|
||||
super.setTitle(res);
|
||||
return this;
|
||||
@@ -111,6 +83,34 @@ public class TextSheetFragment extends BaseBottomSheetFragment {
|
||||
});
|
||||
}
|
||||
|
||||
private void scrollToBottom() {
|
||||
if (getActivity() != null) getActivity().runOnUiThread(() -> editor.goToEnd());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
return getString(R.string.build_output);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShow() {
|
||||
super.onShow();
|
||||
editor.setText(getOutputBuilder());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bind(@NonNull LinearLayout container) {
|
||||
final IDEEditor e = getEditor();
|
||||
container.setPadding(0, 0, 0, 0);
|
||||
container.setPaddingRelative(0, 0, 0, 0);
|
||||
LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(-1, 0);
|
||||
p.weight = 1.0f;
|
||||
if (e.getParent() != null && e.getParent() instanceof ViewGroup) {
|
||||
((ViewGroup) e.getParent()).removeView(e);
|
||||
}
|
||||
container.addView(getEditor(), p);
|
||||
}
|
||||
|
||||
private IDEEditor getEditor() {
|
||||
return editor == null ? createEditor() : editor;
|
||||
}
|
||||
|
||||
@@ -20,11 +20,6 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.lsp.api.ILanguageServer;
|
||||
import com.itsaky.lsp.models.DiagnosticItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.github.rosemoe.sora.lang.Language;
|
||||
|
||||
@@ -40,10 +35,6 @@ public abstract class IDELanguage implements Language {
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getTabSize() {
|
||||
return StudioApp.getInstance().getPrefManager().getEditorTabSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence format(CharSequence text) {
|
||||
final var server = getLanguageServer();
|
||||
@@ -54,11 +45,15 @@ public abstract class IDELanguage implements Language {
|
||||
return text;
|
||||
}
|
||||
|
||||
public int getIndentAdvance(@NonNull String line) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected ILanguageServer getLanguageServer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getTabSize() {
|
||||
return StudioApp.getInstance().getPrefManager().getEditorTabSize();
|
||||
}
|
||||
|
||||
public int getIndentAdvance(@NonNull String line) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,9 @@ import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import java.lang.Override;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.github.rosemoe.sora.lang.analysis.AsyncIncrementalAnalyzeManager;
|
||||
import io.github.rosemoe.sora.lang.styling.CodeBlock;
|
||||
import io.github.rosemoe.sora.lang.styling.Span;
|
||||
import io.github.rosemoe.sora.text.Content;
|
||||
|
||||
public class CppAnalyzer extends BaseIncrementalAnalyzeManager {
|
||||
|
||||
@@ -43,8 +39,10 @@ public class CppAnalyzer extends BaseIncrementalAnalyzeManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[] getCodeBlockTokens() {
|
||||
return new int[] {LeftBrace, RightBrace};
|
||||
protected int[][] getMultilineTokenStartEndTypes() {
|
||||
final var start = new int[] {Div, Star};
|
||||
final var end = new int[] {Star, Div};
|
||||
return new int[][] {start, end};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -215,15 +213,13 @@ public class CppAnalyzer extends BaseIncrementalAnalyzeManager {
|
||||
return spans;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[][] getMultilineTokenStartEndTypes() {
|
||||
final var start = new int[] {Div, Star};
|
||||
final var end = new int[] {Star, Div};
|
||||
return new int[][] {start, end};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleIncompleteToken(final IncrementalToken token) {
|
||||
token.type = BlockComment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[] getCodeBlockTokens() {
|
||||
return new int[] {LeftBrace, RightBrace};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,42 +43,13 @@ import io.github.rosemoe.sora.widget.SymbolPairMatch;
|
||||
public class CppLanguage extends IDELanguage {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("CppLanguage");
|
||||
private CppAnalyzer analyzer;
|
||||
private final NewlineHandler[] newlineHandlers = new NewlineHandler[] {new BraceHandler()};
|
||||
private CppAnalyzer analyzer;
|
||||
|
||||
public CppLanguage() {
|
||||
analyzer = new CppAnalyzer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentAdvance(@NonNull String line) {
|
||||
try {
|
||||
CPP14Lexer lexer = new CPP14Lexer(CharStreams.fromReader(new StringReader(line)));
|
||||
Token token;
|
||||
int advance = 0;
|
||||
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
|
||||
switch (token.getType()) {
|
||||
case CPP14Lexer.LeftBrace:
|
||||
advance++;
|
||||
break;
|
||||
case CPP14Lexer.RightBrace:
|
||||
advance--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
advance = Math.max(0, advance);
|
||||
return advance * getTabSize();
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Error calculating indent advance", e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymbolPairMatch getSymbolPairs() {
|
||||
return new CppSymbolPairs();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AnalyzeManager getAnalyzeManager() {
|
||||
@@ -107,8 +78,8 @@ public class CppLanguage extends IDELanguage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence format(CharSequence content) {
|
||||
return content;
|
||||
public SymbolPairMatch getSymbolPairs() {
|
||||
return new CppSymbolPairs();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,6 +92,35 @@ public class CppLanguage extends IDELanguage {
|
||||
analyzer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence format(CharSequence content) {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentAdvance(@NonNull String line) {
|
||||
try {
|
||||
CPP14Lexer lexer = new CPP14Lexer(CharStreams.fromReader(new StringReader(line)));
|
||||
Token token;
|
||||
int advance = 0;
|
||||
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
|
||||
switch (token.getType()) {
|
||||
case CPP14Lexer.LeftBrace:
|
||||
advance++;
|
||||
break;
|
||||
case CPP14Lexer.RightBrace:
|
||||
advance--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
advance = Math.max(0, advance);
|
||||
return advance * getTabSize();
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Error calculating indent advance", e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static class CppSymbolPairs extends SymbolPairMatch {
|
||||
public CppSymbolPairs() {
|
||||
super.putPair('{', new Replacement("{}", 1));
|
||||
|
||||
@@ -23,10 +23,8 @@ import com.itsaky.androidide.language.incremental.LineState
|
||||
import com.itsaky.androidide.lexers.groovy.GroovyLexer
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE
|
||||
import io.github.rosemoe.sora.lang.analysis.IncrementalAnalyzeManager.LineTokenizeResult
|
||||
import io.github.rosemoe.sora.lang.styling.CodeBlock
|
||||
import io.github.rosemoe.sora.lang.styling.Span
|
||||
import io.github.rosemoe.sora.lang.styling.TextStyle
|
||||
import io.github.rosemoe.sora.text.Content
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class GroovyAnalyzer : BaseIncrementalAnalyzeManager(GroovyLexer::class.java) {
|
||||
|
||||
@@ -53,35 +53,6 @@ public class GroovyLanguage extends IDELanguage {
|
||||
completer = new GroovyAutoComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentAdvance(@NonNull String p1) {
|
||||
try {
|
||||
GroovyLexer lexer = new GroovyLexer(CharStreams.fromReader(new StringReader(p1)));
|
||||
Token token = null;
|
||||
int advance = 0;
|
||||
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
|
||||
switch (token.getType()) {
|
||||
case GroovyLexer.LBRACE:
|
||||
advance++;
|
||||
break;
|
||||
case GroovyLexer.RBRACE:
|
||||
advance--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
advance = Math.max(0, advance);
|
||||
return advance * getTabSize();
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Failed to calculate indent advance", e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymbolPairMatch getSymbolPairs() {
|
||||
return new SymbolPairMatch.DefaultSymbolPairs();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AnalyzeManager getAnalyzeManager() {
|
||||
@@ -129,8 +100,8 @@ public class GroovyLanguage extends IDELanguage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence format(CharSequence content) {
|
||||
return content;
|
||||
public SymbolPairMatch getSymbolPairs() {
|
||||
return new SymbolPairMatch.DefaultSymbolPairs();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,6 +112,35 @@ public class GroovyLanguage extends IDELanguage {
|
||||
@Override
|
||||
public void destroy() {}
|
||||
|
||||
@Override
|
||||
public CharSequence format(CharSequence content) {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentAdvance(@NonNull String p1) {
|
||||
try {
|
||||
GroovyLexer lexer = new GroovyLexer(CharStreams.fromReader(new StringReader(p1)));
|
||||
Token token = null;
|
||||
int advance = 0;
|
||||
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
|
||||
switch (token.getType()) {
|
||||
case GroovyLexer.LBRACE:
|
||||
advance++;
|
||||
break;
|
||||
case GroovyLexer.RBRACE:
|
||||
advance--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
advance = Math.max(0, advance);
|
||||
return advance * getTabSize();
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Failed to calculate indent advance", e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
class BraceHandler implements NewlineHandler {
|
||||
|
||||
@Override
|
||||
|
||||
+139
-139
@@ -61,11 +61,11 @@ import kotlin.Pair;
|
||||
public abstract class BaseIncrementalAnalyzeManager
|
||||
extends AsyncIncrementalAnalyzeManager<LineState, IncrementalToken> {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("BaseIncrementalAnalyzeManager");
|
||||
protected final Lexer lexer;
|
||||
private final int[] multilineStartTypes;
|
||||
private final int[] multilineEndTypes;
|
||||
private final int[] blockTokens;
|
||||
private static final ILogger LOG = ILogger.newInstance("BaseIncrementalAnalyzeManager");
|
||||
|
||||
public BaseIncrementalAnalyzeManager(final Class<? extends Lexer> lexer) {
|
||||
Objects.requireNonNull(lexer, "Cannot create analyzer manager for null lexer");
|
||||
@@ -92,6 +92,16 @@ public abstract class BaseIncrementalAnalyzeManager
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected CharStream createStream(@NonNull CharSequence source) {
|
||||
Objects.requireNonNull(source);
|
||||
try {
|
||||
return CharStreams.fromReader(new CharSequenceReader(source));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot create CharStream for source", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyMultilineTypes(@NonNull final int[][] types) {
|
||||
Preconditions.checkState(
|
||||
types.length == 2, "There must be exact two inner int[] in multiline token types");
|
||||
@@ -102,45 +112,6 @@ public abstract class BaseIncrementalAnalyzeManager
|
||||
Preconditions.checkState(end.length > 0, "Invalid end token types");
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
@NonNull
|
||||
private Pair<EvictingQueue<IncrementalToken>, EvictingQueue<IncrementalToken>>
|
||||
createEvictingQueueForTokens() {
|
||||
return new Pair<>(
|
||||
EvictingQueue.create(multilineStartTypes.length),
|
||||
EvictingQueue.create(multilineEndTypes.length));
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
protected boolean isIncompleteTokenStart(EvictingQueue<IncrementalToken> q) {
|
||||
return matchTokenTypes(this.multilineStartTypes, q);
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
protected boolean isIncompleteTokenEnd(EvictingQueue<IncrementalToken> q) {
|
||||
return matchTokenTypes(this.multilineEndTypes, q);
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
private boolean matchTokenTypes(
|
||||
@NonNull int[] types, @NonNull EvictingQueue<IncrementalToken> tokens) {
|
||||
final var arr = tokens.toArray(new IncrementalToken[0]);
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (types[i] != arr[i].getType()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean isCodeBlockStart(IncrementalToken token) {
|
||||
return blockTokens.length == 2 && token.getType() == blockTokens[0];
|
||||
}
|
||||
|
||||
protected boolean isCodeBlockEnd(IncrementalToken token) {
|
||||
return blockTokens.length == 2 && token.getType() == blockTokens[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the token types which start and end a multiline token.
|
||||
*
|
||||
@@ -152,31 +123,6 @@ public abstract class BaseIncrementalAnalyzeManager
|
||||
*/
|
||||
protected abstract int[][] getMultilineTokenStartEndTypes();
|
||||
|
||||
/**
|
||||
* Generate spans for the given {@link LineTokenizeResult}.
|
||||
*
|
||||
* @param tokens The tokenization result.
|
||||
* @return The spans for the tokens.
|
||||
*/
|
||||
protected abstract List<Span> generateSpans(
|
||||
final LineTokenizeResult<LineState, IncrementalToken> tokens);
|
||||
|
||||
/**
|
||||
* Called when the analyzer finds an incomplete token in a lne.
|
||||
*
|
||||
* @param token The incomplete token.
|
||||
*/
|
||||
protected abstract void handleIncompleteToken(IncrementalToken token);
|
||||
|
||||
/**
|
||||
* Gets the next token from lexer and return an {@link IncrementalToken}.
|
||||
*
|
||||
* @return The next {@link IncrementalToken}.
|
||||
*/
|
||||
protected IncrementalToken nextToken() {
|
||||
return new IncrementalToken(lexer.nextToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the token types for left and right braces.
|
||||
*
|
||||
@@ -184,51 +130,6 @@ public abstract class BaseIncrementalAnalyzeManager
|
||||
*/
|
||||
protected abstract int[] getCodeBlockTokens();
|
||||
|
||||
/**
|
||||
* Pop (remove) required number of tokens after an incomplete token has been encountered. <br>
|
||||
*
|
||||
* <p>For example: <br>
|
||||
* When ['/', '*'] tokens are encountered, the '*' token must be removed. In this case, <code>
|
||||
* incompleteToken</code> parameter will point to '/' token.
|
||||
*
|
||||
* <p>By default, this method removes only the last token.
|
||||
*
|
||||
* @param incompleteToken The token which is the start of the incomplete token.
|
||||
* @param tokens The list of tokens from which extra tokens must be removed.
|
||||
*/
|
||||
protected void popTokensAfterIncomplete(
|
||||
@NonNull IncrementalToken incompleteToken, @NonNull List<IncrementalToken> tokens) {
|
||||
tokens.remove(tokens.size() - 1);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected CharStream createStream(@NonNull CharSequence source) {
|
||||
Objects.requireNonNull(source);
|
||||
try {
|
||||
return CharStreams.fromReader(new CharSequenceReader(source));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot create CharStream for source", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleLineCommentSpan(
|
||||
@NonNull IncrementalToken token, @NonNull List<Span> spans, int offset) {
|
||||
var commentType = SchemeAndroidIDE.COMMENT;
|
||||
|
||||
// highlight special line comments
|
||||
var commentText = token.getText();
|
||||
if (commentText.length() > 2) {
|
||||
commentText = commentText.substring(2);
|
||||
commentText = commentText.trim();
|
||||
if ("todo".equalsIgnoreCase(commentText.substring(0, 4))) {
|
||||
commentType = TODO_COMMENT;
|
||||
} else if ("fixme".equalsIgnoreCase(commentText.substring(0, 5))) {
|
||||
commentType = FIXME_COMMENT;
|
||||
}
|
||||
}
|
||||
spans.add(Span.obtain(offset, withoutCompletion(commentType)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LineState getInitialState() {
|
||||
return new LineState();
|
||||
@@ -239,6 +140,52 @@ public abstract class BaseIncrementalAnalyzeManager
|
||||
return state.equals(another);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LineTokenizeResult<LineState, IncrementalToken> tokenizeLine(
|
||||
final CharSequence line, final LineState state) {
|
||||
final var tokens = new ArrayList<IncrementalToken>();
|
||||
var newState = 0;
|
||||
var stateObj = new LineState();
|
||||
if (state.state == NORMAL) {
|
||||
newState = tokenizeNormal(line, 0, tokens, stateObj, state.lexerMode);
|
||||
} else if (state.state == INCOMPLETE) {
|
||||
final var result = fillIncomplete(line, tokens, state.lexerMode);
|
||||
newState = IntPair.getFirst(result);
|
||||
if (newState == NORMAL) {
|
||||
newState =
|
||||
tokenizeNormal(line, IntPair.getSecond(result), tokens, stateObj, state.lexerMode);
|
||||
} else {
|
||||
newState = INCOMPLETE;
|
||||
}
|
||||
}
|
||||
stateObj.state = newState;
|
||||
stateObj.lexerMode = lexer._mode;
|
||||
return new LineTokenizeResult<>(stateObj, tokens);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Span> generateSpansForLine(
|
||||
final LineTokenizeResult<LineState, IncrementalToken> tokens) {
|
||||
var result = generateSpans(tokens);
|
||||
|
||||
Objects.requireNonNull(result);
|
||||
|
||||
if (result.isEmpty()) {
|
||||
result.add(Span.obtain(0, TextStyle.makeStyle(SchemeAndroidIDE.TEXT_NORMAL)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate spans for the given {@link LineTokenizeResult}.
|
||||
*
|
||||
* @param tokens The tokenization result.
|
||||
* @return The spans for the tokens.
|
||||
*/
|
||||
protected abstract List<Span> generateSpans(
|
||||
final LineTokenizeResult<LineState, IncrementalToken> tokens);
|
||||
|
||||
@Override
|
||||
public List<CodeBlock> computeBlocks(
|
||||
final Content text,
|
||||
@@ -292,27 +239,73 @@ public abstract class BaseIncrementalAnalyzeManager
|
||||
return blocks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LineTokenizeResult<LineState, IncrementalToken> tokenizeLine(
|
||||
final CharSequence line, final LineState state) {
|
||||
final var tokens = new ArrayList<IncrementalToken>();
|
||||
var newState = 0;
|
||||
var stateObj = new LineState();
|
||||
if (state.state == NORMAL) {
|
||||
newState = tokenizeNormal(line, 0, tokens, stateObj, state.lexerMode);
|
||||
} else if (state.state == INCOMPLETE) {
|
||||
final var result = fillIncomplete(line, tokens, state.lexerMode);
|
||||
newState = IntPair.getFirst(result);
|
||||
if (newState == NORMAL) {
|
||||
newState =
|
||||
tokenizeNormal(line, IntPair.getSecond(result), tokens, stateObj, state.lexerMode);
|
||||
} else {
|
||||
newState = INCOMPLETE;
|
||||
protected boolean isCodeBlockStart(IncrementalToken token) {
|
||||
return blockTokens.length == 2 && token.getType() == blockTokens[0];
|
||||
}
|
||||
|
||||
protected boolean isCodeBlockEnd(IncrementalToken token) {
|
||||
return blockTokens.length == 2 && token.getType() == blockTokens[1];
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
protected boolean isIncompleteTokenStart(EvictingQueue<IncrementalToken> q) {
|
||||
return matchTokenTypes(this.multilineStartTypes, q);
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
protected boolean isIncompleteTokenEnd(EvictingQueue<IncrementalToken> q) {
|
||||
return matchTokenTypes(this.multilineEndTypes, q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the analyzer finds an incomplete token in a lne.
|
||||
*
|
||||
* @param token The incomplete token.
|
||||
*/
|
||||
protected abstract void handleIncompleteToken(IncrementalToken token);
|
||||
|
||||
/**
|
||||
* Gets the next token from lexer and return an {@link IncrementalToken}.
|
||||
*
|
||||
* @return The next {@link IncrementalToken}.
|
||||
*/
|
||||
protected IncrementalToken nextToken() {
|
||||
return new IncrementalToken(lexer.nextToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop (remove) required number of tokens after an incomplete token has been encountered. <br>
|
||||
*
|
||||
* <p>For example: <br>
|
||||
* When ['/', '*'] tokens are encountered, the '*' token must be removed. In this case, <code>
|
||||
* incompleteToken</code> parameter will point to '/' token.
|
||||
*
|
||||
* <p>By default, this method removes only the last token.
|
||||
*
|
||||
* @param incompleteToken The token which is the start of the incomplete token.
|
||||
* @param tokens The list of tokens from which extra tokens must be removed.
|
||||
*/
|
||||
protected void popTokensAfterIncomplete(
|
||||
@NonNull IncrementalToken incompleteToken, @NonNull List<IncrementalToken> tokens) {
|
||||
tokens.remove(tokens.size() - 1);
|
||||
}
|
||||
|
||||
protected void handleLineCommentSpan(
|
||||
@NonNull IncrementalToken token, @NonNull List<Span> spans, int offset) {
|
||||
var commentType = SchemeAndroidIDE.COMMENT;
|
||||
|
||||
// highlight special line comments
|
||||
var commentText = token.getText();
|
||||
if (commentText.length() > 2) {
|
||||
commentText = commentText.substring(2);
|
||||
commentText = commentText.trim();
|
||||
if ("todo".equalsIgnoreCase(commentText.substring(0, 4))) {
|
||||
commentType = TODO_COMMENT;
|
||||
} else if ("fixme".equalsIgnoreCase(commentText.substring(0, 5))) {
|
||||
commentType = FIXME_COMMENT;
|
||||
}
|
||||
}
|
||||
stateObj.state = newState;
|
||||
stateObj.lexerMode = lexer._mode;
|
||||
return new LineTokenizeResult<>(stateObj, tokens);
|
||||
spans.add(Span.obtain(offset, withoutCompletion(commentType)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -445,17 +438,24 @@ public abstract class BaseIncrementalAnalyzeManager
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Span> generateSpansForLine(
|
||||
final LineTokenizeResult<LineState, IncrementalToken> tokens) {
|
||||
var result = generateSpans(tokens);
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
@NonNull
|
||||
private Pair<EvictingQueue<IncrementalToken>, EvictingQueue<IncrementalToken>>
|
||||
createEvictingQueueForTokens() {
|
||||
return new Pair<>(
|
||||
EvictingQueue.create(multilineStartTypes.length),
|
||||
EvictingQueue.create(multilineEndTypes.length));
|
||||
}
|
||||
|
||||
Objects.requireNonNull(result);
|
||||
|
||||
if (result.isEmpty()) {
|
||||
result.add(Span.obtain(0, TextStyle.makeStyle(SchemeAndroidIDE.TEXT_NORMAL)));
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
private boolean matchTokenTypes(
|
||||
@NonNull int[] types, @NonNull EvictingQueue<IncrementalToken> tokens) {
|
||||
final var arr = tokens.toArray(new IncrementalToken[0]);
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (types[i] != arr[i].getType()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,8 @@ import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.forKeyword
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.forString
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import io.github.rosemoe.sora.lang.analysis.IncrementalAnalyzeManager.LineTokenizeResult
|
||||
import io.github.rosemoe.sora.lang.styling.CodeBlock
|
||||
import io.github.rosemoe.sora.lang.styling.Span
|
||||
import io.github.rosemoe.sora.lang.styling.TextStyle.makeStyle
|
||||
import io.github.rosemoe.sora.text.Content
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class JavaAnalyzer : BaseIncrementalAnalyzeManager(JavaLexer::class.java) {
|
||||
|
||||
@@ -63,36 +63,6 @@ public class JavaLanguage extends IDELanguage {
|
||||
this.newlineHandlers[0] = new BraceHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentAdvance(@NonNull String line) {
|
||||
try {
|
||||
JavaLexer lexer = new JavaLexer(CharStreams.fromReader(new StringReader(line)));
|
||||
Token token;
|
||||
int advance = 0;
|
||||
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
|
||||
switch (token.getType()) {
|
||||
case JavaLexer.LBRACE:
|
||||
advance++;
|
||||
break;
|
||||
case JavaParser.RBRACE:
|
||||
advance--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
advance = Math.max(0, advance);
|
||||
return advance * getTabSize();
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Error calculating indent advance", e);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymbolPairMatch getSymbolPairs() {
|
||||
return new JavaSymbolPairs();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AnalyzeManager getAnalyzeManager() {
|
||||
@@ -127,11 +97,41 @@ public class JavaLanguage extends IDELanguage {
|
||||
return getIndentAdvance(content.getLine(line).substring(0, column));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentAdvance(@NonNull String line) {
|
||||
try {
|
||||
JavaLexer lexer = new JavaLexer(CharStreams.fromReader(new StringReader(line)));
|
||||
Token token;
|
||||
int advance = 0;
|
||||
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
|
||||
switch (token.getType()) {
|
||||
case JavaLexer.LBRACE:
|
||||
advance++;
|
||||
break;
|
||||
case JavaParser.RBRACE:
|
||||
advance--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
advance = Math.max(0, advance);
|
||||
return advance * getTabSize();
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Error calculating indent advance", e);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ILanguageServer getLanguageServer() {
|
||||
return StudioApp.getInstance().getJavaLanguageServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymbolPairMatch getSymbolPairs() {
|
||||
return new JavaSymbolPairs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NewlineHandler[] getNewlineHandlers() {
|
||||
return newlineHandlers;
|
||||
|
||||
@@ -29,10 +29,8 @@ import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.forComment
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.forKeyword
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.forString
|
||||
import io.github.rosemoe.sora.lang.analysis.IncrementalAnalyzeManager.LineTokenizeResult
|
||||
import io.github.rosemoe.sora.lang.styling.CodeBlock
|
||||
import io.github.rosemoe.sora.lang.styling.Span
|
||||
import io.github.rosemoe.sora.lang.styling.TextStyle.makeStyle
|
||||
import io.github.rosemoe.sora.text.Content
|
||||
|
||||
/**
|
||||
* Incremental syntax analyzer for Kotlin.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user