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.
Files changed (45) hide show
  1. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/.claude/settings.local.json +2 -1
  2. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/PKG-INFO +2 -1
  3. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/_version.py +2 -2
  4. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/project_state_handlers.py +219 -1
  5. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode.egg-info/PKG-INFO +2 -1
  6. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode.egg-info/requires.txt +1 -0
  7. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/setup.py +1 -0
  8. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/.claude/agents/communication-manager.md +0 -0
  9. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/.gitignore +0 -0
  10. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/.gitmodules +0 -0
  11. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/LICENSE +0 -0
  12. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/MANIFEST.in +0 -0
  13. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/Makefile +0 -0
  14. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/README.md +0 -0
  15. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/backup.sh +0 -0
  16. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/docker-compose.yaml +0 -0
  17. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/README.md +0 -0
  18. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/__init__.py +0 -0
  19. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/__main__.py +0 -0
  20. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/cli.py +0 -0
  21. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/README.md +0 -0
  22. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/__init__.py +0 -0
  23. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/client.py +0 -0
  24. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/README.md +0 -0
  25. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
  26. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/__init__.py +0 -0
  27. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/base.py +0 -0
  28. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/file_handlers.py +0 -0
  29. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/registry.py +0 -0
  30. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/session.py +0 -0
  31. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/system_handlers.py +0 -0
  32. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/tab_factory.py +0 -0
  33. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/handlers/terminal_handlers.py +0 -0
  34. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/multiplex.py +0 -0
  35. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/connection/terminal.py +0 -0
  36. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/data.py +0 -0
  37. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/keypair.py +0 -0
  38. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode/service.py +0 -0
  39. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode.egg-info/SOURCES.txt +0 -0
  40. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode.egg-info/dependency_links.txt +0 -0
  41. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode.egg-info/entry_points.txt +0 -0
  42. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/portacode.egg-info/top_level.txt +0 -0
  43. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/pyproject.toml +0 -0
  44. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/restore.sh +0 -0
  45. {portacode-0.3.19.dev0 → portacode-0.3.19.dev2}/setup.cfg +0 -0
@@ -8,7 +8,8 @@
8
8
  "Bash(find:*)",
9
9
  "Bash(touch:*)",
10
10
  "Bash(npm run lint)",
11
- "Bash(node:*)"
11
+ "Bash(node:*)",
12
+ "Bash(rm:*)"
12
13
  ],
13
14
  "deny": []
14
15
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portacode
3
- Version: 0.3.19.dev0
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.dev0'
21
- __version_tuple__ = version_tuple = (0, 3, 19, 'dev0')
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('&', '&amp;')
534
+ .replace('<', '&lt;')
535
+ .replace('>', '&gt;')
536
+ .replace('"', '&quot;')
537
+ .replace("'", '&#x27;'))
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.dev0
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"
@@ -8,6 +8,7 @@ pyte>=0.8
8
8
  GitPython>=3.1.45
9
9
  watchdog>=3.0
10
10
  diff-match-patch>=20230430
11
+ Pygments>=2.14.0
11
12
 
12
13
  [:platform_system == "Windows"]
13
14
  pywinpty>=2.0
@@ -30,6 +30,7 @@ setup(
30
30
  "GitPython>=3.1.45",
31
31
  "watchdog>=3.0",
32
32
  "diff-match-patch>=20230430",
33
+ "Pygments>=2.14.0",
33
34
  ],
34
35
  extras_require={
35
36
  "dev": ["black", "flake8", "pytest"],
File without changes