portacode 0.3.19.dev4__tar.gz → 0.3.19.dev6__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.dev4 → portacode-0.3.19.dev6}/PKG-INFO +1 -1
  2. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/_version.py +2 -2
  3. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/handlers/project_state_handlers.py +122 -116
  4. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode.egg-info/PKG-INFO +1 -1
  5. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/restore.sh +0 -2
  6. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/.claude/agents/communication-manager.md +0 -0
  7. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/.claude/settings.local.json +0 -0
  8. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/.gitignore +0 -0
  9. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/.gitmodules +0 -0
  10. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/LICENSE +0 -0
  11. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/MANIFEST.in +0 -0
  12. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/Makefile +0 -0
  13. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/README.md +0 -0
  14. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/backup.sh +0 -0
  15. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/docker-compose.yaml +0 -0
  16. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/README.md +0 -0
  17. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/__init__.py +0 -0
  18. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/__main__.py +0 -0
  19. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/cli.py +0 -0
  20. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/README.md +0 -0
  21. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/__init__.py +0 -0
  22. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/client.py +0 -0
  23. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/handlers/README.md +0 -0
  24. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
  25. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/handlers/__init__.py +0 -0
  26. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/handlers/base.py +0 -0
  27. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/handlers/file_handlers.py +0 -0
  28. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/handlers/registry.py +0 -0
  29. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/handlers/session.py +0 -0
  30. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/handlers/system_handlers.py +0 -0
  31. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/handlers/tab_factory.py +0 -0
  32. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/handlers/terminal_handlers.py +0 -0
  33. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/multiplex.py +0 -0
  34. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/connection/terminal.py +0 -0
  35. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/data.py +0 -0
  36. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/keypair.py +0 -0
  37. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode/service.py +0 -0
  38. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode.egg-info/SOURCES.txt +0 -0
  39. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode.egg-info/dependency_links.txt +0 -0
  40. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode.egg-info/entry_points.txt +0 -0
  41. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode.egg-info/requires.txt +0 -0
  42. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/portacode.egg-info/top_level.txt +0 -0
  43. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/pyproject.toml +0 -0
  44. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/setup.cfg +0 -0
  45. {portacode-0.3.19.dev4 → portacode-0.3.19.dev6}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portacode
3
- Version: 0.3.19.dev4
3
+ Version: 0.3.19.dev6
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -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.dev4'
21
- __version_tuple__ = version_tuple = (0, 3, 19, 'dev4')
20
+ __version__ = version = '0.3.19.dev6'
21
+ __version_tuple__ = version_tuple = (0, 3, 19, 'dev6')
@@ -338,70 +338,23 @@ class GitManager:
338
338
  return None
339
339
 
340
340
  def _get_pygments_lexer(self, file_path: str) -> Optional[object]:
341
- """Get Pygments lexer for a file path."""
341
+ """Get Pygments lexer for a file path using built-in detection."""
342
342
  if not PYGMENTS_AVAILABLE:
343
343
  return None
344
344
 
345
345
  try:
346
- # Try to get lexer by filename
346
+ # Use Pygments' built-in filename detection
347
347
  return get_lexer_for_filename(file_path)
348
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
349
+ # If no lexer found, return None (will fall back to plain text)
350
+ logger.debug("No Pygments lexer found for file: %s", file_path)
351
+ return None
399
352
  except Exception as e:
400
353
  logger.debug("Error getting Pygments lexer: %s", e)
401
354
  return None
402
355
 
403
- def _generate_html_diff(self, original_content: str, modified_content: str, file_path: str) -> Optional[str]:
404
- """Generate unified HTML diff with intra-line highlighting using GitPython and diff-match-patch."""
356
+ def _generate_html_diff(self, original_content: str, modified_content: str, file_path: str) -> Optional[Dict[str, str]]:
357
+ """Generate unified HTML diff with intra-line highlighting. Returns both minimal and full context versions."""
405
358
  if not PYGMENTS_AVAILABLE:
406
359
  logger.debug("Pygments not available for HTML diff generation")
407
360
  return None
@@ -413,85 +366,138 @@ class GitManager:
413
366
  original_lines = original_content.splitlines(keepends=True)
414
367
  modified_lines = modified_content.splitlines(keepends=True)
415
368
 
