thonny-codemate 0.1.4__py3-none-any.whl → 0.1.5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thonny-codemate
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: A Thonny IDE plugin that provides AI-powered coding assistance using local and cloud LLMs
5
5
  Author-email: tokoroten <shinta.nakayama@gmail.com>
6
6
  License: MIT
@@ -174,8 +174,15 @@ huggingface-cli download bartowski/Llama-3.2-1B-Instruct-GGUF Llama-3.2-1B-Instr
174
174
  4. **Code Generation**:
175
175
  - Write a comment describing what you want
176
176
  - Right-click and choose "Generate from Comment"
177
- - Or use the AI Assistant panel for interactive chat
178
- 5. **Error Fixing**:
177
+ 5. **Edit Mode (New in v0.1.5)**:
178
+ - Switch to "Edit" mode in the LLM Assistant panel
179
+ - Type your modification request (e.g., "Add error handling to this function")
180
+ - The AI will modify your code directly in the editor
181
+ - Works with selected text or entire file
182
+ 6. **Interactive Chat**:
183
+ - Use the AI Assistant panel for general questions
184
+ - Include context from your current file with the checkbox
185
+ 7. **Error Fixing**:
179
186
  - When you encounter an error, click "Explain Error" in the assistant panel
180
187
  - The AI will analyze the error and suggest fixes
181
188
 
@@ -1,8 +1,8 @@
1
- thonny_codemate-0.1.4.dist-info/licenses/LICENSE,sha256=f6YK7MRRtaWl2brMAtg_aGrsLK_i4rVyho0y1PdnFi0,1065
2
- thonnycontrib/__init__.py,sha256=7INAjyU8ojDRVyo0y1OQh3ynMPRGqBJZL4fa--i3elk,33
3
- thonnycontrib/thonny_codemate/__init__.py,sha256=vslo9LDAl7QUJD5DAM6RjelXiu1_nIvLtS2FTzhskgU,13487
1
+ thonny_codemate-0.1.5.dist-info/licenses/LICENSE,sha256=6Mww21sIIHwlMy-By5wl0oPcegzkWF2wE0vi_ZSzz3g,1065
2
+ thonnycontrib/thonny_codemate/__init__.py,sha256=5uJvG0Oah15EVvARAZxN4neMm2xJcsOBRw_xRcaEG4g,13487
4
3
  thonnycontrib/thonny_codemate/api.py,sha256=YLXjPsB6cykgRwyjzrUBOGFtw0eu4BQgr1AiMbj8e7c,4259
5
4
  thonnycontrib/thonny_codemate/context_manager.py,sha256=K83M-VlBy7DR1xzedyPT8NViqnR9CmX95YmXnDBzRFM,11274
5
+ thonnycontrib/thonny_codemate/edit_mode_handler.py,sha256=7i-FTETFcIOu7lWjPa-zXH27gWY4w9VRg5YcSQ_wzDQ,7612
6
6
  thonnycontrib/thonny_codemate/external_providers.py,sha256=PmIRY_lLOILCAZa4mpP3yB9PU7ZWSddWjYPf2S7vkyY,29016
7
7
  thonnycontrib/thonny_codemate/i18n.py,sha256=tq1pBwNLYgQtJBXH36MmTOl1sRuaPaXgSyMaAXVIIPU,28846
8
8
  thonnycontrib/thonny_codemate/llm_client.py,sha256=Ojk8oxNI7tKAzVEtaoSwOLVisT1cOYdXf9Qw56eC2L4,33767
@@ -12,7 +12,7 @@ thonnycontrib/thonny_codemate/performance_monitor.py,sha256=L6gIin6oBzplRgZB676A
12
12
  thonnycontrib/thonny_codemate/prompts.py,sha256=bV4Qjr8SjqWBnnUIk-g-R58sRGbyZOauvlWvNO9GkGg,3885
13
13
  thonnycontrib/thonny_codemate/ui/__init__.py,sha256=SDKZ1vYdsb14_jHIP4wTeM8Vqkog8z0PkWquS3-mQIY,43
14
14
  thonnycontrib/thonny_codemate/ui/chat_view.py,sha256=veyFSCJUOPVFUt1qJochKTvHQi49u3fzxKQ0Nti0NNc,29203
