Skip to content

Commit 5b03c77

Browse files
committed
adding extention customization
1 parent fe219a1 commit 5b03c77

19 files changed

Lines changed: 2548 additions & 399 deletions

include/pythonic/REPL/ScriptIt.cpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "scriptit_builtins.hpp"
77
#include "perser.hpp"
88
#include "json_and_kernel.hpp"
9+
#include <unistd.h> // readlink for --notebook and --customize
910
// ═══════════════════════════════════════════════════════════
1011
// ──── Execute Script Helper ────────────────────────────────
1112
// ═══════════════════════════════════════════════════════════
@@ -48,6 +49,132 @@ int main(int argc, char *argv[])
4849
return 0;
4950
}
5051

52+
if (argc > 1 && std::string(argv[1]) == "--notebook")
53+
{
54+
// Launch the web notebook server
55+
// Try the installed scriptit-notebook launcher first, then fallback to local notebook.sh
56+
std::string notebookCmd;
57+
// Check for system-installed launcher
58+
if (std::system("command -v scriptit-notebook >/dev/null 2>&1") == 0)
59+
{
60+
notebookCmd = "scriptit-notebook";
61+
}
62+
else
63+
{
64+
// Fallback: find notebook.sh relative to this binary
65+
// argv[0] might be a path or just "scriptit" from PATH
66+
std::string selfPath;
67+
// Try /proc/self/exe on Linux
68+
char buf[4096] = {};
69+
ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
70+
if (len > 0)
71+
{
72+
buf[len] = '\0';
73+
selfPath = std::string(buf);
74+
}
75+
if (!selfPath.empty())
76+
{
77+
std::string dir = selfPath.substr(0, selfPath.rfind('/'));
78+
// If installed to /usr/local/bin, notebook files are in /usr/local/share/scriptit/notebook/
79+
std::string shareNotebook = dir + "/../share/scriptit/notebook/notebook_server.py";
80+
std::string localNotebook = dir + "/notebook.sh";
81+
// Also check REPL layout
82+
std::string replNotebook = dir + "/notebook/notebook_server.py";
83+
if (std::ifstream(shareNotebook).good())
84+
{
85+
notebookCmd = "python3 \"" + shareNotebook + "\"";
86+
}
87+
else if (std::ifstream(replNotebook).good())
88+
{
89+
notebookCmd = "python3 \"" + replNotebook + "\"";
90+
}
91+
else if (std::ifstream(localNotebook).good())
92+
{
93+
notebookCmd = "bash \"" + localNotebook + "\"";
94+
}
95+
}
96+
}
97+
98+
if (notebookCmd.empty())
99+
{
100+
std::cerr << "Error: Could not find notebook server.\n";
101+
std::cerr << "Make sure ScriptIt is installed system-wide (sudo cmake --install build_scriptit)\n";
102+
return 1;
103+
}
104+
105+
// Forward remaining args (e.g. --port, notebook file)
106+
for (int i = 2; i < argc; i++)
107+
{
108+
notebookCmd += " ";
109+
notebookCmd += argv[i];
110+
}
111+
return std::system(notebookCmd.c_str());
112+
}
113+
114+
if (argc > 1 && std::string(argv[1]) == "--customize")
115+
{
116+
// Launch the color customizer web server
117+
std::string customizerScript;
118+
std::string selfPath;
119+
char buf[4096] = {};
120+
ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
121+
if (len > 0)
122+
{
123+
buf[len] = '\0';
124+
selfPath = std::string(buf);
125+
}
126+
if (!selfPath.empty())
127+
{
128+
std::string dir = selfPath.substr(0, selfPath.rfind('/'));
129+
// Check various locations
130+
std::string paths[] = {
131+
dir + "/../share/scriptit/color_customizer/customizer_server.py", // system install
132+
dir + "/scriptit-vscode/color_customizer/customizer_server.py", // REPL layout
133+
dir + "/color_customizer/customizer_server.py", // local
134+
};
135+
for (auto &p : paths)
136+
{
137+
if (std::ifstream(p).good())
138+
{
139+
customizerScript = p;
140+
break;
141+
}
142+
}
143+
}
144+
// Also try relative to CWD
145+
if (customizerScript.empty())
146+
{
147+
std::string cwdPaths[] = {
148+
"scriptit-vscode/color_customizer/customizer_server.py",
149+
"color_customizer/customizer_server.py",
150+
};
151+
for (auto &p : cwdPaths)
152+
{
153+
if (std::ifstream(p).good())
154+
{
155+
customizerScript = p;
156+
break;
157+
}
158+
}
159+
}
160+
161+
if (customizerScript.empty())
162+
{
163+
std::cerr << "Error: Could not find the color customizer.\n";
164+
std::cerr << "Make sure ScriptIt is installed with the VS Code extension files.\n";
165+
return 1;
166+
}
167+
168+
std::string cmd = "python3 \"" + customizerScript + "\"";
169+
// Forward --port if provided
170+
for (int i = 2; i < argc; i++)
171+
{
172+
cmd += " ";
173+
cmd += argv[i];
174+
}
175+
return std::system(cmd.c_str());
176+
}
177+
51178
if (argc > 1 && std::string(argv[1]) == "--test")
52179
{
53180
std::string source =
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"nsit_format": "1.0",
3+
"metadata": {
4+
"title": "Untitled",
5+
"created": "2026-02-14T02:29:06.844439Z",
6+
"modified": "2026-02-14T02:29:35.575456Z",
7+
"kernel": "scriptit",
8+
"kernel_version": "2.0"
9+
},
10+
"cells": [
11+
{
12+
"id": "95e13544-4987-4983-88ef-45ca1ff017ab",
13+
"type": "code",
14+
"source": "",
15+
"outputs": [],
16+
"execution_count": null,
17+
"metadata": {}
18+
}
19+
]
20+
}
4.95 KB
Binary file not shown.

