diff --git a/aider/__init__.py b/aider/__init__.py index a9686a84cb3..71d22aafe3d 100644 --- a/aider/__init__.py +++ b/aider/__init__.py @@ -1,6 +1,6 @@ from packaging import version -__version__ = "0.88.14.dev" +__version__ = "0.88.15.dev" safe_version = __version__ try: diff --git a/aider/commands.py b/aider/commands.py index 1c3c84f0882..b9ea302db89 100644 --- a/aider/commands.py +++ b/aider/commands.py @@ -414,7 +414,8 @@ async def cmd_lint(self, args="", fnames=None): ) lint_coder.add_rel_fname(fname) - await lint_coder.run(errors) + await self.coder.io.recreate_input() + await lint_coder.run_one(errors, preproc=False) lint_coder.abs_fnames = set() if lint_coder and self.coder.repo.is_dirty() and self.coder.auto_commits: @@ -536,7 +537,7 @@ def cmd_tokens(self, args): tokens = self.coder.main_model.token_count_for_image(fname) else: # approximate - content = f"{relative_fname}\n{fence}\n" + content + "{fence}\n" + content = f"{relative_fname}\n{fence}\n" + content + f"{fence}\n" tokens = self.coder.main_model.token_count(content) file_res.append((tokens, f"{relative_fname}", "/drop to remove")) @@ -556,7 +557,7 @@ def cmd_tokens(self, args): content = self.io.read_text(fname) if content is not None and not is_image_file(relative_fname): # approximate - content = f"{relative_fname}\n{fence}\n" + content + "{fence}\n" + content = f"{relative_fname}\n{fence}\n" + content + f"{fence}\n" tokens = self.coder.main_model.token_count(content) file_res.append((tokens, f"{relative_fname} (read-only)", "/drop to remove")) @@ -1215,9 +1216,17 @@ async def cmd_run(self, args, add_on_nonzero_exit=False): finally: self.cmd_running = False - def cmd_exit(self, args): + async def cmd_exit(self, args): "Exit the application" self.coder.event("exit", reason="/exit") + + for server in self.coder.mcp_servers: + try: + await server.exit_stack.aclose() + except Exception: + pass + + await asyncio.sleep(0) sys.exit() def cmd_quit(self, args): diff --git a/aider/io.py b/aider/io.py index 35d9fdb083f..b307790d576 100644 --- a/aider/io.py +++ b/aider/io.py @@ -699,6 +699,8 @@ async def recreate_input(self, future=None): if coder: self.input_task = asyncio.create_task(coder.get_input()) await asyncio.sleep(0) + else: + self.input_task = asyncio.create_task(self.get_input(None, [], [], [])) async def get_input( self, @@ -1359,13 +1361,18 @@ def stream_output(self, text, final=False): incomplete_line = "" output = "" + lines = self.remove_consecutive_empty_strings(lines) + needs_new_line = False if len(lines) == 2 and lines[0] and not lines[-1] else True + if len(lines) > 1 or final: # All lines except the last one are complete complete_lines = lines[:-1] if not final else lines incomplete_line = lines[-1] if not final else "" + last_index = len(complete_lines) - 1 - for complete_line in complete_lines: + for index, complete_line in enumerate(complete_lines): output += complete_line + output += "\n" if needs_new_line and index != last_index else "" self._stream_line_count += 1 self._stream_buffer = incomplete_line @@ -1380,6 +1387,17 @@ def stream_output(self, text, final=False): self.console.print(Text.from_ansi(output) if self.has_ansi_codes(output) else output) self.reset_streaming_response() + def remove_consecutive_empty_strings(self, string_list): + new_list = [] + first_item = True + + for item in string_list: + if first_item or item != "" or (new_list and new_list[-1] != ""): + first_item = False + new_list.append(item) + + return new_list + def has_ansi_codes(self, s: str) -> bool: """Check if a string contains the ANSI escape character.""" return "\x1b" in s diff --git a/aider/main.py b/aider/main.py index 46f613bb2f7..c05c6da5a05 100644 --- a/aider/main.py +++ b/aider/main.py @@ -124,7 +124,7 @@ async def setup_git(git_root, io): ) return elif cwd and await io.confirm_ask( - "No git repo found, create one to track aider's changes (recommended)?" + "No git repo found, create one to track aider's changes (recommended)?", acknowledge=True ): git_root = str(cwd.resolve()) repo = await make_new_repo(git_root, io) @@ -193,7 +193,8 @@ async def check_gitignore(git_root, io, ask=True): if ask: io.tool_output("You can skip this check with --no-gitignore") if not await io.confirm_ask( - f"Add {', '.join(patterns_to_add)} to .gitignore (recommended)?" + f"Add {', '.join(patterns_to_add)} to .gitignore (recommended)?", + acknowledge=True, ): return @@ -717,28 +718,27 @@ def get_io(pretty): posthog_host=args.analytics_posthog_host, posthog_project_api_key=args.analytics_posthog_project_api_key, ) - if args.analytics is not False: - if analytics.need_to_ask(args.analytics): - io.tool_output( - "Aider respects your privacy and never collects your code, chat messages, keys or" - " personal info." - ) - io.tool_output(f"For more info: {urls.analytics}") - disable = not await io.confirm_ask( - "Allow collection of anonymous analytics to help improve aider?" - ) - - analytics.asked_opt_in = True - if disable: - analytics.disable(permanently=True) - io.tool_output("Analytics have been permanently disabled.") - - analytics.save_data() - io.tool_output() - - # This is a no-op if the user has opted out - analytics.enable() + # if args.analytics is not False: + # if analytics.need_to_ask(args.analytics): + # io.tool_output( + # "Aider respects your privacy and never collects your code, chat messages, keys or" + # " personal info." + # ) + # io.tool_output(f"For more info: {urls.analytics}") + # disable = not await io.confirm_ask( + # "Allow collection of anonymous analytics to help improve aider?" + # ) + # analytics.asked_opt_in = True + # if disable: + # analytics.disable(permanently=True) + # io.tool_output("Analytics have been permanently disabled.") + # analytics.save_data() + # io.tool_output() + # # This is a no-op if the user has opted out + # analytics.enable() + + analytics.disable(permanently=True) analytics.event("launched") if args.gui and not return_coder: @@ -821,11 +821,6 @@ def get_io(pretty): if args.check_update: check_version(io, verbose=args.verbose) - if args.git: - git_root = await setup_git(git_root, io) - if args.gitignore: - await check_gitignore(git_root, io) - if args.verbose: show = format_settings(parser, args) io.tool_output(show) @@ -1118,6 +1113,11 @@ def get_io(pretty): analytics.event("exit", reason="Keyboard interrupt during model warnings") return 1 + if args.git: + git_root = await setup_git(git_root, io) + if args.gitignore: + await check_gitignore(git_root, io) + except UnknownEditFormat as err: io.tool_error(str(err)) await io.offer_url(urls.edit_formats, "Open documentation about edit formats?") diff --git a/aider/onboarding.py b/aider/onboarding.py index f552e33e82b..0299d8cb6f4 100644 --- a/aider/onboarding.py +++ b/aider/onboarding.py @@ -93,6 +93,7 @@ async def offer_openrouter_oauth(io, analytics): if await io.confirm_ask( "Login to OpenRouter or create a free account?", default="y", + acknowledge=True, ): analytics.event("oauth_flow_initiated", provider="openrouter") openrouter_key = start_openrouter_oauth_flow(io, analytics)