15
- thonnycontrib/thonny_codemate/ui/chat_view_html.py,sha256=86UFq4imEeYgPRU4QuXzKa_u2GG4GvoA9xQVpmLe7dg,53661
15
+ thonnycontrib/thonny_codemate/ui/chat_view_html.py,sha256=yul_leokkFdf_LGEME8FYVfPSA_JNP_f3Hv4QyFvDew,60866
16
16
  thonnycontrib/thonny_codemate/ui/custom_prompt_dialog.py,sha256=LEemjMmHM_KcidEsUhOOffa1DzCauOXNiUqlH2wGjts,5973
17
17
  thonnycontrib/thonny_codemate/ui/markdown_renderer.py,sha256=K1vHLTn6EocPRxb7fXMKGsSFD6iY-OhFTqMY_dBkiRw,15649
18
18
  thonnycontrib/thonny_codemate/ui/model_download_dialog.py,sha256=1qzqCL_2J_cXCh9OvUdaPAyouBm76BgfyUlPRkNCDGw,13701
@@ -21,7 +21,7 @@ thonnycontrib/thonny_codemate/utils/__init__.py,sha256=xjA_6r7WFCf3RbCNnZhwFOSap
21
21
  thonnycontrib/thonny_codemate/utils/constants.py,sha256=l6KKItPClPy7IWZKH53Kpq-PNMfJogTYIZ2OGKTZO8w,3159
22
22
  thonnycontrib/thonny_codemate/utils/error_messages.py,sha256=kOucGfSwBIOE23GsVAIcQYY7YbKjezIuOxiIM1wTKY4,3601
23
23
  thonnycontrib/thonny_codemate/utils/unified_error_handler.py,sha256=yvrSiXC3Kzro4KLL6lDLMBRfdff79PLvBF1yjHFhv9E,10769
24
- thonny_codemate-0.1.4.dist-info/METADATA,sha256=jHCA9FGbVa8l4HkHJuUJXPxePpPK7BuI_Jp-VmVplkU,10870
25
- thonny_codemate-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
- thonny_codemate-0.1.4.dist-info/top_level.txt,sha256=thF9WQtY_lcNKOOR4SQeeWLAVWb4i8XhdGC7pEMAOHY,14
27
- thonny_codemate-0.1.4.dist-info/RECORD,,
24
+ thonny_codemate-0.1.5.dist-info/METADATA,sha256=g1mOYKzg0jD93DgLMlOMsY35otT9PCIEewvjf8NraDg,11227
25
+ thonny_codemate-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
+ thonny_codemate-0.1.5.dist-info/top_level.txt,sha256=thF9WQtY_lcNKOOR4SQeeWLAVWb4i8XhdGC7pEMAOHY,14
27
+ thonny_codemate-0.1.5.dist-info/RECORD,,
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 tokoroten
3
+ Copyright (c) 2025 tokoroten
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -31,7 +31,7 @@ logger = logging.getLogger(__name__)
31
31
  logger.disabled = True
32
32
 
33
33
  # プラグインのバージョン
34
- __version__ = "0.1.4"
34
+ __version__ = "0.1.5"
35
35
 
36
36
  # グローバル変数でプラグインの状態を管理
37
37
  _plugin_loaded = False