include/pythonic/REPL/json_and_kernel.hpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,33 @@ inline std::string make_json_response(const std::string &cellId, const std::stri
137137
// ──── Kernel Mode ──────────────────────────────────────────
138138
// ═══════════════════════════════════════════════════════════
139139

140+
// Global kernel state for the input() override
141+
// These are `extern`-declared in scriptit_builtins.hpp so input() can access them
142+
bool _scriptit_kernel_mode = false;
143+
std::streambuf *_scriptit_kernel_real_stdout = nullptr;
144+
std::string _scriptit_kernel_cell_id;
145+
146+
namespace kernel_io
147+
{
148+
static std::streambuf *realStdoutBuf = nullptr; // original cout buffer
149+
static std::string currentCellId; // currently executing cell
150+
static bool inKernelMode = false;
151+
}
152+
140153
void runKernel()
141154
{
142155
Scope globalScope;
143156
globalScope.define("PI", var(3.14159265));
144157
globalScope.define("e", var(2.7182818));
145158
int executionCount = 0;
146159

160+
kernel_io::inKernelMode = true;
161+
kernel_io::realStdoutBuf = std::cout.rdbuf(); // save the real stdout
162+
163+
// Set the extern globals so input() builtin can access them
164+
_scriptit_kernel_mode = true;
165+
_scriptit_kernel_real_stdout = std::cout.rdbuf();
166+
147167
// Signal ready
148168
std::cout << "{\"status\":\"kernel_ready\",\"version\":\"2.0\"}" << std::endl;
149169
std::cout.flush();
@@ -181,6 +201,9 @@ void runKernel()
181201
std::string code = cmd["code"];
182202
executionCount++;
183203

204+
kernel_io::currentCellId = cellId;
205+
_scriptit_kernel_cell_id = cellId;
206+
184207
// Capture stdout
185208
std::ostringstream capturedOut;
186209
std::streambuf *oldBuf = std::cout.rdbuf(capturedOut.rdbuf());
@@ -217,6 +240,14 @@ void runKernel()
217240
continue;
218241
}
219242

243+
if (action == "input_reply")
244+
{
245+
// This arrives as a response to an input_request we sent.
246+
// It's handled inline in the input() override via readLine,
247+
// so we shouldn't normally reach here. Ignore it.
248+
continue;
249+
}
250+
220251
if (action == "complete")
221252
{
222253
std::string code = cmd["code"];

include/pythonic/REPL/notebook.sh

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,27 @@
1212
set -e
1313

1414
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
15-
NOTEBOOK_DIR="$SCRIPT_DIR/notebook"
15+
16+
# Try multiple locations for notebook files
17+
# 1. Local REPL layout: $SCRIPT_DIR/notebook/
18+
# 2. System install: /usr/local/share/scriptit/notebook/
19+
# 3. Prefix-based: $SCRIPT_DIR/../share/scriptit/notebook/
20+
if [ -f "$SCRIPT_DIR/notebook/notebook_server.py" ]; then
21+
NOTEBOOK_DIR="$SCRIPT_DIR/notebook"
22+
elif [ -f "$SCRIPT_DIR/../share/scriptit/notebook/notebook_server.py" ]; then
23+
NOTEBOOK_DIR="$(cd "$SCRIPT_DIR/../share/scriptit/notebook" && pwd)"
24+
elif [ -f "/usr/local/share/scriptit/notebook/notebook_server.py" ]; then
25+
NOTEBOOK_DIR="/usr/local/share/scriptit/notebook"
26+
else
27+
echo -e "\033[0;31mError: Cannot find notebook_server.py\033[0m"
28+
echo " Tried: $SCRIPT_DIR/notebook/"
29+
echo " Tried: $SCRIPT_DIR/../share/scriptit/notebook/"
30+
echo " Tried: /usr/local/share/scriptit/notebook/"
31+
exit 1
32+
fi
33+
1634
SERVER="$NOTEBOOK_DIR/notebook_server.py"
17-
BINARY="$SCRIPT_DIR/scriptit"
18-
CPP_SOURCE="$SCRIPT_DIR/ScriptIt.cpp"
19-
INCLUDE_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)/include"
20-
STUBS_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)/src/pythonicDispatchStubs.cpp"
35+
BINARY="$(command -v scriptit 2>/dev/null || echo "$SCRIPT_DIR/scriptit")"
2136

