diff options
| -rw-r--r-- | continuedev/src/continuedev/steps/core/core.py | 98 | ||||
| -rw-r--r-- | extension/package-lock.json | 4 | ||||
| -rw-r--r-- | extension/package.json | 2 | ||||
| -rw-r--r-- | extension/src/diffs.ts | 65 | ||||
| -rw-r--r-- | extension/src/lang-server/codeLens.ts | 8 | 
5 files changed, 117 insertions, 60 deletions
| diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index 5ea95104..787da316 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -486,58 +486,64 @@ Please output the code to be inserted at the cursor in order to fulfill the user          completion_lines_covered = 0          repeating_file_suffix = False          line_below_highlighted_range = file_suffix.lstrip().split("\n")[0] -        async for chunk in model_to_use.stream_chat(messages, temperature=0, max_tokens=max_tokens): -            # Stop early if it is repeating the file_suffix or the step was deleted -            if repeating_file_suffix: -                break -            if sdk.current_step_was_deleted(): -                return -            # Accumulate lines -            if "content" not in chunk: -                continue -            chunk = chunk["content"] -            chunk_lines = chunk.split("\n") -            chunk_lines[0] = unfinished_line + chunk_lines[0] -            if chunk.endswith("\n"): -                unfinished_line = "" -                chunk_lines.pop()  # because this will be an empty string -            else: -                unfinished_line = chunk_lines.pop() - -            # Deal with newly accumulated lines -            for i in range(len(chunk_lines)): -                # Trailing whitespace doesn't matter -                chunk_lines[i] = chunk_lines[i].rstrip() -                chunk_lines[i] = common_whitespace + chunk_lines[i] - -                # Lines that should signify the end of generation -                if self.is_end_line(chunk_lines[i]): -                    break -                # Lines that should be ignored, like the <> tags -                elif self.line_to_be_ignored(chunk_lines[i], completion_lines_covered == 0): -                    continue -                # Check if we are currently just copying the prefix -                elif (lines_of_prefix_copied > 0 or completion_lines_covered == 0) and lines_of_prefix_copied < len(file_prefix.splitlines()) and chunk_lines[i] == full_file_contents_lines[lines_of_prefix_copied]: -                    # This is a sketchy way of stopping it from repeating the file_prefix. Is a bug if output happens to have a matching line -                    lines_of_prefix_copied += 1 -                    continue -                # Because really short lines might be expected to be repeated, this is only a !heuristic! -                # Stop when it starts copying the file_suffix -                elif chunk_lines[i].strip() == line_below_highlighted_range.strip() and len(chunk_lines[i].strip()) > 4 and not (len(original_lines_below_previous_blocks) > 0 and chunk_lines[i].strip() == original_lines_below_previous_blocks[0].strip()): -                    repeating_file_suffix = True +        generator = model_to_use.stream_chat( +            messages, temperature=0, max_tokens=max_tokens) + +        try: +            async for chunk in generator: +                # Stop early if it is repeating the file_suffix or the step was deleted +                if repeating_file_suffix:                      break +                if sdk.current_step_was_deleted(): +                    return -                # If none of the above, insert the line! -                if False: -                    await handle_generated_line(chunk_lines[i]) +                # Accumulate lines +                if "content" not in chunk: +                    continue +                chunk = chunk["content"] +                chunk_lines = chunk.split("\n") +                chunk_lines[0] = unfinished_line + chunk_lines[0] +                if chunk.endswith("\n"): +                    unfinished_line = "" +                    chunk_lines.pop()  # because this will be an empty string +                else: +                    unfinished_line = chunk_lines.pop() + +                # Deal with newly accumulated lines +                for i in range(len(chunk_lines)): +                    # Trailing whitespace doesn't matter +                    chunk_lines[i] = chunk_lines[i].rstrip() +                    chunk_lines[i] = common_whitespace + chunk_lines[i] + +                    # Lines that should signify the end of generation +                    if self.is_end_line(chunk_lines[i]): +                        break +                    # Lines that should be ignored, like the <> tags +                    elif self.line_to_be_ignored(chunk_lines[i], completion_lines_covered == 0): +                        continue +                    # Check if we are currently just copying the prefix +                    elif (lines_of_prefix_copied > 0 or completion_lines_covered == 0) and lines_of_prefix_copied < len(file_prefix.splitlines()) and chunk_lines[i] == full_file_contents_lines[lines_of_prefix_copied]: +                        # This is a sketchy way of stopping it from repeating the file_prefix. Is a bug if output happens to have a matching line +                        lines_of_prefix_copied += 1 +                        continue +                    # Because really short lines might be expected to be repeated, this is only a !heuristic! +                    # Stop when it starts copying the file_suffix +                    elif chunk_lines[i].strip() == line_below_highlighted_range.strip() and len(chunk_lines[i].strip()) > 4 and not (len(original_lines_below_previous_blocks) > 0 and chunk_lines[i].strip() == original_lines_below_previous_blocks[0].strip()): +                        repeating_file_suffix = True +                        break -                lines.append(chunk_lines[i]) -                completion_lines_covered += 1 -                current_line_in_file += 1 +                    # If none of the above, insert the line! +                    if False: +                        await handle_generated_line(chunk_lines[i]) -            await sendDiffUpdate(lines + [common_whitespace if unfinished_line.startswith("<") else (common_whitespace + unfinished_line)], sdk) +                    lines.append(chunk_lines[i]) +                    completion_lines_covered += 1 +                    current_line_in_file += 1 +                await sendDiffUpdate(lines + [common_whitespace if unfinished_line.startswith("<") else (common_whitespace + unfinished_line)], sdk) +        finally: +            await generator.aclose()          # Add the unfinished line          if unfinished_line != "" and not self.line_to_be_ignored(unfinished_line, completion_lines_covered == 0) and not self.is_end_line(unfinished_line):              unfinished_line = common_whitespace + unfinished_line diff --git a/extension/package-lock.json b/extension/package-lock.json index a235aa81..d84be971 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@  {    "name": "continue", -  "version": "0.0.158", +  "version": "0.0.159",    "lockfileVersion": 2,    "requires": true,    "packages": {      "": {        "name": "continue", -      "version": "0.0.158", +      "version": "0.0.159",        "license": "Apache-2.0",        "dependencies": {          "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 6bb6b720..bdc67cc9 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@    "displayName": "Continue",    "pricing": "Free",    "description": "The open-source coding autopilot", -  "version": "0.0.158", +  "version": "0.0.159",    "publisher": "Continue",    "engines": {      "vscode": "^1.67.0" diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts index 28089fc6..910c30f2 100644 --- a/extension/src/diffs.ts +++ b/extension/src/diffs.ts @@ -9,6 +9,7 @@ interface DiffInfo {    newFilepath: string;    editor?: vscode.TextEditor;    step_index: number; +  range: vscode.Range;  }  export const DIFF_DIRECTORY = path.join(os.homedir(), ".continue", "diffs"); @@ -18,6 +19,10 @@ class DiffManager {    // Doing this because virtual files are read-only    private diffs: Map<string, DiffInfo> = new Map(); +  diffAtNewFilepath(newFilepath: string): DiffInfo | undefined { +    return this.diffs.get(newFilepath); +  } +    private setupDirectory() {      // Make sure the diff directory exists      if (!fs.existsSync(DIFF_DIRECTORY)) { @@ -35,6 +40,10 @@ class DiffManager {      return filepath.replace(/\\/g, "_").replace(/\//g, "_");    } +  private getNewFilepath(originalFilepath: string): string { +    return path.join(DIFF_DIRECTORY, this.escapeFilepath(originalFilepath)); +  } +    private openDiffEditor(      originalFilepath: string,      newFilepath: string @@ -103,18 +112,28 @@ class DiffManager {      this.setupDirectory();      // Create or update existing diff -    const newFilepath = path.join( -      DIFF_DIRECTORY, -      this.escapeFilepath(originalFilepath) -    ); +    const newFilepath = this.getNewFilepath(originalFilepath);      fs.writeFileSync(newFilepath, newContent);      // Open the diff editor if this is a new diff      if (!this.diffs.has(newFilepath)) { +      // Figure out the first line that is different +      const oldContent = fs.readFileSync(originalFilepath).toString("utf-8"); +      let line = 0; +      const newLines = newContent.split("\n"); +      const oldLines = oldContent.split("\n"); +      for (let i = 0; i < newLines.length && i < oldLines.length; i++) { +        if (newLines[i] !== oldLines[i]) { +          line = i; +          break; +        } +      } +        const diffInfo: DiffInfo = {          originalFilepath,          newFilepath,          step_index, +        range: new vscode.Range(line, 0, line + 1, 0),        };        this.diffs.set(newFilepath, diffInfo);      } @@ -139,10 +158,38 @@ class DiffManager {      fs.unlinkSync(diffInfo.newFilepath);    } +  private inferNewFilepath() { +    const activeEditorPath = +      vscode.window.activeTextEditor?.document.uri.fsPath; +    if (activeEditorPath && path.dirname(activeEditorPath) === DIFF_DIRECTORY) { +      return activeEditorPath; +    } +    const visibleEditors = vscode.window.visibleTextEditors.map( +      (editor) => editor.document.uri.fsPath +    ); +    for (const editorPath of visibleEditors) { +      if (path.dirname(editorPath) === DIFF_DIRECTORY) { +        for (const otherEditorPath of visibleEditors) { +          if ( +            path.dirname(otherEditorPath) !== DIFF_DIRECTORY && +            this.getNewFilepath(otherEditorPath) === editorPath +          ) { +            return editorPath; +          } +        } +      } +    } + +    if (this.diffs.size === 1) { +      return Array.from(this.diffs.keys())[0]; +    } +    return undefined; +  } +    acceptDiff(newFilepath?: string) { -    // If no newFilepath is provided and there is only one in the dictionary, use that -    if (!newFilepath && this.diffs.size === 1) { -      newFilepath = Array.from(this.diffs.keys())[0]; +    // When coming from a keyboard shortcut, we have to infer the newFilepath from visible text editors +    if (!newFilepath) { +      newFilepath = this.inferNewFilepath();      }      if (!newFilepath) {        console.log("No newFilepath provided to accept the diff"); @@ -170,8 +217,8 @@ class DiffManager {    rejectDiff(newFilepath?: string) {      // If no newFilepath is provided and there is only one in the dictionary, use that -    if (!newFilepath && this.diffs.size === 1) { -      newFilepath = Array.from(this.diffs.keys())[0]; +    if (!newFilepath) { +      newFilepath = this.inferNewFilepath();      }      if (!newFilepath) {        console.log( diff --git a/extension/src/lang-server/codeLens.ts b/extension/src/lang-server/codeLens.ts index 778b98dc..5800a00e 100644 --- a/extension/src/lang-server/codeLens.ts +++ b/extension/src/lang-server/codeLens.ts @@ -2,7 +2,7 @@ import * as vscode from "vscode";  import { editorToSuggestions, editorSuggestionsLocked } from "../suggestions";  import * as path from "path";  import * as os from "os"; -import { DIFF_DIRECTORY } from "../diffs"; +import { DIFF_DIRECTORY, diffManager } from "../diffs";  class SuggestionsCodeLensProvider implements vscode.CodeLensProvider {    public provideCodeLenses(      document: vscode.TextDocument, @@ -53,7 +53,11 @@ class DiffViewerCodeLensProvider implements vscode.CodeLensProvider {    ): vscode.CodeLens[] | Thenable<vscode.CodeLens[]> {      if (path.dirname(document.uri.fsPath) === DIFF_DIRECTORY) {        const codeLenses: vscode.CodeLens[] = []; -      const range = new vscode.Range(0, 0, 1, 0); +      let range = new vscode.Range(0, 0, 1, 0); +      const diffInfo = diffManager.diffAtNewFilepath(document.uri.fsPath); +      if (diffInfo) { +        range = diffInfo.range; +      }        codeLenses.push(          new vscode.CodeLens(range, {            title: "Accept ✅ (⌘⇧↩)", | 