@@ -0,0 +1,218 @@
1
+ """
2
+ Edit mode handler for single file editing
3
+ Inspired by VSCode Copilot Chat's edit functionality
4
+ """
5
+ import re
6
+ import tkinter as tk
7
+ from typing import Optional, Tuple, List
8
+ from pathlib import Path
9
+ import difflib
10
+ import logging
11
+
12
+ from thonny import get_workbench
13
+ from .i18n import tr
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class EditModeHandler:
19
+ """Handles edit mode functionality for modifying code in the current file"""
20
+
21
+ # Prompt template for edit mode
22
+ EDIT_PROMPT_TEMPLATE = """You are an AI programming assistant specialized in modifying code.
23
+
24
+ Instructions:
25
+ 1. Provide the complete modified code
26
+ 2. Use '# ...existing code...' to represent unchanged regions (this saves tokens)
27
+ 3. Preserve the original indentation and coding style
28
+ 4. Focus only on the requested changes
29
+ 5. Include helpful comments for significant changes
30
+
31
+ Current file: {filename}
32
+ Language: {language}
33
+
34
+ Current code:
35
+ ```{language}
36
+ {content}
37
+ ```
38
+
39
+ {selection_info}
40
+
41
+ User request: {user_prompt}
42
+
43
+ Please provide the modified code. Start your response with a code block containing the updated code:
44
+ """
45
+
46
+ SELECTION_TEMPLATE = """Selected region (lines {start_line}-{end_line}):
47
+ ```{language}
48
+ {selected_text}
49
+ ```
50
+
51
+ Focus your changes primarily on the selected region, but you may modify other parts if necessary.
52
+ """
53
+
54
+ def __init__(self, llm_client):
55
+ self.llm_client = llm_client
56
+ self.workbench = get_workbench()
57
+
58
+ def build_edit_prompt(self, user_prompt: str, filename: str, content: str,
59
+ selection: Optional[Tuple[str, int, int]] = None) -> str:
60
+ """Build the prompt for edit mode"""
61
+ # Detect language from filename
62
+ language = self._detect_language(filename)
63
+
64
+ # Handle selection if provided
65
+ selection_info = ""
66
+ if selection:
67
+ selected_text, start_line, end_line = selection
68
+ selection_info = self.SELECTION_TEMPLATE.format(
69
+ language=language,
70
+ selected_text=selected_text,
71
+ start_line=start_line,
72
+ end_line=end_line
73
+ )
74
+
75
+ return self.EDIT_PROMPT_TEMPLATE.format(
76
+ filename=filename or "Untitled",
77
+ language=language,
78
+ content=content,
79
+ selection_info=selection_info,
80
+ user_prompt=user_prompt
81
+ )
82
+
83
+ def _detect_language(self, filename: str) -> str:
84
+ """Detect programming language from filename"""
85
+ if not filename:
86
+ return "python"
87
+
88
+ ext = Path(filename).suffix.lower()
89
+ language_map = {
90
+ '.py': 'python',
91
+ '.js': 'javascript',
92
+ '.java': 'java',
93
+ '.cpp': 'cpp',
94
+ '.c': 'c',
95
+ '.cs': 'csharp',
96
+ '.rb': 'ruby',
97
+ '.go': 'go',
98
+ '.rs': 'rust',
99
+ '.php': 'php',
100
+ '.html': 'html',
101
+ '.css': 'css',
102
+ '.sql': 'sql',
103
+ '.sh': 'bash',
104
+ '.yml': 'yaml',
105
+ '.yaml': 'yaml',
106
+ '.json': 'json',
107
+ '.xml': 'xml'
108
+ }
109
+ return language_map.get(ext, 'text')
110
+
111
+ def extract_code_block(self, response: str) -> Optional[str]:
112
+ """Extract the first code block from LLM response"""
113
+ # Pattern to match code blocks with optional language specifier
114
+ pattern = r'```(?:\w+)?\s*\n(.*?)```'
115
+ matches = re.findall(pattern, response, re.DOTALL)
116
+
117
+ if matches:
118
+ return matches[0].strip()
119
+
120
+ # If no code block found, check if the entire response might be code
121
+ # (sometimes LLMs return code without backticks)
122
+ lines = response.strip().split('\n')
123
+ if len(lines) > 3 and any(line.strip().startswith(('def ', 'class ', 'import ', 'from ')) for line in lines):
124
+ return response.strip()
125
+
126
+ return None
127
+
128
+ def expand_existing_code_markers(self, modified_code: str, original_code: str) -> str:
129
+ """Expand '# ...existing code...' markers with actual code"""
130
+ if '# ...existing code...' not in modified_code:
131
+ return modified_code
132
+
133
+ original_lines = original_code.split('\n')
134
+ modified_lines = modified_code.split('\n')
135
+ result_lines = []
136
+
137
+ original_idx = 0
138
+
139
+ for line in modified_lines:
140
+ if '# ...existing code...' in line.strip():
141
+ # Skip this marker
142
+ indent = len(line) - len(line.lstrip())
143
+
144
+ # Find the next matching line in modified code
145
+ next_modified_idx = modified_lines.index(line) + 1
146
+ next_modified_line = None
147
+ while next_modified_idx < len(modified_lines):
148
+ next_line = modified_lines[next_modified_idx]
149
+ if next_line.strip() and '# ...existing code...' not in next_line:
150
+ next_modified_line = next_line.strip()
151
+ break
152
+ next_modified_idx += 1
153
+
154
+ # Copy original lines until we find the next modified line
155
+ while original_idx < len(original_lines):
156
+ orig_line = original_lines[original_idx]
157
+ if next_modified_line and orig_line.strip() == next_modified_line:
158
+ break
159
+ result_lines.append(orig_line)
160
+ original_idx += 1
161
+ else:
162
+ result_lines.append(line)
163
+ # Advance original_idx if this line matches
164
+ if original_idx < len(original_lines) and line.strip() == original_lines[original_idx].strip():
165
+ original_idx += 1
166
+
167
+ return '\n'.join(result_lines)
168
+
169
+ def create_diff(self, original: str, modified: str) -> List[str]:
170
+ """Create a unified diff between original and modified code"""
171
+ original_lines = original.splitlines(keepends=True)
172
+ modified_lines = modified.splitlines(keepends=True)
173
+
174
+ diff = list(difflib.unified_diff(
175
+ original_lines,
176
+ modified_lines,
177
+ fromfile='Original',
178
+ tofile='Modified',
179
+ lineterm=''
180
+ ))
181
+
182
+ return diff
183
+
184
+ def apply_edit(self, editor, new_code: str) -> bool:
185
+ """Apply the edit to the editor"""
186
+ try:
187
+ text_widget = editor.get_text_widget()
188
+
189
+ # Save cursor position
190
+ cursor_pos = text_widget.index("insert")
191
+
192
+ # Replace content
193
+ text_widget.delete("1.0", tk.END)
194
+ text_widget.insert("1.0", new_code)
195
+
196
+ # Restore cursor position if possible
197
+ try:
198
+ text_widget.mark_set("insert", cursor_pos)
199
+ except:
200
+ pass
201
+
202
+ return True
203
+
204
+ except Exception as e:
205
+ logger.error(f"Failed to apply edit: {e}")
206
+ return False
207
+
208
+ def get_selection_info(self, editor) -> Optional[Tuple[str, int, int]]:
209
+ """Get selected text and line numbers if any"""
210
+ text_widget = editor.get_text_widget()
211
+
212
+ if text_widget.tag_ranges("sel"):
213
+ selected_text = text_widget.get("sel.first", "sel.last")
214
+ start_line = int(text_widget.index("sel.first").split(".")[0])
215
+ end_line = int(text_widget.index("sel.last").split(".")[0])
216
+ return (selected_text, start_line, end_line)
217
+
218
+ return None
@@ -105,6 +105,9 @@ class LLMChatViewHTML(ttk.Frame):
105
105
 
