Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions python-tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@ Two Python project templates:

It also installs Python on-device (via Termux) the first time it is needed, and provides built-in run, install-dependencies, and test actions for Python projects.

## In-app help

Long-pressing a Python command in the editor toolbar shows its documentation, shipped with the
plugin and written into the IDE's `documentation.db` at install time. The plugin binds the
long-press on its own toolbar buttons, so no host change is needed:

- **Tier 1** - one-line summary of what the command runs.
- **Tier 2** - "See more" detail: entry-point order, dependency auto-install, timeouts.
- **Tier 3** - `src/main/assets/docs/index.html`, served offline in the IDE's help viewer.

Tooltip tags are `<plugin.id>.<action id>` (for example
`com.appdevforall.python.plugin.python.run.app`) under the category
`plugin_com.appdevforall.python.plugin`, which is what the IDE derives when it resolves a tooltip
for a plugin-contributed action.

## Disclaimer

Only light testing has been done; use at your own risk. Customer support cannot provide help with this plugin.
Expand Down
70 changes: 70 additions & 0 deletions python-tools/src/main/assets/docs/css/walkthrough.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* Tier 3 walkthrough styles - kept tiny so the doc loads instantly even
* on slow devices. Dark-mode friendly via CSS color-scheme + media
* query. No external resources / fonts.
*/

:root {
color-scheme: light dark;
--bg: #ffffff;
--fg: #1b1b1f;
--muted: #5e5e6c;
--accent: #485d92;
--code-bg: #f5f6fa;
--code-fg: #1b1b1f;
--border: #e3e3ea;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #121215;
--fg: #e6e1e5;
--muted: #b8b8c0;
--accent: #b1c5ff;
--code-bg: #1a1a1f;
--code-fg: #e6e1e5;
--border: #303038;
}
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: var(--bg);
color: var(--fg);
line-height: 1.55;
padding: 24px 20px 64px;
max-width: 760px;
margin: 0 auto;
}
h1 { font-size: 1.6rem; margin: 0 0 4px; }
h2 { font-size: 1.2rem; margin: 32px 0 8px; color: var(--accent); }
h3 { font-size: 1.05rem; margin: 24px 0 6px; }
p, li { font-size: 0.95rem; }
.lede { color: var(--muted); margin: 0 0 24px; }
a { color: var(--accent); }
ul, ol { padding-left: 20px; }
hr { border: 0; border-top: 1px solid var(--border); margin: 24px 0; }

pre {
background: var(--code-bg);
color: var(--code-fg);
padding: 12px 14px;
overflow-x: auto;
border-radius: 6px;
border: 1px solid var(--border);
font-size: 0.85rem;
line-height: 1.45;
font-family: ui-monospace, SFMono-Regular, "JetBrains Mono", Menlo, monospace;
}
code { font-family: ui-monospace, SFMono-Regular, "JetBrains Mono", Menlo, monospace; font-size: 0.9em; }
.callout {
background: rgba(72, 93, 146, 0.08);
border-left: 3px solid var(--accent);
padding: 10px 14px;
margin: 16px 0;
border-radius: 4px;
}
.muted { color: var(--muted); font-size: 0.85rem; }
table { border-collapse: collapse; width: 100%; margin: 12px 0; }
th, td { border-bottom: 1px solid var(--border); padding: 8px 10px; text-align: left; font-size: 0.9rem; }
th { color: var(--muted); font-weight: 600; }
96 changes: 96 additions & 0 deletions python-tools/src/main/assets/docs/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Python Tools - Commands</title>
<link rel="stylesheet" href="css/walkthrough.css">
</head>
<body>

<h1>Python Tools</h1>
<p class="lede">Run Python and Flask projects on the device, straight from the editor toolbar.</p>

<p>Python Tools adds two project templates and four toolbar commands. The commands appear only
while a Python project is open; in a Java, Kotlin, or Android project the toolbar is left alone.
Because Gradle plays no part in a Python project, the Gradle actions (Quick Run, Sync, Debug, Run
Tasks, Launch App) are hidden while you work in one.</p>