416
- # Generate unified diff
417
- diff_lines = list(difflib.unified_diff(
369
+ # Generate both minimal (3 lines context) and full context diffs
370
+ minimal_diff_lines = list(difflib.unified_diff(
418
371
  original_lines,
419
372
  modified_lines,
420
373
  fromfile='a/' + os.path.basename(file_path),
421
374
  tofile='b/' + os.path.basename(file_path),
422
- lineterm=''
375
+ lineterm='',
376
+ n=3 # 3 lines of context (default)
423
377
  ))
424
378
 
425
- # Parse the unified diff and add intra-line highlighting
426
- parsed_diff = self._parse_unified_diff_with_intraline(diff_lines, original_lines, modified_lines)
379
+ full_diff_lines = list(difflib.unified_diff(
380
+ original_lines,
381
+ modified_lines,
382
+ fromfile='a/' + os.path.basename(file_path),
383
+ tofile='b/' + os.path.basename(file_path),
384
+ lineterm='',
385
+ n=len(original_lines) + len(modified_lines) # Show all lines
386
+ ))
427
387
 
428
- # Get Pygments lexer for syntax highlighting
429
- lexer = self._get_pygments_lexer(file_path)
388
+ # Parse both diffs
389
+ minimal_parsed = self._parse_unified_diff_with_intraline(minimal_diff_lines, original_lines, modified_lines)
390
+ full_parsed = self._parse_unified_diff_with_intraline(full_diff_lines, original_lines, modified_lines)
430
391
 
431
- # Build HTML
432
- html_parts = []
433
- html_parts.append('<div class="unified-diff-container">')
392
+ # Generate HTML for both versions
393
+ minimal_html = self._generate_diff_html(minimal_parsed, file_path, 'minimal')
394
+ full_html = self._generate_diff_html(full_parsed, file_path, 'full')
434
395
 
435
- # Add stats header
436
- line_additions = sum(1 for line in parsed_diff if line['type'] == 'add')
437
- line_deletions = sum(1 for line in parsed_diff if line['type'] == 'delete')
396
+ return {
397
+ 'minimal': minimal_html,
398
+ 'full': full_html
399
+ }
438
400
 
439
- html_parts.append(f'''
440
- <div class="diff-stats">
401
+ except Exception as e:
402
+ logger.error("Error generating HTML diff: %s", e)
403
+ return None
404
+
405
+ def _generate_diff_html(self, parsed_diff: List[Dict], file_path: str, view_mode: str) -> str:
406
+ """Generate HTML for a parsed diff."""
407
+ # Get Pygments lexer for syntax highlighting
408
+ lexer = self._get_pygments_lexer(file_path)
409
+
410
+ # Build HTML
411
+ html_parts = []
412
+ html_parts.append(f'<div class="unified-diff-container" data-view-mode="{view_mode}">')
413
+
414
+ # Add stats header with toggle
415
+ line_additions = sum(1 for line in parsed_diff if line['type'] == 'add')
416
+ line_deletions = sum(1 for line in parsed_diff if line['type'] == 'delete')
417
+
418
+ html_parts.append(f'''
419
+ <div class="diff-stats">
420
+ <div class="diff-stats-left">
441
421
  <span class="additions">+{line_additions}</span>
442
422
  <span class="deletions">-{line_deletions}</span>
443
423
  <span class="file-path">{os.path.basename(file_path)}</span>
444
424
  </div>
445
- ''')
446
-
447
- # Generate unified diff view
448
- html_parts.append('<div class="diff-content">')
449
- html_parts.append('<table class="diff-table">')
450
-
451
- for line_info in parsed_diff:
452
- if line_info['type'] == 'header':
453
- continue # Skip diff headers
454
-
455
- line_type = line_info['type']
456
- old_line_num = line_info.get('old_line_num', '')
457
- new_line_num = line_info.get('new_line_num', '')
458
- content = line_info['content']
459
- intraline_html = line_info.get('intraline_html', '')
425
+ <div class="diff-stats-right">
426
+ <button class="diff-toggle-btn" data-current-mode="{view_mode}">
427
+ <i class="fas fa-eye"></i>
428
+ <span class="toggle-text">{''}</span>
429
+ </button>
430
+ </div>
431
+ </div>
432
+ ''')
433
+
434
+ # Generate unified diff view
435
+ html_parts.append('<div class="diff-content">')
436
+ html_parts.append('<table class="diff-table">')
437
+
438
+ for line_info in parsed_diff:
439
+ if line_info['type'] == 'header':
440
+ continue # Skip all diff headers including --- and +++ lines
460
441
 
461
- # Use intra-line highlighted content if available, otherwise apply syntax highlighting
462
- if intraline_html:
463
- final_content = intraline_html
464
- elif lexer and content.strip():
465
- try:
466
- # Apply syntax highlighting to the content (without the +/- prefix)
467
- clean_content = content[1:] if content and content[0] in '+-' else content
468
- highlighted = highlight(clean_content, lexer, HtmlFormatter(nowrap=True, noclasses=False))
469
- final_content = content[0] + highlighted if content and content[0] in '+-' else highlighted
470
- except Exception:
442
+ line_type = line_info['type']
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
+ intraline_html = line_info.get('intraline_html', '')
447
+
448
+ # Use intra-line highlighted content if available
449
+ if intraline_html:
450
+ final_content = intraline_html
451
+ else:
452
+ # Apply syntax highlighting to the content
453
+ if content and content[0] in '+-':
454
+ # For added/deleted lines, highlight the content without the prefix
455
+ prefix = content[0]
456
+ clean_content = content[1:]
457
+
458
+ if lexer and clean_content.strip():
459
+ try:
460
+ # Apply syntax highlighting
461
+ highlighted = highlight(clean_content, lexer, HtmlFormatter(nowrap=True, noclasses=False))
462
+ final_content = prefix + highlighted
463
+ except Exception as e:
464
+ logger.debug("Error applying syntax highlighting: %s", e)
465
+ final_content = self._escape_html(content)
466
+ else:
467
+ final_content = self._escape_html(content)
468
+ elif content and content[0] == ' ':
469
+ # For context lines, highlight without the space prefix
470
+ clean_content = content[1:]
471
+
472
+ if lexer and clean_content.strip():
473
+ try:
474
+ # Apply syntax highlighting
475
+ highlighted = highlight(clean_content, lexer, HtmlFormatter(nowrap=True, noclasses=False))
476
+ final_content = ' ' + highlighted
477
+ except Exception as e:
478
+ logger.debug("Error applying syntax highlighting: %s", e)
479
+ final_content = self._escape_html(content)
480
+ else:
471
481
  final_content = self._escape_html(content)
472
482
  else:
473
483
  final_content = self._escape_html(content)
474
-
475
- # CSS classes for different line types
476
- row_class = f'diff-line diff-{line_type}'
477
-
478
- html_parts.append(f'''
479
- <tr class="{row_class}">
480
- <td class="line-num old-line-num">{old_line_num}</td>
481
- <td class="line-num new-line-num">{new_line_num}</td>
482
- <td class="line-content">{final_content}</td>
483
- </tr>
484
- ''')
485
-
486
- html_parts.append('</table>')
487
- html_parts.append('</div>')
488
- html_parts.append('</div>')
489
484
 
490
- return ''.join(html_parts)
485
+ # CSS classes for different line types
486
+ row_class = f'diff-line diff-{line_type}'
491
487
 
492
- except Exception as e:
493
- logger.error("Error generating HTML diff: %s", e)
494
- return None
488
+ html_parts.append(f'''
489
+ <tr class="{row_class}">
490
+ <td class="line-num old-line-num">{old_line_num}</td>
491
+ <td class="line-num new-line-num">{new_line_num}</td>
492
+ <td class="line-content">{final_content}</td>
493
+ </tr>
494
+ ''')
495
+
496
+ html_parts.append('</table>')
497
+ html_parts.append('</div>')
498
+ html_parts.append('</div>')
499
+
500
+ return ''.join(html_parts)
495
501
 
496
502
  def _parse_unified_diff_with_intraline(self, diff_lines, original_lines, modified_lines):
497
503
  """Parse unified diff and add intra-line character highlighting."""
@@ -1556,8 +1562,8 @@ class ProjectStateManager:
1556
1562
  # Compute diff details for the client
1557
1563
  diff_details = git_manager._compute_diff_details(original_content, modified_content)
1558
1564
 
1559
- # Generate HTML diff with syntax highlighting
1560
- html_diff = git_manager._generate_html_diff(original_content, modified_content, file_path)
1565
+ # Generate HTML diff with syntax highlighting (both minimal and full context)
1566
+ html_diff_versions = git_manager._generate_html_diff(original_content, modified_content, file_path)
1561
1567
 
1562
1568
  # Create a descriptive title for the diff
1563
1569
  title_parts = []
@@ -1585,7 +1591,7 @@ class ProjectStateManager:
1585
1591
  'from_hash': from_hash,
1586
1592
  'to_hash': to_hash,
1587
1593
  'diff_timeline': True,
1588
- 'html_diff': html_diff
1594
+ 'html_diff_versions': html_diff_versions
1589
1595
  })
1590
1596
 
1591
1597
  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.dev4
3
+ Version: 0.3.19.dev6
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -8,8 +8,6 @@ BACKUP_DIR="${1:-$PWD/../backups}"
8
8
  VOLUME_NAME="portacode_pgdata"
9
9
  SERVICE="db"
10
10
 
11
- SDFSD
12
-
13
11
  # ─── PICK A BACKUP ────────────────────────────────────────────────────────
14
12
  mapfile -t BACKUPS < <(
15
13
  find "$BACKUP_DIR" -maxdepth 1 -type f -name "pgdata-*.tar.gz" \
File without changes