106
106
  # 会話履歴を読み込む
107
107
  self._load_chat_history()
108
+
109
+ # EditModeHandlerを初期化
110
+ self.edit_mode_handler = None
108
111
 
109
112
  def _show_fallback_ui(self):
110
113
  """tkinterwebが利用できない場合のフォールバックUI"""
@@ -248,6 +251,27 @@ class LLMChatViewHTML(ttk.Frame):
248
251
 
249
252
  def _create_buttons(self, button_frame):
250
253
  """ボタン類を作成"""
254
+ # モード選択フレーム
255
+ mode_frame = ttk.LabelFrame(button_frame, text=tr("Mode"), padding=2)
256
+ mode_frame.pack(side=tk.LEFT, padx=5)
257
+
258
+ self.mode_var = tk.StringVar(value="chat")
259
+ ttk.Radiobutton(
260
+ mode_frame,
261
+ text=tr("Chat"),
262
+ variable=self.mode_var,
263
+ value="chat",
264
+ command=self._on_mode_change
265
+ ).pack(side=tk.LEFT, padx=2)
266
+
267
+ ttk.Radiobutton(
268
+ mode_frame,
269
+ text=tr("Edit"),
270
+ variable=self.mode_var,
271
+ value="edit",
272
+ command=self._on_mode_change
273
+ ).pack(side=tk.LEFT, padx=2)
274
+
251
275
  # Sendボタン
