portacode 0.3.19.dev7__tar.gz → 0.3.19.dev9__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.dev7 → portacode-0.3.19.dev9}/PKG-INFO +1 -1
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/_version.py +2 -2
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/project_state_handlers.py +168 -54
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode.egg-info/PKG-INFO +1 -1
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/.claude/agents/communication-manager.md +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/.claude/settings.local.json +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/.gitignore +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/.gitmodules +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/LICENSE +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/MANIFEST.in +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/Makefile +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/README.md +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/backup.sh +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/docker-compose.yaml +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/README.md +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/__init__.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/__main__.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/cli.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/README.md +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/__init__.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/client.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/README.md +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/__init__.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/base.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/registry.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/session.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/tab_factory.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/terminal_handlers.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/multiplex.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/terminal.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/data.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/keypair.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/service.py +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode.egg-info/SOURCES.txt +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode.egg-info/requires.txt +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode.egg-info/top_level.txt +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/pyproject.toml +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/restore.sh +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/setup.cfg +0 -0
- {portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/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.19.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 3, 19, '
|
|
20
|
+
__version__ = version = '0.3.19.dev9'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 3, 19, 'dev9')
|
|
@@ -300,9 +300,18 @@ class GitManager:
|
|
|
300
300
|
logger.debug("diff-match-patch not available, skipping diff details computation")
|
|
301
301
|
return None
|
|
302
302
|
|
|
303
|
+
# Add performance safeguards to prevent blocking
|
|
304
|
+
max_content_size = 50000 # 50KB max per file for diff details
|
|
305
|
+
if len(original_content) > max_content_size or len(modified_content) > max_content_size:
|
|
306
|
+
logger.debug("File too large for diff details computation")
|
|
307
|
+
return None
|
|
308
|
+
|
|
303
309
|
try:
|
|
304
310
|
dmp = diff_match_patch()
|
|
305
311
|
|
|
312
|
+
# Set timeout for diff computation
|
|
313
|
+
dmp.Diff_Timeout = 1.0 # 1 second timeout
|
|
314
|
+
|
|
306
315
|
# Compute the diff
|
|
307
316
|
diffs = dmp.diff_main(original_content, modified_content)
|
|
308
317
|
|
|
@@ -344,7 +353,8 @@ class GitManager:
|
|
|
344
353
|
|
|
345
354
|
try:
|
|
346
355
|
# Use Pygments' built-in filename detection
|
|
347
|
-
|
|
356
|
+
lexer = get_lexer_for_filename(file_path)
|
|
357
|
+
return lexer
|
|
348
358
|
except ClassNotFound:
|
|
349
359
|
# If no lexer found, return None (will fall back to plain text)
|
|
350
360
|
logger.debug("No Pygments lexer found for file: %s", file_path)
|
|
@@ -359,14 +369,38 @@ class GitManager:
|
|
|
359
369
|
logger.debug("Pygments not available for HTML diff generation")
|
|
360
370
|
return None
|
|
361
371
|
|
|
372
|
+
# Add performance safeguards to prevent blocking
|
|
373
|
+
max_content_size = 100000 # 100KB max per file
|
|
374
|
+
max_lines = 1000 # Max 1000 lines per file
|
|
375
|
+
|
|
376
|
+
if len(original_content) > max_content_size or len(modified_content) > max_content_size:
|
|
377
|
+
logger.warning(f"File too large for diff generation: {file_path}")
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
original_line_count = original_content.count('\n')
|
|
381
|
+
modified_line_count = modified_content.count('\n')
|
|
382
|
+
|
|
383
|
+
if original_line_count > max_lines or modified_line_count > max_lines:
|
|
384
|
+
logger.warning(f"Too many lines for diff generation: {file_path} ({max(original_line_count, modified_line_count)} lines)")
|
|
385
|
+
return None
|
|
386
|
+
|
|
362
387
|
try:
|
|
363
388
|
import difflib
|
|
389
|
+
import time
|
|
390
|
+
|
|
391
|
+
start_time = time.time()
|
|
392
|
+
timeout_seconds = 5 # 5 second timeout
|
|
364
393
|
|
|
365
394
|
# Get line-based diff using Python's difflib (similar to git diff)
|
|
366
395
|
original_lines = original_content.splitlines(keepends=True)
|
|
367
396
|
modified_lines = modified_content.splitlines(keepends=True)
|
|
368
397
|
|
|
369
|
-
#
|
|
398
|
+
# Check timeout
|
|
399
|
+
if time.time() - start_time > timeout_seconds:
|
|
400
|
+
logger.warning(f"Diff generation timeout for {file_path}")
|
|
401
|
+
return None
|
|
402
|
+
|
|
403
|
+
# Generate minimal diff only to improve performance
|
|
370
404
|
minimal_diff_lines = list(difflib.unified_diff(
|
|
371
405
|
original_lines,
|
|
372
406
|
modified_lines,
|
|
@@ -376,34 +410,96 @@ class GitManager:
|
|
|
376
410
|
n=3 # 3 lines of context (default)
|
|
377
411
|
))
|
|
378
412
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
))
|
|
413
|
+
# Check timeout again
|
|
414
|
+
if time.time() - start_time > timeout_seconds:
|
|
415
|
+
logger.warning(f"Diff generation timeout for {file_path}")
|
|
416
|
+
return None
|
|
417
|
+
|
|
418
|
+
# Parse diff (simplified - no intraline processing to improve performance)
|
|
419
|
+
minimal_parsed = self._parse_unified_diff_simple(minimal_diff_lines)
|
|
387
420
|
|
|
388
|
-
#
|
|
389
|
-
|
|
390
|
-
|
|
421
|
+
# Check timeout
|
|
422
|
+
if time.time() - start_time > timeout_seconds:
|
|
423
|
+
logger.warning(f"Diff generation timeout for {file_path}")
|
|
424
|
+
return None
|
|
391
425
|
|
|
392
|
-
# Generate HTML for
|
|
426
|
+
# Generate HTML for minimal version only
|
|
393
427
|
minimal_html = self._generate_diff_html(minimal_parsed, file_path, 'minimal')
|
|
394
|
-
full_html = self._generate_diff_html(full_parsed, file_path, 'full')
|
|
395
428
|
|
|
396
429
|
return {
|
|
397
430
|
'minimal': minimal_html,
|
|
398
|
-
'full':
|
|
431
|
+
'full': minimal_html # Use same for both to improve performance
|
|
399
432
|
}
|
|
400
433
|
|
|
401
434
|
except Exception as e:
|
|
402
435
|
logger.error("Error generating HTML diff: %s", e)
|
|
403
436
|
return None
|
|
404
437
|
|
|
438
|
+
def _parse_unified_diff_simple(self, diff_lines):
|
|
439
|
+
"""Simple unified diff parser without intra-line highlighting for better performance."""
|
|
440
|
+
parsed = []
|
|
441
|
+
old_line_num = 0
|
|
442
|
+
new_line_num = 0
|
|
443
|
+
|
|
444
|
+
for line in diff_lines:
|
|
445
|
+
if line.startswith('@@'):
|
|
446
|
+
# Parse hunk header to get line numbers
|
|
447
|
+
import re
|
|
448
|
+
match = re.match(r'@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@', line)
|
|
449
|
+
if match:
|
|
450
|
+
old_line_num = int(match.group(1)) - 1
|
|
451
|
+
new_line_num = int(match.group(2)) - 1
|
|
452
|
+
|
|
453
|
+
parsed.append({
|
|
454
|
+
'type': 'header',
|
|
455
|
+
'content': line,
|
|
456
|
+
'old_line_num': '',
|
|
457
|
+
'new_line_num': ''
|
|
458
|
+
})
|
|
459
|
+
elif line.startswith('---') or line.startswith('+++'):
|
|
460
|
+
# Skip diff file headers (--- a/file, +++ b/file)
|
|
461
|
+
parsed.append({
|
|
462
|
+
'type': 'header',
|
|
463
|
+
'content': line,
|
|
464
|
+
'old_line_num': '',
|
|
465
|
+
'new_line_num': ''
|
|
466
|
+
})
|
|
467
|
+
elif line.startswith('-'):
|
|
468
|
+
old_line_num += 1
|
|
469
|
+
parsed.append({
|
|
470
|
+
'type': 'delete',
|
|
471
|
+
'old_line_num': old_line_num,
|
|
472
|
+
'new_line_num': '',
|
|
473
|
+
'content': line
|
|
474
|
+
})
|
|
475
|
+
elif line.startswith('+'):
|
|
476
|
+
new_line_num += 1
|
|
477
|
+
parsed.append({
|
|
478
|
+
'type': 'add',
|
|
479
|
+
'old_line_num': '',
|
|
480
|
+
'new_line_num': new_line_num,
|
|
481
|
+
'content': line
|
|
482
|
+
})
|
|
483
|
+
elif line.startswith(' '):
|
|
484
|
+
old_line_num += 1
|
|
485
|
+
new_line_num += 1
|
|
486
|
+
parsed.append({
|
|
487
|
+
'type': 'context',
|
|
488
|
+
'old_line_num': old_line_num,
|
|
489
|
+
'new_line_num': new_line_num,
|
|
490
|
+
'content': line
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
return parsed
|
|
494
|
+
|
|
405
495
|
def _generate_diff_html(self, parsed_diff: List[Dict], file_path: str, view_mode: str) -> str:
|
|
406
496
|
"""Generate HTML for a parsed diff."""
|
|
497
|
+
# Limit diff size to prevent performance issues
|
|
498
|
+
max_diff_lines = 200
|
|
499
|
+
if len(parsed_diff) > max_diff_lines:
|
|
500
|
+
logger.warning(f"Diff too large, truncating: {file_path} ({len(parsed_diff)} lines)")
|
|
501
|
+
parsed_diff = parsed_diff[:max_diff_lines]
|
|
502
|
+
|
|
407
503
|
# Get Pygments lexer for syntax highlighting
|
|
408
504
|
lexer = self._get_pygments_lexer(file_path)
|
|
409
505
|
|
|
@@ -443,44 +539,40 @@ class GitManager:
|
|
|
443
539
|
old_line_num = line_info.get('old_line_num', '')
|
|
444
540
|
new_line_num = line_info.get('new_line_num', '')
|
|
445
541
|
content = line_info['content']
|
|
446
|
-
intraline_html = line_info.get('intraline_html', '')
|
|
447
542
|
|
|
448
|
-
#
|
|
449
|
-
if
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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:
|
|
543
|
+
# Apply syntax highlighting to the content
|
|
544
|
+
if content and content[0] in '+-':
|
|
545
|
+
# For added/deleted lines, highlight the content without the prefix
|
|
546
|
+
prefix = content[0]
|
|
547
|
+
clean_content = content[1:]
|
|
548
|
+
|
|
549
|
+
if lexer and clean_content.strip():
|
|
550
|
+
try:
|
|
551
|
+
# Apply syntax highlighting using Monokai theme to match ACE editor
|
|
552
|
+
highlighted = highlight(clean_content, lexer, HtmlFormatter(nowrap=True, noclasses=False, style='monokai'))
|
|
553
|
+
final_content = prefix + highlighted
|
|
554
|
+
except Exception as e:
|
|
555
|
+
logger.debug("Error applying syntax highlighting: %s", e)
|
|
467
556
|
final_content = self._escape_html(content)
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
557
|
+
else:
|
|
558
|
+
final_content = self._escape_html(content)
|
|
559
|
+
|
|
560
|
+
elif content and content[0] == ' ':
|
|
561
|
+
# For context lines, highlight without the space prefix
|
|
562
|
+
clean_content = content[1:]
|
|
563
|
+
|
|
564
|
+
if lexer and clean_content.strip():
|
|
565
|
+
try:
|
|
566
|
+
# Apply syntax highlighting using Monokai theme to match ACE editor
|
|
567
|
+
highlighted = highlight(clean_content, lexer, HtmlFormatter(nowrap=True, noclasses=False, style='monokai'))
|
|
568
|
+
final_content = ' ' + highlighted
|
|
569
|
+
except Exception as e:
|
|
570
|
+
logger.debug("Error applying syntax highlighting: %s", e)
|
|
481
571
|
final_content = self._escape_html(content)
|
|
482
572
|
else:
|
|
483
573
|
final_content = self._escape_html(content)
|
|
574
|
+
else:
|
|
575
|
+
final_content = self._escape_html(content)
|
|
484
576
|
|
|
485
577
|
# CSS classes for different line types
|
|
486
578
|
row_class = f'diff-line diff-{line_type}'
|
|
@@ -550,6 +642,14 @@ class GitManager:
|
|
|
550
642
|
old_line_num = int(match.group(1)) - 1
|
|
551
643
|
new_line_num = int(match.group(2)) - 1
|
|
552
644
|
|
|
645
|
+
parsed.append({
|
|
646
|
+
'type': 'header',
|
|
647
|
+
'content': line,
|
|
648
|
+
'old_line_num': '',
|
|
649
|
+
'new_line_num': ''
|
|
650
|
+
})
|
|
651
|
+
elif line.startswith('---') or line.startswith('+++'):
|
|
652
|
+
# Skip diff file headers (--- a/file, +++ b/file)
|
|
553
653
|
parsed.append({
|
|
554
654
|
'type': 'header',
|
|
555
655
|
'content': line,
|
|
@@ -599,6 +699,9 @@ class GitManager:
|
|
|
599
699
|
|
|
600
700
|
def _generate_intraline_diff(self, old_text: str, new_text: str) -> Tuple[str, str]:
|
|
601
701
|
"""Generate intra-line character-level diff highlighting."""
|
|
702
|
+
# Temporarily disable intraline highlighting to fix performance issues
|
|
703
|
+
return self._escape_html(old_text), self._escape_html(new_text)
|
|
704
|
+
|
|
602
705
|
if not DIFF_MATCH_PATCH_AVAILABLE:
|
|
603
706
|
return self._escape_html(old_text), self._escape_html(new_text)
|
|
604
707
|
|
|
@@ -1563,7 +1666,13 @@ class ProjectStateManager:
|
|
|
1563
1666
|
diff_details = git_manager._compute_diff_details(original_content, modified_content)
|
|
1564
1667
|
|
|
1565
1668
|
# Generate HTML diff with syntax highlighting (both minimal and full context)
|
|
1566
|
-
|
|
1669
|
+
# Wrap in try-catch to prevent blocking the WebSocket handler
|
|
1670
|
+
html_diff_versions = None
|
|
1671
|
+
try:
|
|
1672
|
+
html_diff_versions = git_manager._generate_html_diff(original_content, modified_content, file_path)
|
|
1673
|
+
except Exception as e:
|
|
1674
|
+
logger.error(f"Error generating HTML diff for {file_path}: {e}")
|
|
1675
|
+
# Continue without HTML diff - fallback to basic diff will be used
|
|
1567
1676
|
|
|
1568
1677
|
# Create a descriptive title for the diff
|
|
1569
1678
|
title_parts = []
|
|
@@ -1585,14 +1694,19 @@ class ProjectStateManager:
|
|
|
1585
1694
|
)
|
|
1586
1695
|
|
|
1587
1696
|
# Add metadata about the diff references
|
|
1588
|
-
|
|
1697
|
+
metadata_update = {
|
|
1589
1698
|
'from_ref': from_ref,
|
|
1590
1699
|
'to_ref': to_ref,
|
|
1591
1700
|
'from_hash': from_hash,
|
|
1592
1701
|
'to_hash': to_hash,
|
|
1593
|
-
'diff_timeline': True
|
|
1594
|
-
|
|
1595
|
-
|
|
1702
|
+
'diff_timeline': True
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
# Only add HTML diff versions if they were successfully generated
|
|
1706
|
+
if html_diff_versions:
|
|
1707
|
+
metadata_update['html_diff_versions'] = html_diff_versions
|
|
1708
|
+
|
|
1709
|
+
diff_tab.metadata.update(metadata_update)
|
|
1596
1710
|
|
|
1597
1711
|
project_state.open_tabs[tab_key] = diff_tab
|
|
1598
1712
|
project_state.active_tab = diff_tab
|
|
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.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/file_handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/system_handlers.py
RENAMED
|
File without changes
|
{portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/portacode/connection/handlers/tab_factory.py
RENAMED
|
File without changes
|
{portacode-0.3.19.dev7 → portacode-0.3.19.dev9}/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
|