Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,23 @@ aider --model sonnet --api-key anthropic=<key>
aider --model o3-mini --api-key openai=<key>
```

### 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
Expand Down
6 changes: 6 additions & 0 deletions aider/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions aider/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
33 changes: 25 additions & 8 deletions aider/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", "[email protected]")
io.tool_warning('Update git email with: git config user.email "[email protected]"')

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", "[email protected]")
io.tool_warning('Update git email with: git config user.email "[email protected]"')
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 \"[email protected]\""
)
# Continue without modifying config, in read-only mode
pass

return repo.working_tree_dir

Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
28 changes: 28 additions & 0 deletions aider/md_renderer.py
Original file line number Diff line number Diff line change
@@ -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))
6 changes: 4 additions & 2 deletions aider/mdstream.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """
Expand Down Expand Up @@ -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()

Expand Down
34 changes: 34 additions & 0 deletions aider/theme_utils.py
Original file line number Diff line number Diff line change
@@ -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
46 changes: 46 additions & 0 deletions tests/basic/test_git_config_network.py
Original file line number Diff line number Diff line change
@@ -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))