252
276
  self.send_button = ttk.Button(
253
277
  button_frame,
@@ -651,6 +675,12 @@ class LLMChatViewHTML(ttk.Frame):
651
675
  # UIをクリア
652
676
  self.input_text.delete("1.0", tk.END)
653
677
 
678
+ # Edit modeの場合は特別な処理
679
+ if self.mode_var.get() == "edit":
680
+ self._handle_edit_mode(message)
681
+ return
682
+
683
+ # Chat mode(通常の処理)
654
684
  # コンテキスト情報を取得して表示メッセージを作成
655
685
  context_info = self._get_context_info()
656
686
  display_message = self._format_display_message(message, context_info)
@@ -913,6 +943,8 @@ Full file content:
913
943
  self._handle_token(content)
914
944
  elif msg_type == "complete":
915
945
  self._handle_completion()
946
+ elif msg_type == "edit_complete":
947
+ self._handle_edit_completion(content)
916
948
  elif msg_type == "error":
917
949
  self._handle_error(content)
918
950
  elif msg_type == "info":
@@ -980,6 +1012,56 @@ Full file content:
980
1012
  if self._stop_generation:
981
1013
  self._add_message("system", tr("[Generation stopped by user]"))
982
1014
 
1015
+ def _handle_edit_completion(self, full_response: str):
1016
+ """Edit mode完了時の処理"""
1017
+ # 通常の完了処理
1018
+ self._handle_completion()
1019
+
1020
+ # 中止された場合のチェック
1021
+ if self._stop_generation:
1022
+ self._add_message("system", tr("⚠️ Edit generation was stopped. Changes were not applied."))
1023
+ return
1024
+
1025
+ # コードブロックを抽出
1026
+ new_code = self.edit_mode_handler.extract_code_block(full_response)
1027
+
1028
+ if not new_code:
1029
+ self._add_message("system", tr("No code changes were generated. Please try rephrasing your request."))
1030
+ return
1031
+
1032
+ # エディタを取得
1033
+ workbench = get_workbench()
1034
+ editor = workbench.get_editor_notebook().get_current_editor()
1035
+ if not editor:
1036
+ self._add_message("system", tr("Editor was closed. Cannot apply changes."))
1037
+ return
1038
+
1039
+ # 現在のコードを取得
1040
+ text_widget = editor.get_text_widget()
1041
+ original_code = text_widget.get("1.0", tk.END).strip()
1042
+
1043
+ # "# ...existing code..." マーカーを展開
1044
+ try:
1045
+ expanded_code = self.edit_mode_handler.expand_existing_code_markers(new_code, original_code)
1046
+ except Exception as e:
1047
+ logger.error(f"Failed to expand code markers: {e}")
1048
+ expanded_code = new_code # フォールバック
1049
+
1050
+ # 差分を作成して表示(オプション)
1051
+ diff_lines = self.edit_mode_handler.create_diff(original_code, expanded_code)
1052
+
1053
+ # 変更を適用
1054
+ if self.edit_mode_handler.apply_edit(editor, expanded_code):
1055
+ self._add_message("system", tr("✅ Changes applied successfully!"))
1056
+
1057
+ # 差分のサマリーを表示
1058
+ added = sum(1 for line in diff_lines if line.startswith('+') and not line.startswith('+++'))
1059
+ removed = sum(1 for line in diff_lines if line.startswith('-') and not line.startswith('---'))
1060
+ if added or removed:
1061
+ self._add_message("system", f"📊 {added} lines added, {removed} lines removed")
1062
+ else:
1063
+ self._add_message("system", tr("❌ Failed to apply changes."))
1064
+
983
1065
  def _handle_error(self, error_message: str):
984
1066
  """エラーを処理"""
985
1067
  # ストリーミングエリアを非表示
@@ -1194,6 +1276,98 @@ Full file content:
1194
1276
  # 履歴もクリア
1195
1277
  self._save_chat_history()
1196
1278
 
1279
+ def _handle_edit_mode(self, user_prompt: str):
1280
+ """Edit modeでのメッセージ処理"""
1281
+ # エディタの情報を取得
1282
+ workbench = get_workbench()
1283
+ editor = workbench.get_editor_notebook().get_current_editor()
1284
+ if not editor:
1285
+ self._add_message("system", tr("No active editor. Please open a file to edit."))
1286
+ return
1287
+
1288
+ # ファイル情報を取得
1289
+ filename = editor.get_filename() or "Untitled"
1290
+ text_widget = editor.get_text_widget()
1291
+ content = text_widget.get("1.0", tk.END).strip()
1292
+
1293
+ if not content:
1294
+ self._add_message("system", tr("The editor is empty. Please write some code first."))
1295
+ return
1296
+
1297
+ # 選択範囲を取得(あれば)
1298
+ selection_info = self.edit_mode_handler.get_selection_info(editor)
1299
+
1300
+ # メッセージを表示
1301
+ selection_text = ""
1302
+ if selection_info:
1303
+ _, start_line, end_line = selection_info
1304
+ selection_text = f" (lines {start_line}-{end_line})"
1305
+ self._add_message("user", f"{user_prompt}\n\n[Edit Mode: {Path(filename).name}{selection_text}]")
1306
+
1307
+ # 生成を開始
1308
+ self._start_edit_generation(user_prompt, filename, content, selection_info)
1309
+
1310
+ def _start_edit_generation(self, user_prompt: str, filename: str, content: str, selection_info):
1311
+ """Edit mode用の生成を開始"""
1312
+ if self._processing:
1313
+ return
1314
+
1315
+ self._processing = True
1316
+ self._stop_generation = False
1317
+ self.send_button.config(text=tr("Stop"), state=tk.NORMAL) # Stop ボタンを有効化
1318
+
1319
+ # ストリーミングの準備
1320
+ self._start_generating_animation()
1321
+
1322
+ # バックグラウンドで生成
1323
+ def generate():
1324
+ try:
1325
+ # Edit用のプロンプトを構築
1326
+ prompt = self.edit_mode_handler.build_edit_prompt(
1327
+ user_prompt, filename, content, selection_info
1328
+ )
1329
+
1330
+ # LLMで生成
1331
+ from .. import get_llm_client
1332
+ llm_client = get_llm_client()
1333
+
1334
+ # 応答を収集
1335
+ full_response = ""
1336
+ for token in llm_client.generate_stream(prompt):
1337
+ if self._stop_generation:
1338
+ # 中止された場合もedit_completeを送信(部分的な応答で処理)
1339
+ self.message_queue.put(("edit_complete", full_response))
1340
+ return
1341
+ full_response += token
1342
+ self.message_queue.put(("token", token))
1343
+
1344
+ # コードブロックを抽出
1345
+ self.message_queue.put(("edit_complete", full_response))
1346
+
1347
+ except Exception as e:
1348
+ self.message_queue.put(("error", str(e)))
1349
+
1350
+ import threading
1351
+ thread = threading.Thread(target=generate, daemon=True)
1352
+ thread.start()
1353
+
1354
+ def _on_mode_change(self):
1355
+ """モード変更時の処理"""
1356
+ mode = self.mode_var.get()
1357
+ if mode == "edit":
1358
+ # Edit modeに切り替え
1359
+ self.context_check.config(state=tk.DISABLED)
1360
+ self.context_var.set(True) # Edit modeでは常にコンテキストを含む
1361
+
1362
+ # EditModeHandlerを初期化(必要な場合)
1363
+ if not self.edit_mode_handler:
1364
+ from ..edit_mode_handler import EditModeHandler
1365
+ from .. import get_llm_client
1366
+ self.edit_mode_handler = EditModeHandler(get_llm_client())
1367
+ else:
1368
+ # Chat modeに戻る
1369
+ self.context_check.config(state=tk.NORMAL)
1370
+
1197
1371
  def _toggle_context(self):
1198
1372
  """コンテキストの有効/無効を切り替え"""
1199
1373
  if self.context_var.get():
thonnycontrib/__init__.py DELETED
@@ -1 +0,0 @@
1
- # thonnycontrib namespace package