picoride 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.claude/launch.json +11 -0
- data/CHANGELOG.md +5 -0
- data/CLAUDE.md +1518 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/Dockerfile +76 -0
- data/LICENSE.txt +21 -0
- data/README.md +76 -0
- data/Rakefile +12 -0
- data/docker/esp32/Dockerfile +82 -0
- data/docker/pico/Dockerfile +77 -0
- data/exe/picoride +16 -0
- data/lib/picoride/background_job.rb +71 -0
- data/lib/picoride/project_store.rb +133 -0
- data/lib/picoride/r2p2_state.rb +137 -0
- data/lib/picoride/server.rb +1156 -0
- data/lib/picoride/version.rb +5 -0
- data/lib/picoride.rb +11 -0
- data/sig/picoruby/esp32/ide.rbs +8 -0
- data/web/funicular/README.md +111 -0
- data/web/funicular/ruby/components/directory_browser_dialog.rb +212 -0
- data/web/funicular/ruby/components/editor_app.rb +1560 -0
- data/web/funicular/ruby/components/log_panel.rb +102 -0
- data/web/funicular/ruby/components/menu_bar.rb +266 -0
- data/web/funicular/ruby/components/project_settings_dialog.rb +203 -0
- data/web/funicular/ruby/components/prompt_dialog.rb +76 -0
- data/web/funicular/ruby/components/toolbar.rb +30 -0
- data/web/funicular/ruby/components/tree_view.rb +112 -0
- data/web/funicular/ruby/main.rb +3 -0
- data/web/public/css/style.css +886 -0
- data/web/public/js/editor.js +257 -0
- data/web/views/index.erb +94 -0
- metadata +132 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// Prism.js + textarea オーバーレイ方式のシンプルなコードエディタ。
|
|
2
|
+
// picoruby.org/terminal の File Editor (PicoModem) と同じ構造:
|
|
3
|
+
// textarea(透明・キャレットのみ表示) の下に、Prismでハイライトした <pre><code> を重ねて表示する。
|
|
4
|
+
// ビルドステップ不要(Prism.jsは <script> タグ読み込みでグローバルに window.Prism を公開する)。
|
|
5
|
+
|
|
6
|
+
(function () {
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const fileListEl = document.getElementById("file-list");
|
|
10
|
+
const currentFileEl = document.getElementById("current-file");
|
|
11
|
+
const saveBtn = document.getElementById("save-btn");
|
|
12
|
+
const statusEl = document.getElementById("status");
|
|
13
|
+
|
|
14
|
+
const textarea = document.getElementById("editor");
|
|
15
|
+
const highlightContent = document.getElementById("highlight-content");
|
|
16
|
+
const lineNumbersInner = document.getElementById("line-numbers-inner");
|
|
17
|
+
const scrollArea = document.querySelector(".editor-scroll-area");
|
|
18
|
+
const lineNumbers = document.getElementById("line-numbers");
|
|
19
|
+
|
|
20
|
+
let currentPath = null;
|
|
21
|
+
let savedContent = "";
|
|
22
|
+
let currentLanguage = null; // Prism.languages.xxx
|
|
23
|
+
let currentLanguageName = null; // "ruby" | "c"
|
|
24
|
+
|
|
25
|
+
// 拡張子 -> Prism言語のマッピング
|
|
26
|
+
function languageFor(path) {
|
|
27
|
+
const ext = path.split(".").pop().toLowerCase();
|
|
28
|
+
switch (ext) {
|
|
29
|
+
case "rb":
|
|
30
|
+
return { grammar: Prism.languages.ruby, name: "ruby" };
|
|
31
|
+
case "c":
|
|
32
|
+
case "h":
|
|
33
|
+
return { grammar: Prism.languages.c, name: "c" };
|
|
34
|
+
default:
|
|
35
|
+
return { grammar: Prism.languages.plain || {}, name: "none" };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function iconFor(path) {
|
|
40
|
+
return path.split(".").pop().toLowerCase();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function setStatus(message, kind) {
|
|
44
|
+
statusEl.textContent = message;
|
|
45
|
+
statusEl.className = "status" + (kind ? " " + kind : "");
|
|
46
|
+
if (message) {
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
if (statusEl.textContent === message) {
|
|
49
|
+
statusEl.textContent = "";
|
|
50
|
+
statusEl.className = "status";
|
|
51
|
+
}
|
|
52
|
+
}, 2500);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---- ハイライト & 行番号の再描画 ----
|
|
57
|
+
|
|
58
|
+
function escapeHtml(text) {
|
|
59
|
+
return text
|
|
60
|
+
.replace(/&/g, "&")
|
|
61
|
+
.replace(/</g, "<")
|
|
62
|
+
.replace(/>/g, ">");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function renderHighlight() {
|
|
66
|
+
const code = textarea.value;
|
|
67
|
+
|
|
68
|
+
if (currentLanguage && currentLanguageName !== "none") {
|
|
69
|
+
highlightContent.innerHTML = Prism.highlight(code, currentLanguage, currentLanguageName);
|
|
70
|
+
} else {
|
|
71
|
+
highlightContent.innerHTML = escapeHtml(code);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// textareaの末尾に改行がある場合、<pre>の高さがずれないよう調整用の改行を足す
|
|
75
|
+
if (code.endsWith("\n")) {
|
|
76
|
+
highlightContent.innerHTML += "\n";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
renderLineNumbers(code);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function renderLineNumbers(code) {
|
|
83
|
+
const lineCount = code.length === 0 ? 1 : code.split("\n").length;
|
|
84
|
+
const currentCount = lineNumbersInner.childElementCount;
|
|
85
|
+
|
|
86
|
+
if (currentCount < lineCount) {
|
|
87
|
+
const fragment = document.createDocumentFragment();
|
|
88
|
+
for (let i = currentCount + 1; i <= lineCount; i++) {
|
|
89
|
+
const div = document.createElement("div");
|
|
90
|
+
div.textContent = String(i);
|
|
91
|
+
fragment.appendChild(div);
|
|
92
|
+
}
|
|
93
|
+
lineNumbersInner.appendChild(fragment);
|
|
94
|
+
} else if (currentCount > lineCount) {
|
|
95
|
+
for (let i = currentCount; i > lineCount; i--) {
|
|
96
|
+
lineNumbersInner.removeChild(lineNumbersInner.lastChild);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function syncScroll() {
|
|
102
|
+
// highlight-layer(<pre>)は#editor(<textarea>)と同じグリッドセルに重なっており、
|
|
103
|
+
// どちらも .editor-scroll-area のスクロールに自動で追従するため、JSでの同期は不要。
|
|
104
|
+
// 行番号だけは独立したカラムなので、ここでスクロール位置を合わせる。
|
|
105
|
+
lineNumbers.scrollTop = scrollArea.scrollTop;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function updateDirtyState() {
|
|
109
|
+
const dirty = textarea.value !== savedContent;
|
|
110
|
+
saveBtn.disabled = !currentPath || !dirty;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
textarea.addEventListener("input", () => {
|
|
114
|
+
renderHighlight();
|
|
115
|
+
updateDirtyState();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
scrollArea.addEventListener("scroll", syncScroll);
|
|
119
|
+
|
|
120
|
+
// Tabキーでインデント挿入(デフォルトのフォーカス移動を止める)
|
|
121
|
+
textarea.addEventListener("keydown", (e) => {
|
|
122
|
+
if (e.key === "Tab") {
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
const start = textarea.selectionStart;
|
|
125
|
+
const end = textarea.selectionEnd;
|
|
126
|
+
textarea.value = textarea.value.slice(0, start) + " " + textarea.value.slice(end);
|
|
127
|
+
textarea.selectionStart = textarea.selectionEnd = start + 2;
|
|
128
|
+
renderHighlight();
|
|
129
|
+
updateDirtyState();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// ---- ファイル一覧・読み込み・保存 ----
|
|
134
|
+
|
|
135
|
+
async function loadFileList() {
|
|
136
|
+
try {
|
|
137
|
+
const res = await fetch("/api/files");
|
|
138
|
+
if (!res.ok) throw new Error("failed to load file list");
|
|
139
|
+
const files = await res.json();
|
|
140
|
+
|
|
141
|
+
fileListEl.innerHTML = "";
|
|
142
|
+
|
|
143
|
+
if (files.length === 0) {
|
|
144
|
+
const li = document.createElement("li");
|
|
145
|
+
li.className = "empty";
|
|
146
|
+
li.textContent = "編集可能なファイルがありません";
|
|
147
|
+
fileListEl.appendChild(li);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const path of files) {
|
|
152
|
+
const li = document.createElement("li");
|
|
153
|
+
li.dataset.path = path;
|
|
154
|
+
|
|
155
|
+
const icon = document.createElement("span");
|
|
156
|
+
icon.className = `file-icon ${iconFor(path)}`;
|
|
157
|
+
icon.textContent = iconFor(path).slice(0, 1).toUpperCase();
|
|
158
|
+
|
|
159
|
+
const label = document.createElement("span");
|
|
160
|
+
label.textContent = path;
|
|
161
|
+
|
|
162
|
+
li.appendChild(icon);
|
|
163
|
+
li.appendChild(label);
|
|
164
|
+
li.addEventListener("click", () => openFile(path));
|
|
165
|
+
|
|
166
|
+
fileListEl.appendChild(li);
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
fileListEl.innerHTML = "";
|
|
170
|
+
const li = document.createElement("li");
|
|
171
|
+
li.className = "empty";
|
|
172
|
+
li.textContent = "ファイル一覧の取得に失敗しました";
|
|
173
|
+
fileListEl.appendChild(li);
|
|
174
|
+
console.error(err);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function openFile(path) {
|
|
179
|
+
try {
|
|
180
|
+
setStatus("読み込み中...", "");
|
|
181
|
+
const res = await fetch(`/api/file?path=${encodeURIComponent(path)}`);
|
|
182
|
+
if (!res.ok) {
|
|
183
|
+
const err = await res.json();
|
|
184
|
+
throw new Error(err.error || "failed to load file");
|
|
185
|
+
}
|
|
186
|
+
const data = await res.json();
|
|
187
|
+
|
|
188
|
+
currentPath = data.path;
|
|
189
|
+
savedContent = data.content;
|
|
190
|
+
|
|
191
|
+
const lang = languageFor(data.path);
|
|
192
|
+
currentLanguage = lang.grammar;
|
|
193
|
+
currentLanguageName = lang.name;
|
|
194
|
+
|
|
195
|
+
textarea.value = data.content;
|
|
196
|
+
textarea.disabled = false;
|
|
197
|
+
renderHighlight();
|
|
198
|
+
|
|
199
|
+
currentFileEl.textContent = data.path;
|
|
200
|
+
updateDirtyState();
|
|
201
|
+
setStatus("", "");
|
|
202
|
+
|
|
203
|
+
for (const li of fileListEl.querySelectorAll("li")) {
|
|
204
|
+
li.classList.toggle("active", li.dataset.path === path);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
textarea.focus();
|
|
208
|
+
} catch (err) {
|
|
209
|
+
setStatus("読み込みエラー", "error");
|
|
210
|
+
console.error(err);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function saveFile() {
|
|
215
|
+
if (!currentPath) return;
|
|
216
|
+
|
|
217
|
+
const content = textarea.value;
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
saveBtn.disabled = true;
|
|
221
|
+
setStatus("保存中...", "");
|
|
222
|
+
|
|
223
|
+
const res = await fetch("/api/file", {
|
|
224
|
+
method: "POST",
|
|
225
|
+
headers: { "Content-Type": "application/json" },
|
|
226
|
+
body: JSON.stringify({ path: currentPath, content }),
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
if (!res.ok) {
|
|
230
|
+
const err = await res.json();
|
|
231
|
+
throw new Error(err.error || "failed to save file");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
savedContent = content;
|
|
235
|
+
setStatus("保存しました", "ok");
|
|
236
|
+
updateDirtyState();
|
|
237
|
+
} catch (err) {
|
|
238
|
+
setStatus("保存に失敗しました", "error");
|
|
239
|
+
saveBtn.disabled = false;
|
|
240
|
+
console.error(err);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
saveBtn.addEventListener("click", saveFile);
|
|
245
|
+
|
|
246
|
+
// Ctrl/Cmd + S で保存
|
|
247
|
+
window.addEventListener("keydown", (e) => {
|
|
248
|
+
const isSaveShortcut = (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s";
|
|
249
|
+
if (isSaveShortcut) {
|
|
250
|
+
e.preventDefault();
|
|
251
|
+
saveFile();
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
renderLineNumbers("");
|
|
256
|
+
loadFileList();
|
|
257
|
+
})();
|
data/web/views/index.erb
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ja">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>PicoRuby Editor (Funicular)</title>
|
|
7
|
+
|
|
8
|
+
<!-- スタイルは web/public/css/style.css をそのまま流用する -->
|
|
9
|
+
<link rel="stylesheet" href="/css/style.css">
|
|
10
|
+
|
|
11
|
+
<!--
|
|
12
|
+
Prism.js (シンタックスハイライト)
|
|
13
|
+
CDNのESM importはバージョン解決が不安定なので、
|
|
14
|
+
バージョン固定の <script> タグで読み込む方針。
|
|
15
|
+
-->
|
|
16
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/components/prism-core.min.js"></script>
|
|
17
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/components/prism-clike.min.js"></script>
|
|
18
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/components/prism-c.min.js"></script>
|
|
19
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/components/prism-ruby.min.js"></script>
|
|
20
|
+
|
|
21
|
+
<!--
|
|
22
|
+
ESP Web Tools (https://esphome.github.io/esp-web-tools/)。
|
|
23
|
+
<esp-web-install-button> カスタム要素を登録する。ブラウザのWeb Serial API経由で
|
|
24
|
+
ESP32にファームウェアを書き込む処理はすべてこのライブラリに任せている
|
|
25
|
+
(サーバ側は /api/firmware/manifest.json でマニフェストを返すだけ)。
|
|
26
|
+
バージョン固定のCDN読み込み方針は他のライブラリと同様。
|
|
27
|
+
-->
|
|
28
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/esp-web-tools@10.4.0/dist/web/install-button.js"></script>
|
|
29
|
+
|
|
30
|
+
<!--
|
|
31
|
+
xterm.js (ターミナル画面の描画)。
|
|
32
|
+
PicoRuby Web Terminal (picoruby.org/terminal) と同じくWeb Serial API経由で
|
|
33
|
+
実機のUSBシリアルに直結し、ここに表示する。バージョン固定のCDN読み込み方針は
|
|
34
|
+
他のライブラリと同様。
|
|
35
|
+
-->
|
|
36
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@6.0.0/css/xterm.css">
|
|
37
|
+
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@6.0.0/lib/xterm.js"></script>
|
|
38
|
+
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.11.0/lib/addon-fit.js"></script>
|
|
39
|
+
|
|
40
|
+
<!--
|
|
41
|
+
Ruby(Funicular)側から呼ぶための薄いJSラッパ。
|
|
42
|
+
Prismの細かい作法(文法オブジェクトの取り出し、末尾改行の補正)はここに閉じ込めて、
|
|
43
|
+
Ruby側は funicularHighlight(code, lang) だけ知っていればよい状態にする。
|
|
44
|
+
-->
|
|
45
|
+
<script>
|
|
46
|
+
window.funicularHighlight = function (code, lang) {
|
|
47
|
+
var grammar = Prism.languages[lang] || Prism.languages.clike;
|
|
48
|
+
// 末尾が改行だと最終行の高さが潰れてキャレットとズレるため空白を足す
|
|
49
|
+
var source = /\n$/.test(code) ? code + ' ' : code;
|
|
50
|
+
return Prism.highlight(source, grammar, lang);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// サイドバー(ファイルツリー)の自前コンテキストメニューとブラウザ標準の
|
|
54
|
+
// 右クリックメニューが同時に出てしまう問題への対処。
|
|
55
|
+
// tree_view.rb / editor_app.rb 側でもFunicularのDSL(oncontextmenu:)経由で
|
|
56
|
+
// event.preventDefaultを呼んでいるが、Funicularの合成イベントはPicoRuby.wasmを
|
|
57
|
+
// 経由する分、ブラウザが「このcontextmenuイベントのデフォルト動作をまだ
|
|
58
|
+
// キャンセルできる」タイミングに間に合わないことがある(実機でのユーザー確認で
|
|
59
|
+
// 発覚)。preventDefaultはネイティブのJSイベントハンドラ内で同期的に呼ぶ必要が
|
|
60
|
+
// あるため、ここだけはPicoRuby/Funicularのブリッジを経由しない素のJSで
|
|
61
|
+
// 直接止める(funicularHighlightと同じ「確実性が要る部分はプレーンJSに
|
|
62
|
+
// 任せる」方針)。サイドバーの外(エディタのtextarea等)は標準メニューを
|
|
63
|
+
// 残したいので、.sidebar配下かどうかをclosestで判定してそこだけ抑止する。
|
|
64
|
+
document.addEventListener('contextmenu', function (event) {
|
|
65
|
+
if (event.target.closest('.sidebar')) {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
</script>
|
|
70
|
+
</head>
|
|
71
|
+
<body>
|
|
72
|
+
<div id="app"><!-- Funicular がここにマウントされる --></div>
|
|
73
|
+
|
|
74
|
+
<!--
|
|
75
|
+
Ruby ソースは読み込み順がそのまま依存順になる。
|
|
76
|
+
子コンポーネント → ルートコンポーネント → 起動 の順に並べること。
|
|
77
|
+
-->
|
|
78
|
+
<script type="text/ruby" src="/ruby/components/tree_view.rb"></script>
|
|
79
|
+
<script type="text/ruby" src="/ruby/components/toolbar.rb"></script>
|
|
80
|
+
<script type="text/ruby" src="/ruby/components/project_settings_dialog.rb"></script>
|
|
81
|
+
<script type="text/ruby" src="/ruby/components/prompt_dialog.rb"></script>
|
|
82
|
+
<script type="text/ruby" src="/ruby/components/directory_browser_dialog.rb"></script>
|
|
83
|
+
<script type="text/ruby" src="/ruby/components/log_panel.rb"></script>
|
|
84
|
+
<script type="text/ruby" src="/ruby/components/menu_bar.rb"></script>
|
|
85
|
+
<script type="text/ruby" src="/ruby/components/editor_app.rb"></script>
|
|
86
|
+
<script type="text/ruby" src="/ruby/main.rb"></script>
|
|
87
|
+
|
|
88
|
+
<!--
|
|
89
|
+
PicoRuby.wasm 本体。Funicular は同梱されている。
|
|
90
|
+
text/ruby スクリプトを走査する側なので、必ず最後に置く。
|
|
91
|
+
-->
|
|
92
|
+
<script src="https://cdn.jsdelivr.net/npm/@picoruby/wasm-wasi@4.0.2/dist/init.iife.js"></script>
|
|
93
|
+
</body>
|
|
94
|
+
</html>
|
metadata
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: picoride
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- youchan
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: sinatra
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: puma
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rackup
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: perfect_toml
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
description: A Integrated develop emvironment for PicoRuby ESP32
|
|
69
|
+
email:
|
|
70
|
+
- youchan@gmail.com
|
|
71
|
+
executables:
|
|
72
|
+
- picoride
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- ".claude/launch.json"
|
|
77
|
+
- CHANGELOG.md
|
|
78
|
+
- CLAUDE.md
|
|
79
|
+
- CODE_OF_CONDUCT.md
|
|
80
|
+
- Dockerfile
|
|
81
|
+
- LICENSE.txt
|
|
82
|
+
- README.md
|
|
83
|
+
- Rakefile
|
|
84
|
+
- docker/esp32/Dockerfile
|
|
85
|
+
- docker/pico/Dockerfile
|
|
86
|
+
- exe/picoride
|
|
87
|
+
- lib/picoride.rb
|
|
88
|
+
- lib/picoride/background_job.rb
|
|
89
|
+
- lib/picoride/project_store.rb
|
|
90
|
+
- lib/picoride/r2p2_state.rb
|
|
91
|
+
- lib/picoride/server.rb
|
|
92
|
+
- lib/picoride/version.rb
|
|
93
|
+
- sig/picoruby/esp32/ide.rbs
|
|
94
|
+
- web/funicular/README.md
|
|
95
|
+
- web/funicular/ruby/components/directory_browser_dialog.rb
|
|
96
|
+
- web/funicular/ruby/components/editor_app.rb
|
|
97
|
+
- web/funicular/ruby/components/log_panel.rb
|
|
98
|
+
- web/funicular/ruby/components/menu_bar.rb
|
|
99
|
+
- web/funicular/ruby/components/project_settings_dialog.rb
|
|
100
|
+
- web/funicular/ruby/components/prompt_dialog.rb
|
|
101
|
+
- web/funicular/ruby/components/toolbar.rb
|
|
102
|
+
- web/funicular/ruby/components/tree_view.rb
|
|
103
|
+
- web/funicular/ruby/main.rb
|
|
104
|
+
- web/public/css/style.css
|
|
105
|
+
- web/public/js/editor.js
|
|
106
|
+
- web/views/index.erb
|
|
107
|
+
homepage: https://github.com/youchan/picoride
|
|
108
|
+
licenses:
|
|
109
|
+
- MIT
|
|
110
|
+
metadata:
|
|
111
|
+
allowed_push_host: https://rubygems.org
|
|
112
|
+
homepage_uri: https://github.com/youchan/picoride
|
|
113
|
+
source_code_uri: https://github.com/youchan/picoride
|
|
114
|
+
changelog_uri: https://github.com/youchan/picoride/CHANGELOG.md
|
|
115
|
+
rdoc_options: []
|
|
116
|
+
require_paths:
|
|
117
|
+
- lib
|
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
119
|
+
requirements:
|
|
120
|
+
- - ">="
|
|
121
|
+
- !ruby/object:Gem::Version
|
|
122
|
+
version: 3.2.0
|
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
|
+
requirements:
|
|
125
|
+
- - ">="
|
|
126
|
+
- !ruby/object:Gem::Version
|
|
127
|
+
version: '0'
|
|
128
|
+
requirements: []
|
|
129
|
+
rubygems_version: 4.0.6
|
|
130
|
+
specification_version: 4
|
|
131
|
+
summary: PicoRuby ESP32 IDE
|
|
132
|
+
test_files: []
|