Skip to content

Commit 2d65dbf

Browse files
ADFA-4432 | Add tooltips for the Python/Flask plugin commands (#71)
python-tools now implements DocumentationExtension with tier 1/2/3 docs for its four build actions, and binds the long-press on its own toolbar buttons so the tooltip shows without a host change. Co-authored-by: Daniel Alome <astrocoder007@gmail.com>
1 parent 62b1e4f commit 2d65dbf

5 files changed

Lines changed: 365 additions & 19 deletions

File tree

python-tools/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,21 @@ Two Python project templates:
1313

1414
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.
1515

16+
## In-app help
17+
18+
Long-pressing a Python command in the editor toolbar shows its documentation, shipped with the
19+
plugin and written into the IDE's `documentation.db` at install time. The plugin binds the
20+
long-press on its own toolbar buttons, so no host change is needed:
21+
22+
- **Tier 1** - one-line summary of what the command runs.
23+
- **Tier 2** - "See more" detail: entry-point order, dependency auto-install, timeouts.
24+
- **Tier 3** - `src/main/assets/docs/index.html`, served offline in the IDE's help viewer.
25+
26+
Tooltip tags are `<plugin.id>.<action id>` (for example
27+
`com.appdevforall.python.plugin.python.run.app`) under the category
28+
`plugin_com.appdevforall.python.plugin`, which is what the IDE derives when it resolves a tooltip
29+
for a plugin-contributed action.
30+
1631
## Disclaimer
1732

1833
Only light testing has been done; use at your own risk. Customer support cannot provide help with this plugin.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/* Tier 3 walkthrough styles - kept tiny so the doc loads instantly even
2+
* on slow devices. Dark-mode friendly via CSS color-scheme + media
3+
* query. No external resources / fonts.
4+
*/
5+
6+
:root {
7+
color-scheme: light dark;
8+
--bg: #ffffff;
9+
--fg: #1b1b1f;
10+
--muted: #5e5e6c;
11+
--accent: #485d92;
12+
--code-bg: #f5f6fa;
13+
--code-fg: #1b1b1f;
14+
--border: #e3e3ea;
15+
}
16+
@media (prefers-color-scheme: dark) {
17+
:root {
18+
--bg: #121215;
19+
--fg: #e6e1e5;
20+
--muted: #b8b8c0;
21+
--accent: #b1c5ff;
22+
--code-bg: #1a1a1f;
23+
--code-fg: #e6e1e5;
24+
--border: #303038;
25+
}
26+
}
27+
28+
* { box-sizing: border-box; }
29+
html, body { margin: 0; padding: 0; }
30+
body {
31+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
32+
background: var(--bg);
33+
color: var(--fg);
34+
line-height: 1.55;
35+
padding: 24px 20px 64px;
36+
max-width: 760px;
37+
margin: 0 auto;
38+
}
39+
h1 { font-size: 1.6rem; margin: 0 0 4px; }
40+
h2 { font-size: 1.2rem; margin: 32px 0 8px; color: var(--accent); }
41+
h3 { font-size: 1.05rem; margin: 24px 0 6px; }
42+
p, li { font-size: 0.95rem; }
43+
.lede { color: var(--muted); margin: 0 0 24px; }
44+
a { color: var(--accent); }
45+
ul, ol { padding-left: 20px; }
46+
hr { border: 0; border-top: 1px solid var(--border); margin: 24px 0; }
47+
48+
pre {
49+
background: var(--code-bg);
50+
color: var(--code-fg);
51+
padding: 12px 14px;
52+
overflow-x: auto;
53+
border-radius: 6px;
54+
border: 1px solid var(--border);
55+
font-size: 0.85rem;
56+
line-height: 1.45;
57+
font-family: ui-monospace, SFMono-Regular, "JetBrains Mono", Menlo, monospace;
58+
}
59+
code { font-family: ui-monospace, SFMono-Regular, "JetBrains Mono", Menlo, monospace; font-size: 0.9em; }
60+
.callout {
61+
background: rgba(72, 93, 146, 0.08);
62+
border-left: 3px solid var(--accent);
63+
padding: 10px 14px;
64+
margin: 16px 0;
65+
border-radius: 4px;
66+
}
67+
.muted { color: var(--muted); font-size: 0.85rem; }
68+
table { border-collapse: collapse; width: 100%; margin: 12px 0; }
69+
th, td { border-bottom: 1px solid var(--border); padding: 8px 10px; text-align: left; font-size: 0.9rem; }
70+
th { color: var(--muted); font-weight: 600; }
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Python Tools - Commands</title>
7+
<link rel="stylesheet" href="css/walkthrough.css">
8+
</head>
9+
<body>
10+
11+
<h1>Python Tools</h1>
12+
<p class="lede">Run Python and Flask projects on the device, straight from the editor toolbar.</p>
13+
14+
<p>Python Tools adds two project templates and four toolbar commands. The commands appear only
15+
while a Python project is open; in a Java, Kotlin, or Android project the toolbar is left alone.
16+
Because Gradle plays no part in a Python project, the Gradle actions (Quick Run, Sync, Debug, Run
17+
Tasks, Launch App) are hidden while you work in one.</p>
18+
19+
<div class="callout">
20+
<p>A project counts as Python when its root holds a Python entry point (<code>app.py</code>,
21+
<code>main.py</code>, <code>manage.py</code>, <code>__main__.py</code>, or <code>wsgi.py</code>), a
22+
<code>requirements.txt</code>, or any <code>.py</code> file.</p>
23+
</div>
24+
25+
<h2>Getting Python</h2>
26+
<p>The first time the plugin activates it checks for a Python interpreter and, when none is found,
27+
installs one with the bundled Termux package manager (<code>pkg install python</code>). That
28+
download runs in the background and reports its result as a message; give it a few minutes on a
29+
fresh device before running anything.</p>
30+
31+
<h2>The commands</h2>
32+
<table>
33+
<tr><th>Command</th><th>What it runs</th><th>Stopped after</th></tr>
34+
<tr><td><a href="#run-app">Run app</a></td><td>the project entry point</td><td>30 minutes</td></tr>
35+
<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>
36+
<tr><td><a href="#install-requirements">Install requirements</a></td><td><code>pip install -r requirements.txt</code></td><td>5 minutes</td></tr>
37+
<tr><td><a href="#run-tests">Run tests</a></td><td><code>python -m pytest -q</code></td><td>10 minutes</td></tr>
38+
</table>
39+
<p>Every command streams its output into the <b>Build Output</b> panel at the bottom of the editor,
40+
and the panel opens itself when a command starts. While a command is running its toolbar button
41+
turns into a Cancel button: tap it to stop the process.</p>
42+
43+
<h3 id="run-app">Run app</h3>
44+
<p>Looks for an entry point in the project root and runs the first one it finds:</p>
45+
<ol>
46+
<li><code>app.py</code> - a Flask app</li>
47+
<li><code>main.py</code> - a plain Python project</li>
48+
<li><code>manage.py</code> - started with <code>runserver</code></li>
49+
<li><code>__main__.py</code></li>
50+
<li><code>wsgi.py</code></li>
51+
</ol>
52+
<p>When none of those exist the command says so and stops, rather than guessing.</p>
53+
<p>Output is unbuffered, so <code>print()</code> lines and a Flask request log appear as they
54+
happen instead of arriving in a block at the end. A Flask app keeps running until you cancel it;
55+
open the port it prints (<code>5000</code> by default) in a browser on the device to use the app.</p>
56+
<p>If the run fails because a module is missing and the project has a <code>requirements.txt</code>,
57+
the plugin installs the dependencies for you, then asks you to tap Run again.</p>
58+
59+
<h3 id="run-current-file">Run current file</h3>
60+
<p>Appears only while a <code>.py</code> file is open, and runs exactly that file by its full path.
61+
Use it for a script that is not the project entry point - a data fixture, a one-off check, a
62+
scratch file.</p>
63+
<p>Missing dependencies are installed from <code>requirements.txt</code> the same way as Run app.</p>
64+
65+
<h3 id="install-requirements">Install requirements</h3>
66+
<p>Runs <code>pip install -r requirements.txt</code> in the project root and streams pip's output
67+
into Build Output.</p>
68+
<p>The same install runs on its own when a run fails because a module is missing, so reach for this
69+
button after you edit <code>requirements.txt</code> yourself. When the file is absent, a failed run
70+
says so instead of installing anything.</p>
71+
72+
<h3 id="run-tests">Run tests</h3>
73+
<p>Runs <code>python -m pytest -q</code> in the project root. When pytest is not installed yet it
74+
is fetched with pip before the tests start, so the first run takes longer than later ones.</p>
75+
76+
<h2>The project templates</h2>
77+
<p>Both templates appear on the New Project screen beside the built-in ones:</p>
78+
<ul>
79+
<li><b>Python Flask App</b> - routes, HTML templates, static CSS, a config file, and a 404 page.
80+
The port is a template parameter and defaults to <code>5000</code>.</li>
81+
<li><b>Python Starter</b> - a single <code>main.py</code> entry point, a
82+
<code>requirements.txt</code>, and a <code>.gitignore</code>.</li>
83+
</ul>
84+
85+
<h2>Stopping a runaway process</h2>
86+
<p>A Python process started by the plugin is tied to the IDE: if the IDE is killed, the child
87+
process is killed with it, so a forgotten Flask server cannot keep holding a port after the IDE is
88+
gone. Each command also has its own time limit, listed in the table above.</p>
89+
90+
<h2>Turning it off</h2>
91+
<p>Disable Python Tools in the Plugin Manager. The Python commands disappear, the Gradle actions
92+
come back for every project, and the two templates are removed from the New Project screen. Any
93+
Python packages already installed on the device stay where they are.</p>
94+
95+
</body>
96+
</html>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.appdevforall.python.plugin
2+
3+
import com.itsaky.androidide.plugins.extensions.PluginTooltipButton
4+
import com.itsaky.androidide.plugins.extensions.PluginTooltipEntry
5+
6+
internal object PythonToolsDocumentation {
7+
8+
const val CATEGORY = "plugin_${PythonToolsPlugin.PLUGIN_ID}"
9+
const val DOCS_ASSET_PATH = "docs"
10+
11+
fun entries(): List<PluginTooltipEntry> = listOf(
12+
PluginTooltipEntry(
13+
tag = tagFor(PythonToolsPlugin.ACTION_RUN_APP),
14+
summary = "Runs the project. For a plain Python project <code>main.py</code> runs; " +
15+
"for a Flask project <code>app.py</code> runs.",
16+
detail = """
17+
<p>Looks for an entry point in the project root and runs the first one it finds:</p>
18+
<ol>
19+
<li><code>app.py</code> - a Flask app</li>
20+
<li><code>main.py</code> - a plain Python project</li>
21+
<li><code>manage.py</code> - started with <code>runserver</code></li>
22+
<li><code>__main__.py</code></li>
23+
<li><code>wsgi.py</code></li>
24+
</ol>
25+
<p>Output streams unbuffered into <b>Build Output</b>. While the process is alive the
26+
toolbar button becomes <b>Cancel Run app</b>; tap it to stop the process.</p>
27+
<p>If the run fails because a module is missing and the project has a
28+
<code>requirements.txt</code>, the dependencies are installed for you and you are asked
29+
to tap Run again. A run is stopped after 30 minutes.</p>
30+
""".trimIndent(),
31+
buttons = buttons("run-app"),
32+
),
33+
PluginTooltipEntry(
34+
tag = tagFor(PythonToolsPlugin.ACTION_RUN_CURRENT_FILE),
35+
summary = "Runs the Python file open in the editor, leaving the project entry point alone.",
36+
detail = """
37+
<p>Appears only while a <code>.py</code> file is open, and runs exactly that file by its
38+
full path - useful for a script that is not the project entry point.</p>
39+
<p>Output streams into <b>Build Output</b> and the button becomes
40+
<b>Cancel Run current file</b> while the file runs. Missing dependencies are installed
41+
from <code>requirements.txt</code> just as they are for Run app. A run is stopped after
42+
30 minutes.</p>
43+
""".trimIndent(),
44+
buttons = buttons("run-current-file"),
45+
),
46+
PluginTooltipEntry(
47+
tag = tagFor(PythonToolsPlugin.ACTION_SYNC_DEPS),
48+
summary = "Downloads and installs dependencies from requirements.txt (if present).",
49+
detail = """
50+
<p>Runs <code>pip install -r requirements.txt</code> in the project root and streams
51+
pip's output into <b>Build Output</b>.</p>
52+
<p>The same install runs on its own when a run fails because a module is missing, so
53+
reach for this button after you edit <code>requirements.txt</code> yourself. The install
54+
is stopped after 5 minutes.</p>
55+
""".trimIndent(),
56+
buttons = buttons("install-requirements"),
57+
),
58+
PluginTooltipEntry(
59+
tag = tagFor(PythonToolsPlugin.ACTION_TEST),
60+
summary = "Runs the test suite with pytest, installing pytest first if it is missing.",
61+
detail = """
62+
<p>Runs <code>python -m pytest -q</code> in the project root. When pytest is not
63+
installed yet it is fetched with pip before the tests start.</p>
64+
<p>Results stream into <b>Build Output</b>. The run is stopped after 10 minutes.</p>
65+
""".trimIndent(),
66+
buttons = buttons("run-tests"),
67+
),
68+
)
69+
70+
fun tagFor(actionId: String): String = "${PythonToolsPlugin.PLUGIN_ID}.$actionId"
71+
72+
private fun buttons(anchor: String): List<PluginTooltipButton> = listOf(
73+
PluginTooltipButton(
74+
description = "How this command works",
75+
uri = "index.html#$anchor",
76+
order = 0,
77+
),
78+
PluginTooltipButton(
79+
description = "About Code On The Go plugins",
80+
uri = "i/plugins-adfa.html",
81+
order = 1,
82+
directPath = true,
83+
),
84+
)
85+
}

0 commit comments

Comments
 (0)