portacode 0.3.17.dev0__tar.gz → 0.3.17.dev2__tar.gz
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.
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/.claude/settings.local.json +2 -1
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/PKG-INFO +1 -1
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/_version.py +2 -2
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +13 -2
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/project_state_handlers.py +85 -25
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode.egg-info/PKG-INFO +1 -1
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/.claude/agents/communication-manager.md +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/.gitignore +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/.gitmodules +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/LICENSE +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/MANIFEST.in +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/Makefile +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/README.md +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/backup.sh +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/docker-compose.yaml +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/README.md +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/__init__.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/__main__.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/cli.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/README.md +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/__init__.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/client.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/README.md +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/__init__.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/base.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/registry.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/session.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/tab_factory.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/terminal_handlers.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/multiplex.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/terminal.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/data.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/keypair.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/service.py +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode.egg-info/SOURCES.txt +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode.egg-info/requires.txt +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode.egg-info/top_level.txt +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/pyproject.toml +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/restore.sh +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/setup.cfg +0 -0
- {portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/setup.py +0 -0
|
@@ -17,5 +17,5 @@ __version__: str
|
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
|
18
18
|
version_tuple: VERSION_TUPLE
|
|
19
19
|
|
|
20
|
-
__version__ = version = '0.3.17.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 3, 17, '
|
|
20
|
+
__version__ = version = '0.3.17.dev2'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 3, 17, 'dev2')
|
{portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md
RENAMED
|
@@ -247,6 +247,13 @@ Project state actions manage the state of project folders, including file struct
|
|
|
247
247
|
|
|
248
248
|
**Note:** Project state is automatically initialized when a client session connects with a `project_folder_path` property. No manual initialization command is required.
|
|
249
249
|
|
|
250
|
+
**Tab Management:** Open tabs are internally managed using a dictionary structure with unique keys to prevent duplicates and race conditions:
|
|
251
|
+
- File tabs use `file_path` as the unique key
|
|
252
|
+
- Diff tabs use a composite key: `diff:{file_path}:{from_ref}:{to_ref}:{from_hash}:{to_hash}`
|
|
253
|
+
- Untitled tabs use their `tab_id` as the unique key
|
|
254
|
+
|
|
255
|
+
This ensures that sending the same command multiple times (e.g., `project_state_diff_open` with identical parameters) will not create duplicate tabs but will instead activate the existing tab.
|
|
256
|
+
|
|
250
257
|
### `project_state_folder_expand`
|
|
251
258
|
|
|
252
259
|
Expands a folder in the project tree, loading its contents and enabling monitoring for that folder level. When a folder is expanded, the system proactively loads one level down for all subdirectories to enable immediate expansion in the UI. This action also scans items in the expanded folder and preloads content for any non-empty subdirectories.
|
|
@@ -279,6 +286,8 @@ Collapses a folder in the project tree, stopping monitoring for that folder leve
|
|
|
279
286
|
|
|
280
287
|
Marks a file as open in the project state, tracking it as part of the current editing session.
|
|
281
288
|
|
|
289
|
+
**Duplicate Prevention:** This action prevents creating duplicate file tabs by using the `file_path` as a unique key. If a file tab with the same path already exists, it will be activated instead of creating a new one.
|
|
290
|
+
|
|
282
291
|
**Payload Fields:**
|
|
283
292
|
|
|
284
293
|
* `project_id` (string, mandatory): The project ID from the initialized project state.
|
|
@@ -322,6 +331,8 @@ Sets the currently active tab in the project state. Only one tab can be active a
|
|
|
322
331
|
|
|
323
332
|
Opens a diff tab for comparing file versions at different points in the git timeline. This replaces the previous `project_state_create_diff_tab` action with a more efficient approach that doesn't require the client to provide file content, instead using git timeline references.
|
|
324
333
|
|
|
334
|
+
**Duplicate Prevention:** This action prevents creating duplicate diff tabs by using a unique key based on `file_path`, `from_ref`, `to_ref`, `from_hash`, and `to_hash`. If a diff tab with the same parameters already exists, it will be activated instead of creating a new one.
|
|
335
|
+
|
|
325
336
|
**Payload Fields:**
|
|
326
337
|
|
|
327
338
|
* `project_id` (string, mandatory): The project ID from the initialized project state.
|
|
@@ -578,7 +589,7 @@ Confirms that project state has been successfully initialized for a client sessi
|
|
|
578
589
|
* `is_staged` (boolean): Always true for staged changes.
|
|
579
590
|
* `unstaged_changes` (array, optional): Array of unstaged file changes with same structure as staged_changes but `is_staged` is always false.
|
|
580
591
|
* `untracked_files` (array, optional): Array of untracked files with same structure as staged_changes but `is_staged` is always false and `change_type` is always 'untracked'.
|
|
581
|
-
* `open_tabs` (array, mandatory): Array of tab objects currently open. Each tab object contains:
|
|
592
|
+
* `open_tabs` (array, mandatory): Array of tab objects currently open. Internally stored as a dictionary with unique keys to prevent duplicates, but serialized as an array for API responses. Each tab object contains:
|
|
582
593
|
* `tab_id` (string, mandatory): Unique identifier for the tab.
|
|
583
594
|
* `tab_type` (string, mandatory): Type of tab ("file", "diff", "untitled", "image", "audio", "video").
|
|
584
595
|
* `title` (string, mandatory): Display title for the tab.
|
|
@@ -619,7 +630,7 @@ Sent automatically when project state changes due to file system modifications,
|
|
|
619
630
|
* `git_branch` (string, optional): The current Git branch name if available.
|
|
620
631
|
* `git_status_summary` (object, optional): Updated summary of Git status counts.
|
|
621
632
|
* `git_detailed_status` (object, optional): Updated detailed Git status with comprehensive file change information and content hashes (same structure as in `project_state_initialized`).
|
|
622
|
-
* `open_tabs` (array, mandatory): Updated array of tab objects currently open.
|
|
633
|
+
* `open_tabs` (array, mandatory): Updated array of tab objects currently open. Internally stored as a dictionary with unique keys to prevent duplicates, but serialized as an array for API responses.
|
|
623
634
|
* `active_tab` (object, optional): Updated active tab object.
|
|
624
635
|
* `items` (array, mandatory): Updated flattened array of all visible file/folder items. Always includes root level items and one level down from the project root (since the project root is treated as expanded by default). Also includes items within explicitly expanded folders and one level down from each expanded folder. Each item object contains the following fields:
|
|
625
636
|
* `name` (string, mandatory): The file or directory name.
|
|
@@ -118,12 +118,12 @@ class ProjectState:
|
|
|
118
118
|
git_branch: Optional[str] = None
|
|
119
119
|
git_status_summary: Optional[Dict[str, int]] = None # Kept for backward compatibility
|
|
120
120
|
git_detailed_status: Optional[GitDetailedStatus] = None # New detailed git state
|
|
121
|
-
open_tabs:
|
|
121
|
+
open_tabs: Dict[str, 'TabInfo'] = None # Changed from List to Dict with unique keys
|
|
122
122
|
active_tab: Optional['TabInfo'] = None
|
|
123
123
|
|
|
124
124
|
def __post_init__(self):
|
|
125
125
|
if self.open_tabs is None:
|
|
126
|
-
self.open_tabs =
|
|
126
|
+
self.open_tabs = {}
|
|
127
127
|
if self.monitored_folders is None:
|
|
128
128
|
self.monitored_folders = []
|
|
129
129
|
|
|
@@ -172,27 +172,30 @@ class GitManager:
|
|
|
172
172
|
return {"is_tracked": False, "status": None, "is_ignored": False}
|
|
173
173
|
|
|
174
174
|
try:
|
|
175
|
-
rel_path = os.path.relpath(file_path, self.repo.working_dir)
|
|
175
|
+
rel_path = os.path.relpath(file_path, self.repo.working_dir)
|
|
176
176
|
|
|
177
|
-
# Check if ignored
|
|
177
|
+
# Check if ignored - GitPython handles path normalization internally
|
|
178
178
|
is_ignored = self.repo.ignored(rel_path)
|
|
179
179
|
if is_ignored:
|
|
180
180
|
return {"is_tracked": False, "status": "ignored", "is_ignored": True}
|
|
181
181
|
|
|
182
182
|
# For directories, only report status if they contain tracked or untracked files
|
|
183
183
|
if os.path.isdir(file_path):
|
|
184
|
-
# Check if directory contains any untracked files
|
|
185
|
-
|
|
184
|
+
# Check if directory contains any untracked files using path.startswith()
|
|
185
|
+
# This handles cross-platform path separators correctly
|
|
186
|
+
has_untracked = any(
|
|
187
|
+
os.path.commonpath([f, rel_path]) == rel_path and f != rel_path
|
|
188
|
+
for f in self.repo.untracked_files
|
|
189
|
+
)
|
|
186
190
|
if has_untracked:
|
|
187
191
|
return {"is_tracked": False, "status": "untracked", "is_ignored": False}
|
|
188
192
|
|
|
189
|
-
# Check if directory is dirty
|
|
193
|
+
# Check if directory is dirty - GitPython handles path normalization
|
|
190
194
|
if self.repo.is_dirty(path=rel_path):
|
|
191
195
|
return {"is_tracked": True, "status": "modified", "is_ignored": False}
|
|
192
196
|
|
|
193
|
-
#
|
|
197
|
+
# Check if directory has tracked files - let GitPython handle paths
|
|
194
198
|
try:
|
|
195
|
-
# Simple check: if ls-files returns anything, directory has tracked content
|
|
196
199
|
tracked_files = self.repo.git.ls_files(rel_path)
|
|
197
200
|
is_tracked = bool(tracked_files.strip())
|
|
198
201
|
status = "clean" if is_tracked else None
|
|
@@ -202,15 +205,15 @@ class GitManager:
|
|
|
202
205
|
|
|
203
206
|
# For files
|
|
204
207
|
else:
|
|
205
|
-
# Check if untracked
|
|
208
|
+
# Check if untracked - direct comparison works cross-platform
|
|
206
209
|
if rel_path in self.repo.untracked_files:
|
|
207
210
|
return {"is_tracked": False, "status": "untracked", "is_ignored": False}
|
|
208
211
|
|
|
209
|
-
# Check if tracked and dirty
|
|
212
|
+
# Check if tracked and dirty - GitPython handles path normalization
|
|
210
213
|
if self.repo.is_dirty(path=rel_path):
|
|
211
214
|
return {"is_tracked": True, "status": "modified", "is_ignored": False}
|
|
212
215
|
|
|
213
|
-
# Check if tracked and clean
|
|
216
|
+
# Check if tracked and clean - GitPython handles paths
|
|
214
217
|
try:
|
|
215
218
|
self.repo.git.ls_files(rel_path, error_unmatch=True)
|
|
216
219
|
return {"is_tracked": True, "status": "clean", "is_ignored": False}
|
|
@@ -577,7 +580,7 @@ class ProjectStateManager:
|
|
|
577
580
|
"git_branch": state.git_branch,
|
|
578
581
|
"git_status_summary": state.git_status_summary,
|
|
579
582
|
"git_detailed_status": asdict(state.git_detailed_status) if state.git_detailed_status else None,
|
|
580
|
-
"open_tabs": [self._serialize_tab_info(tab) for tab in state.open_tabs],
|
|
583
|
+
"open_tabs": [self._serialize_tab_info(tab) for tab in state.open_tabs.values()],
|
|
581
584
|
"active_tab": self._serialize_tab_info(state.active_tab) if state.active_tab else None,
|
|
582
585
|
"monitored_folders": [asdict(mf) for mf in state.monitored_folders],
|
|
583
586
|
"items": [self._serialize_file_item(item) for item in state.items]
|
|
@@ -950,9 +953,12 @@ class ProjectStateManager:
|
|
|
950
953
|
|
|
951
954
|
project_state = self.projects[client_session_key]
|
|
952
955
|
|
|
956
|
+
# Generate unique key for file tab
|
|
957
|
+
tab_key = generate_tab_key('file', file_path)
|
|
958
|
+
|
|
953
959
|
# Check if file is already open
|
|
954
|
-
|
|
955
|
-
|
|
960
|
+
if tab_key in project_state.open_tabs:
|
|
961
|
+
existing_tab = project_state.open_tabs[tab_key]
|
|
956
962
|
if set_active:
|
|
957
963
|
project_state.active_tab = existing_tab
|
|
958
964
|
self._write_debug_state()
|
|
@@ -964,7 +970,7 @@ class ProjectStateManager:
|
|
|
964
970
|
|
|
965
971
|
try:
|
|
966
972
|
new_tab = await tab_factory.create_file_tab(file_path)
|
|
967
|
-
project_state.open_tabs
|
|
973
|
+
project_state.open_tabs[tab_key] = new_tab
|
|
968
974
|
if set_active:
|
|
969
975
|
project_state.active_tab = new_tab
|
|
970
976
|
|
|
@@ -982,17 +988,25 @@ class ProjectStateManager:
|
|
|
982
988
|
|
|
983
989
|
project_state = self.projects[client_session_key]
|
|
984
990
|
|
|
985
|
-
# Find and remove the tab
|
|
986
|
-
|
|
991
|
+
# Find and remove the tab by searching through the dictionary values
|
|
992
|
+
tab_key_to_remove = None
|
|
993
|
+
tab_to_remove = None
|
|
994
|
+
for key, tab in project_state.open_tabs.items():
|
|
995
|
+
if tab.tab_id == tab_id:
|
|
996
|
+
tab_key_to_remove = key
|
|
997
|
+
tab_to_remove = tab
|
|
998
|
+
break
|
|
999
|
+
|
|
987
1000
|
if not tab_to_remove:
|
|
988
1001
|
return False
|
|
989
1002
|
|
|
990
|
-
project_state.open_tabs
|
|
1003
|
+
del project_state.open_tabs[tab_key_to_remove]
|
|
991
1004
|
|
|
992
1005
|
# Clear active tab if it was the closed tab
|
|
993
1006
|
if project_state.active_tab and project_state.active_tab.tab_id == tab_id:
|
|
994
1007
|
# Set active tab to the last remaining tab, or None if no tabs left
|
|
995
|
-
|
|
1008
|
+
remaining_tabs = list(project_state.open_tabs.values())
|
|
1009
|
+
project_state.active_tab = remaining_tabs[-1] if remaining_tabs else None
|
|
996
1010
|
|
|
997
1011
|
self._write_debug_state()
|
|
998
1012
|
return True
|
|
@@ -1005,8 +1019,12 @@ class ProjectStateManager:
|
|
|
1005
1019
|
project_state = self.projects[client_session_key]
|
|
1006
1020
|
|
|
1007
1021
|
if tab_id:
|
|
1008
|
-
# Find the tab by ID
|
|
1009
|
-
tab =
|
|
1022
|
+
# Find the tab by ID in the dictionary values
|
|
1023
|
+
tab = None
|
|
1024
|
+
for t in project_state.open_tabs.values():
|
|
1025
|
+
if t.tab_id == tab_id:
|
|
1026
|
+
tab = t
|
|
1027
|
+
break
|
|
1010
1028
|
if not tab:
|
|
1011
1029
|
return False
|
|
1012
1030
|
project_state.active_tab = tab
|
|
@@ -1030,6 +1048,19 @@ class ProjectStateManager:
|
|
|
1030
1048
|
logger.error("Cannot create diff tab: not a git repository")
|
|
1031
1049
|
return False
|
|
1032
1050
|
|
|
1051
|
+
# Generate unique key for diff tab
|
|
1052
|
+
tab_key = generate_tab_key('diff', file_path,
|
|
1053
|
+
from_ref=from_ref, to_ref=to_ref,
|
|
1054
|
+
from_hash=from_hash, to_hash=to_hash)
|
|
1055
|
+
|
|
1056
|
+
# Check if this diff tab is already open
|
|
1057
|
+
if tab_key in project_state.open_tabs:
|
|
1058
|
+
existing_tab = project_state.open_tabs[tab_key]
|
|
1059
|
+
project_state.active_tab = existing_tab
|
|
1060
|
+
logger.info(f"Diff tab already exists, activating: {tab_key}")
|
|
1061
|
+
self._write_debug_state()
|
|
1062
|
+
return True
|
|
1063
|
+
|
|
1033
1064
|
try:
|
|
1034
1065
|
# Get content based on the reference type
|
|
1035
1066
|
original_content = ""
|
|
@@ -1100,7 +1131,7 @@ class ProjectStateManager:
|
|
|
1100
1131
|
'diff_timeline': True
|
|
1101
1132
|
})
|
|
1102
1133
|
|
|
1103
|
-
project_state.open_tabs
|
|
1134
|
+
project_state.open_tabs[tab_key] = diff_tab
|
|
1104
1135
|
project_state.active_tab = diff_tab
|
|
1105
1136
|
|
|
1106
1137
|
logger.info(f"Created timeline diff tab for: {file_path} ({from_ref} → {to_ref})")
|
|
@@ -1194,7 +1225,7 @@ class ProjectStateManager:
|
|
|
1194
1225
|
"git_branch": project_state.git_branch,
|
|
1195
1226
|
"git_status_summary": project_state.git_status_summary,
|
|
1196
1227
|
"git_detailed_status": str(project_state.git_detailed_status) if project_state.git_detailed_status else None,
|
|
1197
|
-
"open_tabs": tuple((tab.tab_id, tab.tab_type, tab.title) for tab in project_state.open_tabs),
|
|
1228
|
+
"open_tabs": tuple((tab.tab_id, tab.tab_type, tab.title) for tab in project_state.open_tabs.values()),
|
|
1198
1229
|
"active_tab": project_state.active_tab.tab_id if project_state.active_tab else None,
|
|
1199
1230
|
"items_count": len(project_state.items),
|
|
1200
1231
|
"monitored_folders": tuple((mf.folder_path, mf.is_expanded) for mf in sorted(project_state.monitored_folders, key=lambda x: x.folder_path))
|
|
@@ -1218,7 +1249,7 @@ class ProjectStateManager:
|
|
|
1218
1249
|
"git_branch": project_state.git_branch,
|
|
1219
1250
|
"git_status_summary": project_state.git_status_summary,
|
|
1220
1251
|
"git_detailed_status": asdict(project_state.git_detailed_status) if project_state.git_detailed_status else None,
|
|
1221
|
-
"open_tabs": [self._serialize_tab_info(tab) for tab in project_state.open_tabs],
|
|
1252
|
+
"open_tabs": [self._serialize_tab_info(tab) for tab in project_state.open_tabs.values()],
|
|
1222
1253
|
"active_tab": self._serialize_tab_info(project_state.active_tab) if project_state.active_tab else None,
|
|
1223
1254
|
"items": [self._serialize_file_item(item) for item in project_state.items],
|
|
1224
1255
|
"timestamp": time.time(),
|
|
@@ -1271,6 +1302,35 @@ class ProjectStateManager:
|
|
|
1271
1302
|
logger.info("Cleaned up %d project states", len(keys_to_remove))
|
|
1272
1303
|
|
|
1273
1304
|
|
|
1305
|
+
def generate_tab_key(tab_type: str, file_path: str, **kwargs) -> str:
|
|
1306
|
+
"""Generate a unique key for a tab.
|
|
1307
|
+
|
|
1308
|
+
Args:
|
|
1309
|
+
tab_type: Type of tab ('file', 'diff', 'untitled', etc.)
|
|
1310
|
+
file_path: Path to the file
|
|
1311
|
+
**kwargs: Additional parameters for diff tabs (from_ref, to_ref, from_hash, to_hash)
|
|
1312
|
+
|
|
1313
|
+
Returns:
|
|
1314
|
+
Unique string key for the tab
|
|
1315
|
+
"""
|
|
1316
|
+
import uuid
|
|
1317
|
+
|
|
1318
|
+
if tab_type == 'file':
|
|
1319
|
+
return file_path
|
|
1320
|
+
elif tab_type == 'diff':
|
|
1321
|
+
from_ref = kwargs.get('from_ref', '')
|
|
1322
|
+
to_ref = kwargs.get('to_ref', '')
|
|
1323
|
+
from_hash = kwargs.get('from_hash', '')
|
|
1324
|
+
to_hash = kwargs.get('to_hash', '')
|
|
1325
|
+
return f"diff:{file_path}:{from_ref}:{to_ref}:{from_hash}:{to_hash}"
|
|
1326
|
+
elif tab_type == 'untitled':
|
|
1327
|
+
# For untitled tabs, use the tab_id as the key since they don't have a file path
|
|
1328
|
+
return kwargs.get('tab_id', str(uuid.uuid4()))
|
|
1329
|
+
else:
|
|
1330
|
+
# For other tab types, use file_path if available, otherwise tab_id
|
|
1331
|
+
return file_path if file_path else kwargs.get('tab_id', str(uuid.uuid4()))
|
|
1332
|
+
|
|
1333
|
+
|
|
1274
1334
|
# Helper function for other handlers to get/create project state manager
|
|
1275
1335
|
def _get_or_create_project_state_manager(context: Dict[str, Any], control_channel) -> 'ProjectStateManager':
|
|
1276
1336
|
"""Get or create project state manager with debug setup."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/file_handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/system_handlers.py
RENAMED
|
File without changes
|
{portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/tab_factory.py
RENAMED
|
File without changes
|
{portacode-0.3.17.dev0 → portacode-0.3.17.dev2}/portacode/connection/handlers/terminal_handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|