CoolFace
Apppublic

akhaliq/anycoder

sourceHugging Faceupdated 5mo agoView on Hugging Face
3.3klikes
backend_search_replace.py215 linesDownload Raw Back to root
1"""2Search/Replace utilities for applying targeted code changes.3Search/Replace utilities for applying targeted code changes.4"""5 6# Search/Replace block markers7SEARCH_START = "\u003c\u003c\u003c\u003c\u003c\u003c\u003c SEARCH"8DIVIDER = "======="9REPLACE_END = "\u003e\u003e\u003e\u003e\u003e\u003e\u003e REPLACE"10 11 12def apply_search_replace_changes(original_content: str, changes_text: str) -> str:13    """Apply search/replace changes to content (HTML, Python, JS, CSS, etc.)14    15    Args:16        original_content: The original file content to modify17        changes_text: Text containing SEARCH/REPLACE blocks18        19    Returns:20        Modified content with all search/replace blocks applied21    """22    if not changes_text.strip():23        return original_content24    25    # If the model didn't use the block markers, try a CSS-rule fallback where26    # provided blocks like `.selector { ... }` replace matching CSS rules.27    if (SEARCH_START not in changes_text) and (DIVIDER not in changes_text) and (REPLACE_END not in changes_text):28        try:29            import re  # Local import to avoid global side effects30            updated_content = original_content31            replaced_any_rule = False32            # Find CSS-like rule blocks in the changes_text33            # This is a conservative matcher that looks for `selector { ... }`34            css_blocks = re.findall(r"([^{]+)\{([\s\S]*?)\}", changes_text, flags=re.MULTILINE)35            for selector_raw, body_raw in css_blocks:36                selector = selector_raw.strip()37                body = body_raw.strip()38                if not selector:39                    continue40                # Build a regex to find the existing rule for this selector41                # Capture opening `{` and closing `}` to preserve them; replace inner body.42                pattern = re.compile(rf"({re.escape(selector)}\s*\{{)([\s\S]*?)(\}})")43                def _replace_rule(match):44                    nonlocal replaced_any_rule45                    replaced_any_rule = True46                    prefix, existing_body, suffix = match.groups()47                    # Preserve indentation of the existing first body line if present48                    first_line_indent = ""49                    for line in existing_body.splitlines():50                        stripped = line.lstrip(" \t")51                        if stripped:52                            first_line_indent = line[: len(line) - len(stripped)]53                            break54                    # Re-indent provided body with the detected indent55                    if body:56                        new_body_lines = [first_line_indent + line if line.strip() else line for line in body.splitlines()]57                        new_body_text = "\n" + "\n".join(new_body_lines) + "\n"58                    else:59                        new_body_text = existing_body  # If empty body provided, keep existing60                    return f"{prefix}{new_body_text}{suffix}"61                updated_content, num_subs = pattern.subn(_replace_rule, updated_content, count=1)62            if replaced_any_rule:63                return updated_content64        except Exception:65            # Fallback silently to the standard block-based application66            pass67 68    # Split the changes text into individual search/replace blocks69    blocks = []70    current_block = ""71    lines = changes_text.split('\n')72    73    for line in lines:74        if line.strip() == SEARCH_START:75            if current_block.strip():76                blocks.append(current_block.strip())77            current_block = line + '\n'78        elif line.strip() == REPLACE_END:79            current_block += line + '\n'80            blocks.append(current_block.strip())81            current_block = ""82        else:83            current_block += line + '\n'84    85    if current_block.strip():86        blocks.append(current_block.strip())87    88    modified_content = original_content89    90    for block in blocks:91        if not block.strip():92            continue93            94        # Parse the search/replace block95        lines = block.split('\n')96        search_lines = []97        replace_lines = []98        in_search = False99        in_replace = False100        101        for line in lines:102            if line.strip() == SEARCH_START:103                in_search = True104                in_replace = False105            elif line.strip() == DIVIDER:106                in_search = False107                in_replace = True108            elif line.strip() == REPLACE_END:109                in_replace = False110            elif in_search:111                search_lines.append(line)112            elif in_replace:113                replace_lines.append(line)114        115        # Apply the search/replace116        if search_lines:117            search_text = '\n'.join(search_lines).strip()118            replace_text = '\n'.join(replace_lines).strip()119            120            if search_text in modified_content:121                modified_content = modified_content.replace(search_text, replace_text)122            else:123                # If exact block match fails, attempt a CSS-rule fallback using the replace_text124                try:125                    import re126                    updated_content = modified_content127                    replaced_any_rule = False128                    css_blocks = re.findall(r"([^{]+)\{([\s\S]*?)\}", replace_text, flags=re.MULTILINE)129                    for selector_raw, body_raw in css_blocks:130                        selector = selector_raw.strip()131                        body = body_raw.strip()132                        if not selector:133                            continue134                        pattern = re.compile(rf"({re.escape(selector)}\s*\{{)([\s\S]*?)(\}})")135                        def _replace_rule(match):136                            nonlocal replaced_any_rule137                            replaced_any_rule = True138                            prefix, existing_body, suffix = match.groups()139                            first_line_indent = ""140                            for line in existing_body.splitlines():141                                stripped = line.lstrip(" \t")142                                if stripped:143                                    first_line_indent = line[: len(line) - len(stripped)]144                                    break145                            if body:146                                new_body_lines = [first_line_indent + line if line.strip() else line for line in body.splitlines()]147                                new_body_text = "\n" + "\n".join(new_body_lines) + "\n"148                            else:149                                new_body_text = existing_body150                            return f"{prefix}{new_body_text}{suffix}"151                        updated_content, num_subs = pattern.subn(_replace_rule, updated_content, count=1)152                    if replaced_any_rule:153                        modified_content = updated_content154                    else:155                        print(f"[Search/Replace] Warning: Search text not found in content: {search_text[:100]}...")156                except Exception:157                    print(f"[Search/Replace] Warning: Search text not found in content: {search_text[:100]}...")158    159    return modified_content160 161 162def has_search_replace_blocks(text: str) -> bool:163    """Check if text contains SEARCH/REPLACE block markers.164    165    Args:166        text: Text to check167        168    Returns:169        True if text contains search/replace markers, False otherwise170    """171    return (SEARCH_START in text) and (DIVIDER in text) and (REPLACE_END in text)172 173 174def parse_file_specific_changes(changes_text: str) -> dict:175    """Parse changes that specify which files to modify.176    177    Looks for patterns like:178        === components/Header.jsx ===179        \u003c\u003c\u003c\u003c\u003c\u003c\u003c SEARCH180        ...181        182    Returns:183        Dict mapping filename -> search/replace changes for that file184    """185    import re186    187    file_changes = {}188    189    # Pattern to match file sections: === filename ===190    file_pattern = re.compile(r"^===\s+([^\n=]+?)\s+===\s*$", re.MULTILINE)191    192    # Find all file sections193    matches = list(file_pattern.finditer(changes_text))194    195    if not matches:196        # No file-specific sections, treat entire text as changes197        return {"__all__": changes_text}198    199    for i, match in enumerate(matches):200        filename = match.group(1).strip()201        start_pos = match.end()202        203        # Find the end of this file's section (start of next file or end of text)204        if i + 1 < len(matches):205            end_pos = matches[i + 1].start()206        else:207            end_pos = len(changes_text)208        209        file_content = changes_text[start_pos:end_pos].strip()210        211        if file_content:212            file_changes[filename] = file_content213    214    return file_changes215