portacode 0.3.19.dev0__tar.gz → 0.3.19.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.19.dev0 → portacode-0.3.19.dev2}/.claude/settings.local.json +2 -1
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/PKG-INFO +2 -1
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/_version.py +2 -2
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/project_state_handlers.py +219 -1
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode.egg-info/PKG-INFO +2 -1
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode.egg-info/requires.txt +1 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/setup.py +1 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/.claude/agents/communication-manager.md +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/.gitignore +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/.gitmodules +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/LICENSE +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/MANIFEST.in +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/Makefile +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/README.md +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/backup.sh +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/docker-compose.yaml +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/README.md +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/__init__.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/__main__.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/cli.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/README.md +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/__init__.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/client.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/README.md +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/__init__.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/base.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/registry.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/session.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/tab_factory.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/terminal_handlers.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/multiplex.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/terminal.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/data.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/keypair.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/service.py +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode.egg-info/SOURCES.txt +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode.egg-info/top_level.txt +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/pyproject.toml +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/restore.sh +0 -0
- {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: portacode
|
|
3
|
-
Version: 0.3.19.
|
|
3
|
+
Version: 0.3.19.dev2
|
|
4
4
|
Summary: Portacode CLI client and SDK
|
|
5
5
|
Home-page: https://github.com/portacode/portacode
|
|
6
6
|
Author: Meena Erian
|
|
@@ -22,6 +22,7 @@ Requires-Dist: pywinpty>=2.0; platform_system == "Windows"
|
|
|
22
22
|
Requires-Dist: GitPython>=3.1.45
|
|
23
23
|
Requires-Dist: watchdog>=3.0
|
|
24
24
|
Requires-Dist: diff-match-patch>=20230430
|
|
25
|
+
Requires-Dist: Pygments>=2.14.0
|
|
25
26
|
Provides-Extra: dev
|
|
26
27
|
Requires-Dist: black; extra == "dev"
|
|
27
28
|
Requires-Dist: flake8; extra == "dev"
|
|
@@ -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.19.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 3, 19, '
|
|
20
|
+
__version__ = version = '0.3.19.dev2'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 3, 19, 'dev2')
|
|
@@ -35,6 +35,21 @@ except ImportError:
|
|
|
35
35
|
DIFF_MATCH_PATCH_AVAILABLE = False
|
|
36
36
|
diff_match_patch = None
|
|
37
37
|
|
|
38
|
+
# Import Pygments with fallback
|
|
39
|
+
try:
|
|
40
|
+
from pygments import highlight
|
|
41
|
+
from pygments.lexers import get_lexer_for_filename, get_lexer_by_name
|
|
42
|
+
from pygments.formatters import HtmlFormatter
|
|
43
|
+
from pygments.util import ClassNotFound
|
|
44
|
+
PYGMENTS_AVAILABLE = True
|
|
45
|
+
except ImportError:
|
|
46
|
+
PYGMENTS_AVAILABLE = False
|
|
47
|
+
highlight = None
|
|
48
|
+
get_lexer_for_filename = None
|
|
49
|
+
get_lexer_by_name = None
|
|
50
|
+
HtmlFormatter = None
|
|
51
|
+
ClassNotFound = Exception
|
|
52
|
+
|
|
38
53
|
# Cross-platform file system monitoring
|
|
39
54
|
try:
|
|
40
55
|
from watchdog.observers import Observer
|
|
@@ -322,6 +337,205 @@ class GitManager:
|
|
|
322
337
|
logger.error("Error computing diff details: %s", e)
|
|
323
338
|
return None
|
|
324
339
|
|
|
340
|
+
def _get_pygments_lexer(self, file_path: str) -> Optional[object]:
|
|
341
|
+
"""Get Pygments lexer for a file path."""
|
|
342
|
+
if not PYGMENTS_AVAILABLE:
|
|
343
|
+
return None
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
# Try to get lexer by filename
|
|
347
|
+
return get_lexer_for_filename(file_path)
|
|
348
|
+
except ClassNotFound:
|
|
349
|
+
# Fallback to common extensions
|
|
350
|
+
extension = os.path.splitext(file_path)[1].lower()
|
|
351
|
+
lexer_map = {
|
|
352
|
+
'.py': 'python',
|
|
353
|
+
'.js': 'javascript',
|
|
354
|
+
'.jsx': 'jsx',
|
|
355
|
+
'.ts': 'typescript',
|
|
356
|
+
'.tsx': 'tsx',
|
|
357
|
+
'.html': 'html',
|
|
358
|
+
'.htm': 'html',
|
|
359
|
+
'.css': 'css',
|
|
360
|
+
'.scss': 'scss',
|
|
361
|
+
'.sass': 'sass',
|
|
362
|
+
'.json': 'json',
|
|
363
|
+
'.xml': 'xml',
|
|
364
|
+
'.yaml': 'yaml',
|
|
365
|
+
'.yml': 'yaml',
|
|
366
|
+
'.java': 'java',
|
|
367
|
+
'.c': 'c',
|
|
368
|
+
'.cpp': 'cpp',
|
|
369
|
+
'.cc': 'cpp',
|
|
370
|
+
'.cxx': 'cpp',
|
|
371
|
+
'.h': 'c',
|
|
372
|
+
'.hpp': 'cpp',
|
|
373
|
+
'.cs': 'csharp',
|
|
374
|
+
'.php': 'php',
|
|
375
|
+
'.rb': 'ruby',
|
|
376
|
+
'.go': 'go',
|
|
377
|
+
'.rs': 'rust',
|
|
378
|
+
'.sh': 'bash',
|
|
379
|
+
'.bash': 'bash',
|
|
380
|
+
'.zsh': 'zsh',
|
|
381
|
+
'.fish': 'fish',
|
|
382
|
+
'.sql': 'sql',
|
|
383
|
+
'.md': 'markdown',
|
|
384
|
+
'.rst': 'rst'
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
lexer_name = lexer_map.get(extension)
|
|
388
|
+
if lexer_name:
|
|
389
|
+
try:
|
|
390
|
+
return get_lexer_by_name(lexer_name)
|
|
391
|
+
except ClassNotFound:
|
|
392
|
+
pass
|
|
393
|
+
|
|
394
|
+
# Final fallback to text
|
|
395
|
+
try:
|
|
396
|
+
return get_lexer_by_name('text')
|
|
397
|
+
except ClassNotFound:
|
|
398
|
+
return None
|
|
399
|
+
except Exception as e:
|
|
400
|
+
logger.debug("Error getting Pygments lexer: %s", e)
|
|
401
|
+
return None
|
|
402
|
+
|
|
403
|
+
def _generate_html_diff(self, original_content: str, modified_content: str, file_path: str) -> Optional[str]:
|
|
404
|
+
"""Generate unified HTML diff with syntax highlighting using Pygments and diff-match-patch."""
|
|
405
|
+
if not DIFF_MATCH_PATCH_AVAILABLE or not PYGMENTS_AVAILABLE:
|
|
406
|
+
logger.debug("Required libraries not available for HTML diff generation")
|
|
407
|
+
return None
|
|
408
|
+
|
|
409
|
+
try:
|
|
410
|
+
# Get diff details
|
|
411
|
+
dmp = diff_match_patch()
|
|
412
|
+
diffs = dmp.diff_main(original_content, modified_content)
|
|
413
|
+
dmp.diff_cleanupSemantic(diffs)
|
|
414
|
+
|
|
415
|
+
# Get Pygments lexer for syntax highlighting
|
|
416
|
+
lexer = self._get_pygments_lexer(file_path)
|
|
417
|
+
|
|
418
|
+
# Convert diffs to line-based unified diff
|
|
419
|
+
unified_diff_lines = self._create_unified_diff_lines(diffs, original_content, modified_content)
|
|
420
|
+
|
|
421
|
+
# Build HTML
|
|
422
|
+
html_parts = []
|
|
423
|
+
html_parts.append('<div class="unified-diff-container">')
|
|
424
|
+
|
|
425
|
+
# Add stats header
|
|
426
|
+
char_additions = sum(len(text) for op, text in diffs if op == 1)
|
|
427
|
+
char_deletions = sum(len(text) for op, text in diffs if op == -1)
|
|
428
|
+
|
|
429
|
+
html_parts.append(f'''
|
|
430
|
+
<div class="diff-stats">
|
|
431
|
+
<span class="additions">+{char_additions}</span>
|
|
432
|
+
<span class="deletions">-{char_deletions}</span>
|
|
433
|
+
<span class="file-path">{os.path.basename(file_path)}</span>
|
|
434
|
+
</div>
|
|
435
|
+
''')
|
|
436
|
+
|
|
437
|
+
# Generate unified diff view
|
|
438
|
+
html_parts.append('<div class="diff-content">')
|
|
439
|
+
html_parts.append('<table class="diff-table">')
|
|
440
|
+
|
|
441
|
+
for line_info in unified_diff_lines:
|
|
442
|
+
line_type = line_info['type'] # 'context', 'delete', 'add', 'header'
|
|
443
|
+
old_line_num = line_info.get('old_line_num', '')
|
|
444
|
+
new_line_num = line_info.get('new_line_num', '')
|
|
445
|
+
content = line_info['content']
|
|
446
|
+
|
|
447
|
+
# Apply syntax highlighting to the content
|
|
448
|
+
if lexer and content.strip() and line_type != 'header':
|
|
449
|
+
try:
|
|
450
|
+
# Remove the +/- prefix for highlighting, then add it back
|
|
451
|
+
clean_content = content[1:] if content and content[0] in '+-' else content
|
|
452
|
+
highlighted = highlight(clean_content, lexer, HtmlFormatter(nowrap=True, noclasses=False))
|
|
453
|
+
# Add the prefix back
|
|
454
|
+
if content and content[0] in '+-':
|
|
455
|
+
highlighted = content[0] + highlighted[len(clean_content):]
|
|
456
|
+
content = highlighted
|
|
457
|
+
except Exception:
|
|
458
|
+
content = self._escape_html(content)
|
|
459
|
+
else:
|
|
460
|
+
content = self._escape_html(content)
|
|
461
|
+
|
|
462
|
+
# CSS classes for different line types
|
|
463
|
+
row_class = f'diff-line diff-{line_type}'
|
|
464
|
+
|
|
465
|
+
html_parts.append(f'''
|
|
466
|
+
<tr class="{row_class}">
|
|
467
|
+
<td class="line-num old-line-num">{old_line_num}</td>
|
|
468
|
+
<td class="line-num new-line-num">{new_line_num}</td>
|
|
469
|
+
<td class="line-content">{content}</td>
|
|
470
|
+
</tr>
|
|
471
|
+
''')
|
|
472
|
+
|
|
473
|
+
html_parts.append('</table>')
|
|
474
|
+
html_parts.append('</div>')
|
|
475
|
+
html_parts.append('</div>')
|
|
476
|
+
|
|
477
|
+
return ''.join(html_parts)
|
|
478
|
+
|
|
479
|
+
except Exception as e:
|
|
480
|
+
logger.error("Error generating HTML diff: %s", e)
|
|
481
|
+
return None
|
|
482
|
+
|
|
483
|
+
def _create_unified_diff_lines(self, diffs, original_content: str, modified_content: str):
|
|
484
|
+
"""Create unified diff lines from diff-match-patch output."""
|
|
485
|
+
lines = []
|
|
486
|
+
|
|
487
|
+
# Split content into lines
|
|
488
|
+
original_lines = original_content.splitlines()
|
|
489
|
+
modified_lines = modified_content.splitlines()
|
|
490
|
+
|
|
491
|
+
old_line_num = 1
|
|
492
|
+
new_line_num = 1
|
|
493
|
+
|
|
494
|
+
# Process each diff operation
|
|
495
|
+
for op, text in diffs:
|
|
496
|
+
text_lines = text.splitlines()
|
|
497
|
+
|
|
498
|
+
if op == 0: # EQUAL - context lines
|
|
499
|
+
for line in text_lines:
|
|
500
|
+
lines.append({
|
|
501
|
+
'type': 'context',
|
|
502
|
+
'old_line_num': old_line_num,
|
|
503
|
+
'new_line_num': new_line_num,
|
|
504
|
+
'content': ' ' + line
|
|
505
|
+
})
|
|
506
|
+
old_line_num += 1
|
|
507
|
+
new_line_num += 1
|
|
508
|
+
|
|
509
|
+
elif op == -1: # DELETE
|
|
510
|
+
for line in text_lines:
|
|
511
|
+
lines.append({
|
|
512
|
+
'type': 'delete',
|
|
513
|
+
'old_line_num': old_line_num,
|
|
514
|
+
'new_line_num': '',
|
|
515
|
+
'content': '-' + line
|
|
516
|
+
})
|
|
517
|
+
old_line_num += 1
|
|
518
|
+
|
|
519
|
+
elif op == 1: # INSERT
|
|
520
|
+
for line in text_lines:
|
|
521
|
+
lines.append({
|
|
522
|
+
'type': 'add',
|
|
523
|
+
'old_line_num': '',
|
|
524
|
+
'new_line_num': new_line_num,
|
|
525
|
+
'content': '+' + line
|
|
526
|
+
})
|
|
527
|
+
new_line_num += 1
|
|
528
|
+
|
|
529
|
+
return lines
|
|
530
|
+
|
|
531
|
+
def _escape_html(self, text: str) -> str:
|
|
532
|
+
"""Escape HTML special characters."""
|
|
533
|
+
return (text.replace('&', '&')
|
|
534
|
+
.replace('<', '<')
|
|
535
|
+
.replace('>', '>')
|
|
536
|
+
.replace('"', '"')
|
|
537
|
+
.replace("'", '''))
|
|
538
|
+
|
|
325
539
|
def get_head_commit_hash(self) -> Optional[str]:
|
|
326
540
|
"""Get the hash of the HEAD commit."""
|
|
327
541
|
if not self.is_git_repo or not self.repo:
|
|
@@ -1249,6 +1463,9 @@ class ProjectStateManager:
|
|
|
1249
1463
|
# Compute diff details for the client
|
|
1250
1464
|
diff_details = git_manager._compute_diff_details(original_content, modified_content)
|
|
1251
1465
|
|
|
1466
|
+
# Generate HTML diff with syntax highlighting
|
|
1467
|
+
html_diff = git_manager._generate_html_diff(original_content, modified_content, file_path)
|
|
1468
|
+
|
|
1252
1469
|
# Create a descriptive title for the diff
|
|
1253
1470
|
title_parts = []
|
|
1254
1471
|
if from_ref == "commit" and from_hash:
|
|
@@ -1274,7 +1491,8 @@ class ProjectStateManager:
|
|
|
1274
1491
|
'to_ref': to_ref,
|
|
1275
1492
|
'from_hash': from_hash,
|
|
1276
1493
|
'to_hash': to_hash,
|
|
1277
|
-
'diff_timeline': True
|
|
1494
|
+
'diff_timeline': True,
|
|
1495
|
+
'html_diff': html_diff
|
|
1278
1496
|
})
|
|
1279
1497
|
|
|
1280
1498
|
project_state.open_tabs[tab_key] = diff_tab
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: portacode
|
|
3
|
-
Version: 0.3.19.
|
|
3
|
+
Version: 0.3.19.dev2
|
|
4
4
|
Summary: Portacode CLI client and SDK
|
|
5
5
|
Home-page: https://github.com/portacode/portacode
|
|
6
6
|
Author: Meena Erian
|
|
@@ -22,6 +22,7 @@ Requires-Dist: pywinpty>=2.0; platform_system == "Windows"
|
|
|
22
22
|
Requires-Dist: GitPython>=3.1.45
|
|
23
23
|
Requires-Dist: watchdog>=3.0
|
|
24
24
|
Requires-Dist: diff-match-patch>=20230430
|
|
25
|
+
Requires-Dist: Pygments>=2.14.0
|
|
25
26
|
Provides-Extra: dev
|
|
26
27
|
Requires-Dist: black; extra == "dev"
|
|
27
28
|
Requires-Dist: flake8; extra == "dev"
|
|
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.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/file_handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/system_handlers.py
RENAMED
|
File without changes
|
{portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/tab_factory.py
RENAMED
|
File without changes
|
{portacode-0.3.19.dev0 → portacode-0.3.19.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
|