diff --git a/README.md b/README.md index f2aecb6f69c..2dc86be1174 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,23 @@ aider --model sonnet --api-key anthropic= aider --model o3-mini --api-key openai= ``` +### Configuration + +Create a `.aider.conf.yml` file to configure model aliases and other settings: + +```yaml +# Use a list to define multiple aliases +alias: + - "fast:gpt-4o-mini" + - "smart:o3-mini" + - "hacker:claude-3-sonnet-20240229" + +# Default model +model: "gpt-4o-mini" +``` + +> **Note:** YAML does not support repeated keys. Always use a list format for multiple aliases. + See the [installation instructions](https://aider.chat/docs/install.html) and [usage documentation](https://aider.chat/docs/usage.html) for more details. ## More Information diff --git a/aider/args.py b/aider/args.py index 5b3fdf07faf..bbfa2915f34 100644 --- a/aider/args.py +++ b/aider/args.py @@ -392,6 +392,12 @@ def get_parser(default_config_files, git_root): " see https://pygments.org/styles for available themes)" ), ) + group.add_argument( + "--code-theme-no-background", + action="store_true", + help="Disable background colors in code theme (default: False)", + default=False, + ) group.add_argument( "--show-diffs", action="store_true", diff --git a/aider/io.py b/aider/io.py index ed6f22d51ae..9411b29b96d 100644 --- a/aider/io.py +++ b/aider/io.py @@ -252,6 +252,7 @@ def __init__( completion_menu_current_color=None, completion_menu_current_bg_color=None, code_theme="default", + code_theme_no_background=False, encoding="utf-8", line_endings="platform", dry_run=False, @@ -297,6 +298,7 @@ def __init__( ) self.code_theme = code_theme + self.code_theme_no_background = code_theme_no_background self.input = input self.output = output @@ -1015,6 +1017,7 @@ def get_assistant_mdstream(self): mdargs = dict( style=self.assistant_output_color, code_theme=self.code_theme, + code_theme_no_background=self.code_theme_no_background, inline_code_lexer="text", ) mdStream = MarkdownStream(mdargs=mdargs) diff --git a/aider/main.py b/aider/main.py index afb3f836624..f1b6f699e2a 100644 --- a/aider/main.py +++ b/aider/main.py @@ -140,14 +140,25 @@ def setup_git(git_root, io): if user_name and user_email: return repo.working_tree_dir - - with repo.config_writer() as git_config: - if not user_name: - git_config.set_value("user", "name", "Your Name") - io.tool_warning('Update git name with: git config user.name "Your Name"') - if not user_email: - git_config.set_value("user", "email", "you@example.com") - io.tool_warning('Update git email with: git config user.email "you@example.com"') + + try: + with repo.config_writer() as git_config: + if not user_name: + git_config.set_value("user", "name", "Your Name") + io.tool_warning('Update git name with: git config user.name "Your Name"') + if not user_email: + git_config.set_value("user", "email", "you@example.com") + io.tool_warning('Update git email with: git config user.email "you@example.com"') + except (OSError, PermissionError) as e: + io.tool_warning( + f"Warning: Could not write to git config: {e}\n" + f"This may be due to network drive permissions or file locking issues.\n" + f"You may need to manually set git config values using:\n" + f"git config user.name \"Your Name\"\n" + f"git config user.email \"you@example.com\"" + ) + # Continue without modifying config, in read-only mode + pass return repo.working_tree_dir @@ -542,6 +553,11 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F args.tool_warning_color = "#FFA500" args.assistant_output_color = "blue" args.code_theme = "default" + # Clear background colors for light mode + args.completion_menu_bg_color = None + args.completion_menu_current_bg_color = None + # Override Pygments theme to disable backgrounds + args.code_theme_no_background = True if return_coder and args.yes_always is None: args.yes_always = True @@ -566,6 +582,7 @@ def get_io(pretty): completion_menu_current_bg_color=args.completion_menu_current_bg_color, assistant_output_color=args.assistant_output_color, code_theme=args.code_theme, + code_theme_no_background=args.code_theme_no_background, dry_run=args.dry_run, encoding=args.encoding, line_endings=args.line_endings, diff --git a/aider/md_renderer.py b/aider/md_renderer.py new file mode 100644 index 00000000000..7debc6cccfb --- /dev/null +++ b/aider/md_renderer.py @@ -0,0 +1,28 @@ +from rich.markdown import Markdown, Padding +from rich.syntax import Syntax + +from .theme_utils import get_code_theme + +class CustomMarkdown(Markdown): + """Custom Markdown renderer that handles code block backgrounds""" + + def __init__(self, text, code_theme="default", code_theme_no_background=False, **kwargs): + self.code_theme_name = code_theme + self.code_theme_no_background = code_theme_no_background + super().__init__(text, **kwargs) + + def render_code_block(self, block, width): + """Render a code block with optional background removal.""" + code = block.text.rstrip() + lexer = block.lexer_name if hasattr(block, "lexer_name") else "default" + + theme = get_code_theme(self.code_theme_name, self.code_theme_no_background) + syntax = Syntax( + code, + lexer, + theme=theme, + word_wrap=False, + padding=0, + background_color=None if self.code_theme_no_background else "default", + ) + return Padding(syntax, pad=(0, 0)) \ No newline at end of file diff --git a/aider/mdstream.py b/aider/mdstream.py index 774b247c2be..1e8b40aac2c 100755 --- a/aider/mdstream.py +++ b/aider/mdstream.py @@ -6,11 +6,13 @@ from rich import box from rich.console import Console from rich.live import Live -from rich.markdown import CodeBlock, Heading, Markdown +from rich.markdown import CodeBlock, Heading from rich.panel import Panel from rich.syntax import Syntax from rich.text import Text +from .md_renderer import CustomMarkdown + from aider.dump import dump # noqa: F401 _text_prefix = """ @@ -131,7 +133,7 @@ def _render_markdown_to_lines(self, text): # Render the markdown to a string buffer string_io = io.StringIO() console = Console(file=string_io, force_terminal=True) - markdown = NoInsetMarkdown(text, **self.mdargs) + markdown = CustomMarkdown(text, **self.mdargs) console.print(markdown) output = string_io.getvalue() diff --git a/aider/theme_utils.py b/aider/theme_utils.py new file mode 100644 index 00000000000..646c43ab502 --- /dev/null +++ b/aider/theme_utils.py @@ -0,0 +1,34 @@ +from pygments.style import Style as PygmentsStyle +from pygments.util import ClassNotFound +from pygments.styles import get_style_by_name + + +class NoBackgroundStyle(PygmentsStyle): + """A style wrapper that removes background colors from another style.""" + + def __init__(self, base_style): + # Get the base style's colors and settings + self.styles = base_style.styles.copy() + # Remove background colors from all token styles + for token, style_string in self.styles.items(): + if style_string: + # Split style into parts + parts = style_string.split() + # Filter out any bg:color settings + parts = [p for p in parts if not p.startswith('bg:')] + self.styles[token] = ' '.join(parts) + + +def get_code_theme(theme_name, no_background=False): + """Get a Pygments style, optionally without backgrounds.""" + try: + base_style = get_style_by_name(theme_name) + if no_background: + return NoBackgroundStyle(base_style) + return base_style + except ClassNotFound: + # Fallback to default style + base_style = get_style_by_name('default') + if no_background: + return NoBackgroundStyle(base_style) + return base_style \ No newline at end of file diff --git a/tests/basic/test_git_config_network.py b/tests/basic/test_git_config_network.py new file mode 100644 index 00000000000..6ff775dd6af --- /dev/null +++ b/tests/basic/test_git_config_network.py @@ -0,0 +1,46 @@ +import os +from pathlib import Path +from unittest import TestCase +from unittest.mock import patch, MagicMock + +import git + +from aider.io import InputOutput +from aider.main import setup_git +from aider.utils import GitTemporaryDirectory + + +class TestGitConfigNetworkDrive(TestCase): + def setUp(self): + self.tempdir = GitTemporaryDirectory() + self.old_cwd = os.getcwd() + os.chdir(self.tempdir.name) + + def tearDown(self): + os.chdir(self.old_cwd) + self.tempdir.cleanup() + + def test_setup_git_with_permission_error(self): + """Test that setup_git handles permission errors gracefully""" + io = InputOutput(pretty=False, yes=True) + + # Create a mock repo that raises PermissionError on config_writer + mock_repo = MagicMock(spec=git.Repo) + mock_config_writer = MagicMock() + mock_config_writer.__enter__ = MagicMock(side_effect=PermissionError("Permission denied")) + mock_repo.config_writer.return_value = mock_config_writer + + # Create a test working directory to return + test_dir = str(Path(self.tempdir.name).resolve()) + mock_repo.working_tree_dir = test_dir + + # Mock git.Repo to return our mock + with patch('git.Repo', return_value=mock_repo): + result = setup_git(test_dir, io) + + # Verify setup_git completes and returns working directory despite error + self.assertEqual(result, test_dir) + + # Verify warning was shown + warnings = [call[0][0] for call in io.tool_warning.call_args_list] + self.assertTrue(any("Could not write to git config" in warning for warning in warnings)) \ No newline at end of file