<div class="callout">
<p>A project counts as Python when its root holds a Python entry point (<code>app.py</code>,
<code>main.py</code>, <code>manage.py</code>, <code>__main__.py</code>, or <code>wsgi.py</code>), a
<code>requirements.txt</code>, or any <code>.py</code> file.</p>
</div>

<h2>Getting Python</h2>
<p>The first time the plugin activates it checks for a Python interpreter and, when none is found,
installs one with the bundled Termux package manager (<code>pkg install python</code>). That
download runs in the background and reports its result as a message; give it a few minutes on a
fresh device before running anything.</p>

<h2>The commands</h2>
<table>
<tr><th>Command</th><th>What it runs</th><th>Stopped after</th></tr>
<tr><td><a href="#run-app">Run app</a></td><td>the project entry point</td><td>30 minutes</td></tr>
<tr><td><a href="#run-current-file">Run current file</a></td><td>the open <code>.py</code> file</td><td>30 minutes</td></tr>
<tr><td><a href="#install-requirements">Install requirements</a></td><td><code>pip install -r requirements.txt</code></td><td>5 minutes</td></tr>
<tr><td><a href="#run-tests">Run tests</a></td><td><code>python -m pytest -q</code></td><td>10 minutes</td></tr>
</table>
<p>Every command streams its output into the <b>Build Output</b> panel at the bottom of the editor,
and the panel opens itself when a command starts. While a command is running its toolbar button
turns into a Cancel button: tap it to stop the process.</p>

<h3 id="run-app">Run app</h3>
<p>Looks for an entry point in the project root and runs the first one it finds:</p>
<ol>
<li><code>app.py</code> - a Flask app</li>
<li><code>main.py</code> - a plain Python project</li>
<li><code>manage.py</code> - started with <code>runserver</code></li>
<li><code>__main__.py</code></li>
<li><code>wsgi.py</code></li>
</ol>
<p>When none of those exist the command says so and stops, rather than guessing.</p>
<p>Output is unbuffered, so <code>print()</code> lines and a Flask request log appear as they
happen instead of arriving in a block at the end. A Flask app keeps running until you cancel it;
open the port it prints (<code>5000</code> by default) in a browser on the device to use the app.</p>
<p>If the run fails because a module is missing and the project has a <code>requirements.txt</code>,
the plugin installs the dependencies for you, then asks you to tap Run again.</p>

<h3 id="run-current-file">Run current file</h3>
<p>Appears only while a <code>.py</code> file is open, and runs exactly that file by its full path.
Use it for a script that is not the project entry point - a data fixture, a one-off check, a
scratch file.</p>
<p>Missing dependencies are installed from <code>requirements.txt</code> the same way as Run app.</p>

<h3 id="install-requirements">Install requirements</h3>
<p>Runs <code>pip install -r requirements.txt</code> in the project root and streams pip's output
into Build Output.</p>
<p>The same install runs on its own when a run fails because a module is missing, so reach for this
button after you edit <code>requirements.txt</code> yourself. When the file is absent, a failed run
says so instead of installing anything.</p>

<h3 id="run-tests">Run tests</h3>
<p>Runs <code>python -m pytest -q</code> in the project root. When pytest is not installed yet it
is fetched with pip before the tests start, so the first run takes longer than later ones.</p>

<h2>The project templates</h2>
<p>Both templates appear on the New Project screen beside the built-in ones:</p>
<ul>
<li><b>Python Flask App</b> - routes, HTML templates, static CSS, a config file, and a 404 page.
The port is a template parameter and defaults to <code>5000</code>.</li>
<li><b>Python Starter</b> - a single <code>main.py</code> entry point, a
<code>requirements.txt</code>, and a <code>.gitignore</code>.</li>
</ul>

<h2>Stopping a runaway process</h2>
<p>A Python process started by the plugin is tied to the IDE: if the IDE is killed, the child
process is killed with it, so a forgotten Flask server cannot keep holding a port after the IDE is
gone. Each command also has its own time limit, listed in the table above.</p>

