janito 1.9.0__py3-none-any.whl → 1.11.0__py3-none-any.whl

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.
Files changed (106) hide show
  1. janito/__init__.py +1 -1
  2. janito/agent/api_exceptions.py +4 -0
  3. janito/agent/config.py +1 -1
  4. janito/agent/config_defaults.py +2 -26
  5. janito/agent/conversation.py +163 -122
  6. janito/agent/conversation_api.py +246 -168
  7. janito/agent/conversation_ui.py +1 -1
  8. janito/agent/{conversation_history.py → llm_conversation_history.py} +30 -1
  9. janito/agent/openai_client.py +38 -23
  10. janito/agent/openai_schema_generator.py +162 -129
  11. janito/agent/platform_discovery.py +134 -77
  12. janito/agent/profile_manager.py +5 -5
  13. janito/agent/rich_message_handler.py +80 -31
  14. janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +20 -4
  15. janito/agent/test_openai_schema_generator.py +93 -0
  16. janito/agent/tool_base.py +7 -2
  17. janito/agent/tool_executor.py +54 -49
  18. janito/agent/tool_registry.py +5 -2
  19. janito/agent/tool_use_tracker.py +26 -5
  20. janito/agent/tools/__init__.py +8 -3
  21. janito/agent/tools/create_directory.py +3 -1
  22. janito/agent/tools/create_file.py +7 -1
  23. janito/agent/tools/fetch_url.py +40 -3
  24. janito/agent/tools/find_files.py +29 -14
  25. janito/agent/tools/get_file_outline/core.py +7 -8
  26. janito/agent/tools/get_file_outline/python_outline.py +139 -95
  27. janito/agent/tools/get_file_outline/search_outline.py +3 -1
  28. janito/agent/tools/get_lines.py +98 -64
  29. janito/agent/tools/move_file.py +59 -31
  30. janito/agent/tools/open_url.py +31 -0
  31. janito/agent/tools/present_choices.py +3 -1
  32. janito/agent/tools/python_command_runner.py +149 -0
  33. janito/agent/tools/python_file_runner.py +147 -0
  34. janito/agent/tools/python_stdin_runner.py +153 -0
  35. janito/agent/tools/remove_directory.py +3 -1
  36. janito/agent/tools/remove_file.py +5 -1
  37. janito/agent/tools/replace_file.py +12 -2
  38. janito/agent/tools/replace_text_in_file.py +195 -149
  39. janito/agent/tools/run_bash_command.py +30 -69
  40. janito/agent/tools/run_powershell_command.py +138 -105
  41. janito/agent/tools/search_text/__init__.py +1 -0
  42. janito/agent/tools/search_text/core.py +176 -0
  43. janito/agent/tools/search_text/match_lines.py +58 -0
  44. janito/agent/tools/search_text/pattern_utils.py +65 -0
  45. janito/agent/tools/search_text/traverse_directory.py +127 -0
  46. janito/agent/tools/validate_file_syntax/core.py +43 -30
  47. janito/agent/tools/validate_file_syntax/html_validator.py +21 -5
  48. janito/agent/tools/validate_file_syntax/markdown_validator.py +77 -34
  49. janito/agent/tools_utils/action_type.py +7 -0
  50. janito/agent/tools_utils/dir_walk_utils.py +3 -2
  51. janito/agent/tools_utils/formatting.py +47 -21
  52. janito/agent/tools_utils/gitignore_utils.py +89 -40
  53. janito/agent/tools_utils/test_gitignore_utils.py +46 -0
  54. janito/agent/tools_utils/utils.py +7 -1
  55. janito/cli/_print_config.py +63 -61
  56. janito/cli/arg_parser.py +13 -12
  57. janito/cli/cli_main.py +137 -147
  58. janito/cli/config_commands.py +112 -109
  59. janito/cli/main.py +152 -174
  60. janito/cli/one_shot.py +40 -26
  61. janito/i18n/__init__.py +1 -1
  62. janito/rich_utils.py +46 -8
  63. janito/shell/commands/__init__.py +2 -4
  64. janito/shell/commands/conversation_restart.py +3 -1
  65. janito/shell/commands/edit.py +3 -0
  66. janito/shell/commands/history_view.py +3 -3
  67. janito/shell/commands/lang.py +3 -0
  68. janito/shell/commands/livelogs.py +5 -3
  69. janito/shell/commands/prompt.py +6 -0
  70. janito/shell/commands/session.py +3 -0
  71. janito/shell/commands/session_control.py +3 -0
  72. janito/shell/commands/termweb_log.py +8 -0
  73. janito/shell/commands/tools.py +3 -0
  74. janito/shell/commands/track.py +36 -0
  75. janito/shell/commands/utility.py +13 -18
  76. janito/shell/commands/verbose.py +3 -4
  77. janito/shell/input_history.py +62 -0
  78. janito/shell/main.py +160 -181
  79. janito/shell/session/config.py +83 -75
  80. janito/shell/session/manager.py +0 -21
  81. janito/shell/ui/interactive.py +97 -75
  82. janito/termweb/static/editor.css +32 -33
  83. janito/termweb/static/editor.css.bak +140 -22
  84. janito/termweb/static/editor.html +12 -7
  85. janito/termweb/static/editor.html.bak +16 -11
  86. janito/termweb/static/editor.js +94 -40
  87. janito/termweb/static/editor.js.bak +97 -65
  88. janito/termweb/static/index.html +1 -2
  89. janito/termweb/static/index.html.bak +1 -1
  90. janito/termweb/static/termweb.css +1 -22
  91. janito/termweb/static/termweb.css.bak +6 -4
  92. janito/termweb/static/termweb.js +0 -6
  93. janito/termweb/static/termweb.js.bak +1 -2
  94. janito/tests/test_rich_utils.py +44 -0
  95. janito/web/app.py +0 -75
  96. {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/METADATA +61 -42
  97. janito-1.11.0.dist-info/RECORD +163 -0
  98. {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/WHEEL +1 -1
  99. janito/agent/providers.py +0 -77
  100. janito/agent/tools/run_python_command.py +0 -161
  101. janito/agent/tools/search_text.py +0 -204
  102. janito/shell/commands/sum.py +0 -49
  103. janito-1.9.0.dist-info/RECORD +0 -151
  104. {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/entry_points.txt +0 -0
  105. {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/licenses/LICENSE +0 -0
  106. {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@
4
4
  <link rel="icon" type="image/svg+xml" href="/static/termicon.svg">
5
5
  <link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico">
6
6
  <meta charset="UTF-8">
7
- <title>TermWeb Editor — janito.dev</title>
7
+ <title>Janito Light Editor</title>
8
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.css">
9
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/theme/dracula.min.css">
10
10
  <link rel="stylesheet" href="/static/termweb.css">
@@ -12,27 +12,32 @@
12
12
  </head>
13
13
  <body>
14
14
  <div class="header">
15
- <div class="header-title">TermWeb Editor</div>
16
- <button class="theme-switcher" id="theme-switcher" title="Alternator tema">
17
- <span id="theme-icon" aria-label="Switch theme" style="pointer-events:none;">🌙</span>
18
- </button>
19
- </div>
15
+ <div class="header-title">Janito Light Editor</div>
16
+ <span id="filename-display" class="filename-display"></span>
17
+ <div style="flex:1 1 auto;"></div>
18
+ <button class="theme-switcher" id="theme-switcher" title="Alternator tema">
19
+ <span id="theme-icon" aria-label="Switch theme" style="pointer-events:none;">🌙</span>
20
+ </button>
21
+ </div>
20
22
  <div class="footer">
21
23
  <button class="save-btn" id="save-btn">Save</button>
22
24
  </div>
23
- <div id="save-popup" style="display:none;position:fixed;top:32px;right:32px;z-index:9999;background:#323b4c;color:#fff;padding:14px 28px;border-radius:8px;box-shadow:0 2px 12px #0007;font-size:1.1em;">Saved!</div>
25
+ <div id="save-popup" style="display:none;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:9999;background:#323b4c;color:#fff;padding:14px 28px;border-radius:8px;box-shadow:0 2px 12px #0007;font-size:1.1em;">Saved!</div>
24
26
  <div class="main">
25
27
  <div class="editor-pane">
26
28
  <textarea id="code" name="code"></textarea>
29
+ <div id="save-popup" style="display:none;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:9999;background:#323b4c;color:#fff;padding:14px 28px;border-radius:8px;box-shadow:0 2px 12px #0007;font-size:1.1em;">Saved!</div>
27
30
  </div>
28
31
  </div>
29
32
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.js"></script>
30
33
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/python/python.min.js"></script>
34
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/django/django.min.js"></script>
31
35
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/theme/dracula.min.js"></script>
32
36
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/selection/active-line.min.js"></script>
33
37
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/search/search.min.js"></script>
34
38
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/search/searchcursor.min.js"></script>
35
39
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/dialog/dialog.min.js"></script>
40
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/mode/overlay.min.js"></script>
36
41
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/dialog/dialog.min.css">
37
42
 
38
43
  <script src="/static/editor.js"></script>
@@ -4,24 +4,25 @@
4
4
  <link rel="icon" type="image/svg+xml" href="/static/termicon.svg">
5
5
  <link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico">
6
6
  <meta charset="UTF-8">
7
- <title>TermWeb Editor — janito.dev</title>
7
+ <title>Janito Light Editor</title>
8
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.css">
9
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/theme/dracula.min.css">
10
10
  <link rel="stylesheet" href="/static/termweb.css">
11
11
  <link rel="stylesheet" href="/static/editor.css">
12
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/dialog/dialog.min.css">
13
12
  </head>
14
13
  <body>
15
- <div class="header compact-header">
16
- <div class="header-title">TermWeb Editor</div>
17
- <div class="header-buttons no-shrink">
18
- <button class="save-btn" id="save-btn" title="Save (Ctrl+S)">Save</button>
19
- <button class="theme-switcher" id="theme-switcher" title="Switch theme">
20
- <span id="theme-icon" aria-label="Switch theme">🌙</span>
21
- </button>
22
- </div>
14
+ <div class="header">
15
+ <div class="header-title">Janito Light Editor</div>
16
+ <span id="filename-display" class="filename-display"></span>
17
+ <div style="flex:1 1 auto;"></div>
18
+ <button class="theme-switcher" id="theme-switcher" title="Alternator tema">
19
+ <span id="theme-icon" aria-label="Switch theme" style="pointer-events:none;">🌙</span>
20
+ </button>
21
+ </div>
22
+ <div class="footer">
23
+ <button class="save-btn" id="save-btn">Save</button>
23
24
  </div>
24
- <div id="toast" class="toast"></div>
25
+ <div id="save-popup" style="display:none;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:9999;background:#323b4c;color:#fff;padding:14px 28px;border-radius:8px;box-shadow:0 2px 12px #0007;font-size:1.1em;">Saved!</div>
25
26
  <div class="main">
26
27
  <div class="editor-pane">
27
28
  <textarea id="code" name="code"></textarea>
@@ -29,11 +30,15 @@
29
30
  </div>
30
31
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.js"></script>
31
32
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/python/python.min.js"></script>
33
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/django/django.min.js"></script>
32
34
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/theme/dracula.min.js"></script>
33
35
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/selection/active-line.min.js"></script>
34
36
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/search/search.min.js"></script>
35
37
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/search/searchcursor.min.js"></script>
36
38
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/dialog/dialog.min.js"></script>
39
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/mode/overlay.min.js"></script>
40
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/dialog/dialog.min.css">
41
+
37
42
  <script src="/static/editor.js"></script>
38
43
  </body>
39
44
  </html>
@@ -4,39 +4,92 @@ function getQueryParam(name) {
4
4
  return url.searchParams.get(name);
5
5
  }
6
6
  const filePath = getQueryParam('path');
7
- let initialContent = `# Bem-vindo ao TermWeb!\n# Este é um editor CodeMirror ao vivo.\n\nprint("Olá, janito.dev!")`;
8
- if (filePath) {
9
- fetch(`/api/explorer/${encodeURIComponent(filePath)}`)
10
- .then(resp => resp.json())
11
- .then(data => {
12
- if (data.type === 'file') {
13
- initialContent = data.content;
14
- if (window.editorInstance) {
15
- window.editorInstance.setValue(initialContent);
7
+
8
+ // Updates the theme icon based on the current theme
9
+ function updateThemeIcon() {
10
+ var icon = document.getElementById('theme-icon');
11
+ if (!icon) return;
12
+ if (document.body.classList.contains('light-theme')) {
13
+ icon.textContent = '☀️'; // Sun for light mode
14
+ icon.title = 'Switch to dark mode';
15
+ } else {
16
+ icon.textContent = '🌙'; // Moon for dark mode
17
+ icon.title = 'Switch to light mode';
18
+ }
19
+ }
20
+
21
+ document.addEventListener('DOMContentLoaded', function() {
22
+ // Display filename in header if present
23
+ if (filePath) {
24
+ const filename = filePath.split(/[\\\/]/).pop();
25
+ const filenameDisplay = document.getElementById('filename-display');
26
+ if (filenameDisplay) {
27
+ filenameDisplay.textContent = '— ' + filePath;
28
+ filenameDisplay.title = filePath;
29
+ }
30
+ }
31
+
32
+ let initialContent = "";
33
+ if (filePath) {
34
+ fetch(`/api/explorer/${encodeURIComponent(filePath)}`)
35
+ .then(resp => resp.json())
36
+ .then(data => {
37
+ if (data.type === 'file') {
38
+ initialContent = data.content;
39
+ if (window.editorInstance) {
40
+ window.editorInstance.setValue(initialContent);
41
+ }
42
+ } else if (data.error) {
43
+ initialContent = '# Error: ' + data.error;
44
+ if (window.editorInstance) {
45
+ window.editorInstance.setValue(initialContent);
46
+ }
16
47
  }
17
- } else if (data.error) {
18
- initialContent = '# Error: ' + data.error;
48
+ })
49
+ .catch(err => {
50
+ initialContent = '# Error ao carregar arquivo: ' + err;
19
51
  if (window.editorInstance) {
20
52
  window.editorInstance.setValue(initialContent);
21
53
  }
22
- }
23
- })
24
- .catch(err => {
25
- initialContent = '# Error ao carregar arquivo: ' + err;
26
- if (window.editorInstance) {
27
- window.editorInstance.setValue(initialContent);
28
- }
29
- });
30
- }
31
- document.addEventListener('DOMContentLoaded', function() {
54
+ });
55
+ }
56
+
57
+ // --- Detect file extension and set CodeMirror mode ---
58
+ function detectMode(filename) {
59
+ if (!filename) return 'python';
60
+ const ext = filename.split('.').pop().toLowerCase();
61
+ const map = {
62
+ 'py': 'python',
63
+ 'js': 'javascript',
64
+ 'json': 'javascript',
65
+ 'md': 'markdown',
66
+ 'html': {name: 'htmlmixed', scriptingModeSpec: 'django'},
67
+ 'htm': {name: 'htmlmixed', scriptingModeSpec: 'django'},
68
+ 'jinja': 'django',
69
+ 'j2': 'django',
70
+ 'jinja2': 'django',
71
+ 'css': 'css',
72
+ 'sh': 'shell',
73
+ 'bash': 'shell',
74
+ 'yml': 'yaml',
75
+ 'yaml': 'yaml',
76
+ };
77
+ return map[ext] || 'python';
78
+ }
79
+ var initialMode = detectMode(filePath);
32
80
  var editorInstance = CodeMirror.fromTextArea(document.getElementById('code'), {
33
81
  lineNumbers: true,
34
- mode: 'python',
82
+ mode: initialMode,
35
83
  theme: 'dracula',
36
84
  indentUnit: 4,
37
85
  tabSize: 4,
38
86
  styleActiveLine: true,
39
87
  });
88
+ // If file loaded later, update mode
89
+ if (filePath) {
90
+ const mode = detectMode(filePath);
91
+ editorInstance.setOption('mode', mode);
92
+ }
40
93
  window.editorInstance = editorInstance;
41
94
  // Add Ctrl-F handler for find
42
95
  editorInstance.addKeyMap({
@@ -54,6 +107,10 @@ document.addEventListener('DOMContentLoaded', function() {
54
107
  'Cmd-S': function(cm) {
55
108
  document.getElementById('save-btn').click();
56
109
  return false;
110
+ },
111
+ 'Alt-W': function(cm) {
112
+ const current = cm.getOption('lineWrapping');
113
+ cm.setOption('lineWrapping', !current);
57
114
  }
58
115
  });
59
116
  // --- Custom floating find navigation panel ---
@@ -153,29 +210,26 @@ document.addEventListener('DOMContentLoaded', function() {
153
210
  window.addEventListener('resize', resizeEditor);
154
211
  setTimeout(resizeEditor, 0);
155
212
  editorInstance.setValue(initialContent);
213
+ updateThemeIcon();
156
214
 
157
- // Theme switcher logic
158
- var themeSwitcher = document.getElementById('theme-switcher');
159
- var themeIcon = document.getElementById('theme-icon');
160
- var isDark = true;
161
- function updateThemeIcon() {
162
- themeIcon.textContent = isDark ? '🌙' : '☀️';
163
- }
164
- themeSwitcher.addEventListener('click', function() {
165
- isDark = !isDark;
166
- if (isDark) {
167
- document.body.classList.remove('light-theme');
168
- editorInstance.setOption('theme', 'dracula');
169
- } else {
170
- document.body.classList.add('light-theme');
171
- editorInstance.setOption('theme', 'default');
172
- }
173
- updateThemeIcon();
174
- });
175
215
  // Set initial state
176
216
  document.body.classList.remove('light-theme');
177
217
  editorInstance.setOption('theme', 'dracula');
178
218
  updateThemeIcon();
219
+
220
+ // Theme switch button logic
221
+ var themeSwitcher = document.getElementById('theme-switcher');
222
+ if (themeSwitcher) {
223
+ themeSwitcher.addEventListener('click', function() {
224
+ var isLight = document.body.classList.toggle('light-theme');
225
+ if (isLight) {
226
+ editorInstance.setOption('theme', 'default');
227
+ } else {
228
+ editorInstance.setOption('theme', 'dracula');
229
+ }
230
+ updateThemeIcon();
231
+ });
232
+ }
179
233
  // Botão de Gravar
180
234
  var saveBtn = document.getElementById('save-btn');
181
235
  saveBtn.addEventListener('click', function() {
@@ -4,39 +4,92 @@ function getQueryParam(name) {
4
4
  return url.searchParams.get(name);
5
5
  }
6
6
  const filePath = getQueryParam('path');
7
- let initialContent = `# Bem-vindo ao TermWeb!\n# Este é um editor CodeMirror ao vivo.\n\nprint("Olá, janito.dev!")`;
8
- if (filePath) {
9
- fetch(`/api/explorer/${encodeURIComponent(filePath)}`)
10
- .then(resp => resp.json())
11
- .then(data => {
12
- if (data.type === 'file') {
13
- initialContent = data.content;
14
- if (window.editorInstance) {
15
- window.editorInstance.setValue(initialContent);
7
+
8
+ // Updates the theme icon based on the current theme
9
+ function updateThemeIcon() {
10
+ var icon = document.getElementById('theme-icon');
11
+ if (!icon) return;
12
+ if (document.body.classList.contains('light-theme')) {
13
+ icon.textContent = '☀️'; // Sun for light mode
14
+ icon.title = 'Switch to dark mode';
15
+ } else {
16
+ icon.textContent = '🌙'; // Moon for dark mode
17
+ icon.title = 'Switch to light mode';
18
+ }
19
+ }
20
+
21
+ document.addEventListener('DOMContentLoaded', function() {
22
+ // Display filename in header if present
23
+ if (filePath) {
24
+ const filename = filePath.split(/[\\\/]/).pop();
25
+ const filenameDisplay = document.getElementById('filename-display');
26
+ if (filenameDisplay) {
27
+ filenameDisplay.textContent = '— ' + filePath;
28
+ filenameDisplay.title = filePath;
29
+ }
30
+ }
31
+
32
+ let initialContent = "";
33
+ if (filePath) {
34
+ fetch(`/api/explorer/${encodeURIComponent(filePath)}`)
35
+ .then(resp => resp.json())
36
+ .then(data => {
37
+ if (data.type === 'file') {
38
+ initialContent = data.content;
39
+ if (window.editorInstance) {
40
+ window.editorInstance.setValue(initialContent);
41
+ }
42
+ } else if (data.error) {
43
+ initialContent = '# Error: ' + data.error;
44
+ if (window.editorInstance) {
45
+ window.editorInstance.setValue(initialContent);
46
+ }
16
47
  }
17
- } else if (data.error) {
18
- initialContent = '# Error: ' + data.error;
48
+ })
49
+ .catch(err => {
50
+ initialContent = '# Error ao carregar arquivo: ' + err;
19
51
  if (window.editorInstance) {
20
52
  window.editorInstance.setValue(initialContent);
21
53
  }
22
- }
23
- })
24
- .catch(err => {
25
- initialContent = '# Error ao carregar arquivo: ' + err;
26
- if (window.editorInstance) {
27
- window.editorInstance.setValue(initialContent);
28
- }
29
- });
30
- }
31
- document.addEventListener('DOMContentLoaded', function() {
54
+ });
55
+ }
56
+
57
+ // --- Detect file extension and set CodeMirror mode ---
58
+ function detectMode(filename) {
59
+ if (!filename) return 'python';
60
+ const ext = filename.split('.').pop().toLowerCase();
61
+ const map = {
62
+ 'py': 'python',
63
+ 'js': 'javascript',
64
+ 'json': 'javascript',
65
+ 'md': 'markdown',
66
+ 'html': {name: 'htmlmixed', scriptingModeSpec: 'django'},
67
+ 'htm': {name: 'htmlmixed', scriptingModeSpec: 'django'},
68
+ 'jinja': 'django',
69
+ 'j2': 'django',
70
+ 'jinja2': 'django',
71
+ 'css': 'css',
72
+ 'sh': 'shell',
73
+ 'bash': 'shell',
74
+ 'yml': 'yaml',
75
+ 'yaml': 'yaml',
76
+ };
77
+ return map[ext] || 'python';
78
+ }
79
+ var initialMode = detectMode(filePath);
32
80
  var editorInstance = CodeMirror.fromTextArea(document.getElementById('code'), {
33
81
  lineNumbers: true,
34
- mode: 'python',
82
+ mode: initialMode,
35
83
  theme: 'dracula',
36
84
  indentUnit: 4,
37
85
  tabSize: 4,
38
86
  styleActiveLine: true,
39
87
  });
88
+ // If file loaded later, update mode
89
+ if (filePath) {
90
+ const mode = detectMode(filePath);
91
+ editorInstance.setOption('mode', mode);
92
+ }
40
93
  window.editorInstance = editorInstance;
41
94
  // Add Ctrl-F handler for find
42
95
  editorInstance.addKeyMap({
@@ -45,15 +98,15 @@ document.addEventListener('DOMContentLoaded', function() {
45
98
  },
46
99
  'Cmd-F': function(cm) {
47
100
  cm.execCommand('find');
48
- }
49
- });
50
- // Add Ctrl+S/Cmd+S handler for save
51
- editorInstance.addKeyMap({
101
+ },
52
102
  'Ctrl-S': function(cm) {
53
103
  document.getElementById('save-btn').click();
104
+ // Prevent default browser save dialog
105
+ return false;
54
106
  },
55
107
  'Cmd-S': function(cm) {
56
108
  document.getElementById('save-btn').click();
109
+ return false;
57
110
  }
58
111
  });
59
112
  // --- Custom floating find navigation panel ---
@@ -153,41 +206,27 @@ document.addEventListener('DOMContentLoaded', function() {
153
206
  window.addEventListener('resize', resizeEditor);
154
207
  setTimeout(resizeEditor, 0);
155
208
  editorInstance.setValue(initialContent);
209
+ updateThemeIcon();
156
210
 
157
- // Theme switcher logic
158
- var themeSwitcher = document.getElementById('theme-switcher');
159
- var themeIcon = document.getElementById('theme-icon');
160
- var isDark = true;
161
- function updateThemeIcon() {
162
- themeIcon.textContent = isDark ? '🌙' : '☀️';
163
- }
164
- themeSwitcher.addEventListener('click', function() {
165
- isDark = !isDark;
166
- if (isDark) {
167
- document.body.classList.remove('light-theme');
168
- editorInstance.setOption('theme', 'dracula');
169
- } else {
170
- document.body.classList.add('light-theme');
171
- editorInstance.setOption('theme', 'default');
172
- }
173
- updateThemeIcon();
174
- });
175
211
  // Set initial state
176
212
  document.body.classList.remove('light-theme');
177
213
  editorInstance.setOption('theme', 'dracula');
178
214
  updateThemeIcon();
179
215
 
180
- // Toast notification logic
181
- function showToast(message) {
182
- var toast = document.getElementById('toast');
183
- toast.textContent = message;
184
- toast.className = 'toast show';
185
- setTimeout(function() {
186
- toast.className = 'toast';
187
- }, 1800);
216
+ // Theme switch button logic
217
+ var themeSwitcher = document.getElementById('theme-switcher');
218
+ if (themeSwitcher) {
219
+ themeSwitcher.addEventListener('click', function() {
220
+ var isLight = document.body.classList.toggle('light-theme');
221
+ if (isLight) {
222
+ editorInstance.setOption('theme', 'default');
223
+ } else {
224
+ editorInstance.setOption('theme', 'dracula');
225
+ }
226
+ updateThemeIcon();
227
+ });
188
228
  }
189
-
190
- // Save Button
229
+ // Botão de Gravar
191
230
  var saveBtn = document.getElementById('save-btn');
192
231
  saveBtn.addEventListener('click', function() {
193
232
  if (!filePath) {
@@ -205,9 +244,10 @@ document.addEventListener('DOMContentLoaded', function() {
205
244
  .then(resp => resp.json())
206
245
  .then(data => {
207
246
  if (data.success) {
208
- showToast('Saved!');
209
- saveBtn.textContent = 'Saved!';
210
- setTimeout(() => saveBtn.textContent = 'Save', 1200);
247
+ // Show popup
248
+ var popup = document.getElementById('save-popup');
249
+ popup.style.display = 'block';
250
+ setTimeout(() => { popup.style.display = 'none'; }, 1500);
211
251
  } else {
212
252
  alert('Error saving: ' + (data.error || 'desconhecido'));
213
253
  }
@@ -216,12 +256,4 @@ document.addEventListener('DOMContentLoaded', function() {
216
256
  alert('Error saving: ' + err);
217
257
  });
218
258
  });
219
-
220
- // Prevent browser default save dialog on Ctrl+S/Cmd+S
221
- window.addEventListener('keydown', function(e) {
222
- if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
223
- e.preventDefault();
224
- document.getElementById('save-btn').click();
225
- }
226
- });
227
259
  });
@@ -24,8 +24,7 @@
24
24
  <img src="/static/termicon.svg" alt="TermWeb Logo" class="header-logo">
25
25
  <span class="header-title">TermWeb</span>
26
26
  <span class="header-subtitle">Explorador de Projetos com IA</span>
27
- <button class="theme-switcher" id="theme-switcher" title="Alternate tema"><span id="theme-icon">🌙</span></button>
28
- </div>
27
+ </div>
29
28
  <div class="toolbar" id="explorer-toolbar">
30
29
 
31
30
  </div>
@@ -24,7 +24,7 @@
24
24
  <img src="/static/termicon.svg" alt="TermWeb Logo" class="header-logo">
25
25
  <span class="header-title">TermWeb</span>
26
26
  <span class="header-subtitle">Explorador de Projetos com IA</span>
27
- <button class="theme-switcher" id="theme-switcher" title="Alternar tema"><span id="theme-icon">🌙</span></button>
27
+ <button class="theme-switcher" id="theme-switcher" title="Alternate tema"><span id="theme-icon">🌙</span></button>
28
28
  </div>
29
29
  <div class="toolbar" id="explorer-toolbar">
30
30
 
@@ -20,7 +20,7 @@ body.light-theme {
20
20
  .header {
21
21
  background: #23272b;
22
22
  color: #f1f1f1;
23
- padding: 1em 2em;
23
+ padding: 0.2em 2em 1em 2em;
24
24
  font-size: 1.5em;
25
25
  font-weight: bold;
26
26
  letter-spacing: 0.04em;
@@ -180,27 +180,6 @@ body.light-theme .footer a {
180
180
  display: inline;
181
181
  }
182
182
 
183
- .theme-switcher {
184
- position: absolute;
185
- right: 20px;
186
- top: 10px;
187
- background: #444;
188
- color: #f1f1f1;
189
- border: none;
190
- border-radius: 4px;
191
- padding: 6px 14px;
192
- cursor: pointer;
193
- font-size: 1em;
194
- transition: background 0.2s, color 0.2s;
195
- }
196
- body.light-theme .theme-switcher {
197
- background: #ddd;
198
- color: #181a1b;
199
- }
200
- body.dark-theme .theme-switcher {
201
- background: #23272b;
202
- color: #f1f1f1;
203
- }
204
183
 
205
184
  /* Explorer file/dir links */
206
185
  .explorer-link, .explorer-link:visited {
@@ -20,7 +20,7 @@ body.light-theme {
20
20
  .header {
21
21
  background: #23272b;
22
22
  color: #f1f1f1;
23
- padding: 1em 2em;
23
+ padding: 0.2em 2em 1em 2em;
24
24
  font-size: 1.5em;
25
25
  font-weight: bold;
26
26
  letter-spacing: 0.04em;
@@ -183,14 +183,16 @@ body.light-theme .footer a {
183
183
  .theme-switcher {
184
184
  position: absolute;
185
185
  right: 20px;
186
- top: 10px;
186
+ top: 2px;
187
187
  background: #444;
188
188
  color: #f1f1f1;
189
189
  border: none;
190
190
  border-radius: 4px;
191
- padding: 6px 14px;
191
+ padding: 0px 6px;
192
+ margin-top: -0.2em;
193
+ margin-bottom: 2px;
192
194
  cursor: pointer;
193
- font-size: 1em;
195
+ font-size: 0.85em;
194
196
  transition: background 0.2s, color 0.2s;
195
197
  }
196
198
  body.light-theme .theme-switcher {
@@ -154,12 +154,6 @@ document.addEventListener('DOMContentLoaded', function() {
154
154
  // Initial theme
155
155
  var theme = localStorage.getItem('theme') || 'dark';
156
156
  setTheme(theme === 'dark');
157
- var themeSwitcher = document.getElementById('theme-switcher');
158
- if (themeSwitcher) {
159
- themeSwitcher.onclick = function() {
160
- setTheme(document.body.classList.contains('light-theme'));
161
- };
162
- }
163
157
  setExplorerView('list'); // Always use list view
164
158
  renderExplorer('.')
165
159
  });
@@ -16,7 +16,6 @@ function getPaiPath(path) {
16
16
  function setExplorerView(view) {
17
17
  explorerView = view;
18
18
  localStorage.setItem('explorerView', view);
19
-
20
19
  }
21
20
 
22
21
  function normalizeExplorerPath(path) {
@@ -133,7 +132,7 @@ window.renderCodePreview = function(container, content, mode) {
133
132
  } catch (e) {
134
133
  container.innerHTML = '<pre>' + (content ? String(content) : '') + '</pre>';
135
134
  }
136
- }
135
+ };
137
136
 
138
137
  // Theme switcher logic
139
138
  function setTheme(dark) {
@@ -0,0 +1,44 @@
1
+ import io
2
+ from rich.console import Console
3
+ from janito.rich_utils import RichPrinter
4
+
5
+
6
+ def test_print_info(capsys=None):
7
+ buf = io.StringIO()
8
+ printer = RichPrinter(
9
+ console=Console(file=buf, force_terminal=True, color_system=None)
10
+ )
11
+ printer.print_info("info message")
12
+ output = buf.getvalue()
13
+ assert "info message" in output
14
+ assert "cyan" in output or output # Style is present if rich renders ANSI
15
+
16
+
17
+ def test_print_error():
18
+ buf = io.StringIO()
19
+ printer = RichPrinter(
20
+ console=Console(file=buf, force_terminal=True, color_system=None)
21
+ )
22
+ printer.print_error("error message")
23
+ output = buf.getvalue()
24
+ assert "error message" in output
25
+
26
+
27
+ def test_print_warning():
28
+ buf = io.StringIO()
29
+ printer = RichPrinter(
30
+ console=Console(file=buf, force_terminal=True, color_system=None)
31
+ )
32
+ printer.print_warning("warning message")
33
+ output = buf.getvalue()
34
+ assert "warning message" in output
35
+
36
+
37
+ def test_print_magenta():
38
+ buf = io.StringIO()
39
+ printer = RichPrinter(
40
+ console=Console(file=buf, force_terminal=True, color_system=None)
41
+ )
42
+ printer.print_magenta("magenta message")
43
+ output = buf.getvalue()
44
+ assert "magenta message" in output