2237
# Colors
2338
RED='\033[0;31m'
@@ -29,15 +44,18 @@ NC='\033[0m'
2944

3045
# ─── Check / Build binary ────────────────────────────────
3146

32-
if [ ! -f "$BINARY" ]; then
33-
echo -e "${CYAN}Building ScriptIt...${NC}"
47+
if ! command -v "$BINARY" &>/dev/null && [ ! -f "$BINARY" ]; then
48+
# Try building from source if we're in the dev tree
49+
CPP_SOURCE="$SCRIPT_DIR/ScriptIt.cpp"
50+
INCLUDE_DIR="$(cd "$SCRIPT_DIR/../../.." 2>/dev/null && pwd)/include"
51+
STUBS_DIR="$(cd "$SCRIPT_DIR/../../.." 2>/dev/null && pwd)/src/pythonicDispatchStubs.cpp"
3452
if [ -f "$CPP_SOURCE" ] && [ -f "$STUBS_DIR" ]; then
35-
g++ -std=c++20 -I"$INCLUDE_DIR" -O2 -o "$BINARY" "$CPP_SOURCE" "$STUBS_DIR"
53+
echo -e "${CYAN}Building ScriptIt...${NC}"
54+
g++ -std=c++20 -I"$INCLUDE_DIR" -O2 -o "$SCRIPT_DIR/scriptit" "$CPP_SOURCE" "$STUBS_DIR"
55+
BINARY="$SCRIPT_DIR/scriptit"
3656
echo -e "${GREEN}✓ Built successfully${NC}"
3757
else
38-
echo -e "${RED}Error: Cannot find source files to build ScriptIt.${NC}"
39-
echo " Expected: $CPP_SOURCE"
40-
echo " Expected: $STUBS_DIR"
58+
echo -e "${RED}Error: scriptit binary not found. Install ScriptIt first.${NC}"
4159
exit 1
4260
fi
4361
fi