<h2>Turning it off</h2>
<p>Disable Python Tools in the Plugin Manager. The Python commands disappear, the Gradle actions
come back for every project, and the two templates are removed from the New Project screen. Any
Python packages already installed on the device stay where they are.</p>

</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.appdevforall.python.plugin

import com.itsaky.androidide.plugins.extensions.PluginTooltipButton
import com.itsaky.androidide.plugins.extensions.PluginTooltipEntry

internal object PythonToolsDocumentation {

const val CATEGORY = "plugin_${PythonToolsPlugin.PLUGIN_ID}"
const val DOCS_ASSET_PATH = "docs"

fun entries(): List<PluginTooltipEntry> = listOf(
PluginTooltipEntry(
tag = tagFor(PythonToolsPlugin.ACTION_RUN_APP),
summary = "Runs the project. For a plain Python project <code>main.py</code> runs; " +
"for a Flask project <code>app.py</code> runs.",
detail = """
<p>Looks for an entry point in the project root and runs the first one it finds:</p>
<ol>
<li><code>app.py</code> - a Flask app</li>
<li><code>main.py</code> - a plain Python project</li>
<li><code>manage.py</code> - started with <code>runserver</code></li>
<li><code>__main__.py</code></li>
<li><code>wsgi.py</code></li>
</ol>
<p>Output streams unbuffered into <b>Build Output</b>. While the process is alive the
toolbar button becomes <b>Cancel Run app</b>; tap it to stop the process.</p>
<p>If the run fails because a module is missing and the project has a
<code>requirements.txt</code>, the dependencies are installed for you and you are asked
to tap Run again. A run is stopped after 30 minutes.</p>
""".trimIndent(),
buttons = buttons("run-app"),
),
PluginTooltipEntry(
tag = tagFor(PythonToolsPlugin.ACTION_RUN_CURRENT_FILE),
summary = "Runs the Python file open in the editor, leaving the project entry point alone.",
detail = """
<p>Appears only while a <code>.py</code> file is open, and runs exactly that file by its
full path - useful for a script that is not the project entry point.</p>
<p>Output streams into <b>Build Output</b> and the button becomes
<b>Cancel Run current file</b> while the file runs. Missing dependencies are installed
from <code>requirements.txt</code> just as they are for Run app. A run is stopped after
30 minutes.</p>
""".trimIndent(),
buttons = buttons("run-current-file"),
),
PluginTooltipEntry(
tag = tagFor(PythonToolsPlugin.ACTION_SYNC_DEPS),
summary = "Downloads and installs dependencies from requirements.txt (if present).",
detail = """
<p>Runs <code>pip install -r requirements.txt</code> in the project root and streams
pip's output into <b>Build Output</b>.</p>
<p>The same install runs on its own when a run fails because a module is missing, so
reach for this button after you edit <code>requirements.txt</code> yourself. The install
is stopped after 5 minutes.</p>
""".trimIndent(),
buttons = buttons("install-requirements"),
),
PluginTooltipEntry(
tag = tagFor(PythonToolsPlugin.ACTION_TEST),
summary = "Runs the test suite with pytest, installing pytest first if it is missing.",
detail = """
<p>Runs <code>python -m pytest -q</code> in the project root. When pytest is not
installed yet it is fetched with pip before the tests start.</p>
<p>Results stream into <b>Build Output</b>. The run is stopped after 10 minutes.</p>
""".trimIndent(),
buttons = buttons("run-tests"),
),
)

fun tagFor(actionId: String): String = "${PythonToolsPlugin.PLUGIN_ID}.$actionId"

private fun buttons(anchor: String): List<PluginTooltipButton> = listOf(
PluginTooltipButton(
description = "How this command works",
uri = "index.html#$anchor",
order = 0,
),
PluginTooltipButton(
description = "About Code On The Go plugins",
uri = "i/plugins-adfa.html",
order = 1,
directPath = true,
),
)
}
Loading
Loading