include/pythonic/REPL/notebook/index.html renamed to include/pythonic/REPL/notebook/notebook.html

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,14 +1349,12 @@ <h2>📝 New Notebook</h2>
13491349
}
13501350

13511351
function browseForSaveLocation() {
1352-
// Only reload browse results if the list is empty or shows an error
1352+
// Always refresh the directory browser when Browse is clicked
1353+
showServerBrowser("save");
13531354
var listDiv = document.getElementById("saveDirList");
1354-
if (listDiv && listDiv.children.length > 1) {
1355-
// Results already loaded — scroll into view
1356-
listDiv.scrollIntoView({ behavior: "smooth", block: "nearest" });
1357-
return;
1355+
if (listDiv) {
1356+
setTimeout(function() { listDiv.scrollIntoView({ behavior: "smooth", block: "nearest" }); }, 200);
13581357
}
1359-
showServerBrowser("save");
13601358
}
13611359

13621360
function downloadNotebook() {

include/pythonic/REPL/notebook/notebook_server.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import json
99
import os
1010
import sys
11+
import shutil
1112
import subprocess
1213
import threading
1314
import time
@@ -21,7 +22,22 @@
2122
# ─── Configuration ───────────────────────────────────────
2223

2324
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
24-
SCRIPTIT_BINARY = os.path.join(SCRIPT_DIR, "..", "scriptit")
25+
26+
# Find the scriptit binary — check PATH first, then relative locations
27+
SCRIPTIT_BINARY = shutil.which("scriptit")
28+
if not SCRIPTIT_BINARY:
29+
# Try relative to this script
30+
candidates = [
31+
os.path.join(SCRIPT_DIR, "..", "scriptit"), # REPL layout
32+
os.path.join(SCRIPT_DIR, "..", "..", "bin", "scriptit"), # share -> bin
33+
"/usr/local/bin/scriptit",
34+
]
35+
for c in candidates:
36+
if os.path.isfile(c) and os.access(c, os.X_OK):
37+
SCRIPTIT_BINARY = c
38+
break
39+
if not SCRIPTIT_BINARY:
40+
SCRIPTIT_BINARY = "scriptit" # hope it's in PATH
2541
DEFAULT_PORT = 8888
2642
NOTEBOOK_DIR = SCRIPT_DIR
2743

@@ -266,8 +282,10 @@ def do_OPTIONS(self):
266282
def do_GET(self):
267283
global current_notebook
268284

269-
if self.path == "/" or self.path == "/index.html":
270-
gui_path = os.path.join(SCRIPT_DIR, "index.html")
285+
if self.path == "/" or self.path == "/index.html" or self.path == "/notebook.html":
286+
gui_path = os.path.join(SCRIPT_DIR, "notebook.html")
287+
if not os.path.exists(gui_path):
288+
gui_path = os.path.join(SCRIPT_DIR, "index.html") # fallback
271289
if os.path.exists(gui_path):
272290
with open(gui_path, "r") as f:
273291
self.send_html(f.read())

include/pythonic/REPL/perser.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,17 @@ class Tokenizer
7171
continue;
7272
}
7373

74+
// Single-line comments: # ... (skip to end of line)
75+
if (c == '#')
76+
{
77+
while (i < source.length() && source[i] != '\n')
78+
i++;
79+
// Don't consume the \n — let the normal newline handler emit it
80+
if (i < source.length())
81+
i--; // back up so the main loop's i++ lands on \n
82+
continue;
83+
}
84+
7485
// String literals: "..." or '...'
7586
if (c == '"' || c == '\'')
7687
{

0 commit comments

Comments
 (0)