From b1a39567addc511ac0b477aaedaae4e10d7f5d31 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Tue, 11 Jul 2023 11:31:41 -0700
Subject: explain insert at cursor, better diff streaming

---
 extension/react-app/src/components/ComboBox.tsx | 32 +++++++++++++++++++++----
 1 file changed, 27 insertions(+), 5 deletions(-)

(limited to 'extension/react-app/src/components/ComboBox.tsx')

diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 801c3a03..585a0584 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -180,6 +180,26 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
 
   useImperativeHandle(ref, () => downshiftProps, [downshiftProps]);
 
+  const [metaKeyPressed, setMetaKeyPressed] = useState(false);
+  useEffect(() => {
+    const handleKeyDown = (e: KeyboardEvent) => {
+      if (e.key === "Meta") {
+        setMetaKeyPressed(true);
+      }
+    };
+    const handleKeyUp = (e: KeyboardEvent) => {
+      if (e.key === "Meta") {
+        setMetaKeyPressed(false);
+      }
+    };
+    window.addEventListener("keydown", handleKeyDown);
+    window.addEventListener("keyup", handleKeyUp);
+    return () => {
+      window.removeEventListener("keydown", handleKeyDown);
+      window.removeEventListener("keyup", handleKeyUp);
+    };
+  });
+
   useEffect(() => {
     if (!inputRef.current) {
       return;
@@ -272,7 +292,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
       <div className="flex px-2" ref={divRef} hidden={!downshiftProps.isOpen}>
         <MainTextInput
           disabled={props.disabled}
-          placeholder="Ask a question, give instructions, or type '/' to see slash commands"
+          placeholder="Ask a question, give instructions, or type '/' to see slash commands. ⌘⏎ to edit."
           {...getInputProps({
             onChange: (e) => {
               const target = e.target as HTMLTextAreaElement;
@@ -357,10 +377,12 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
             ))}
         </Ul>
       </div>
-      {/* <span className="text-trueGray-400 ml-auto m-auto text-xs text-right">
-        Highlight code to include as context. Currently open file included by
-        default. {highlightedCodeSections.length === 0 && ""}
-      </span> */}
+      {highlightedCodeSections.length === 0 &&
+        (downshiftProps.inputValue?.startsWith("/edit") || metaKeyPressed) && (
+          <div className="text-trueGray-400 pr-4 text-xs text-right">
+            Inserting at cursor
+          </div>
+        )}
       <ContextDropdown
         onMouseEnter={() => {
           setHoveringContextDropdown(true);
-- 
cgit v1.2.3-70-g09d2


From 3fc16804fe74c38d4f94c25281025a84fd51771b Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Tue, 11 Jul 2023 14:13:14 -0700
Subject: little ui stuff

---
 extension/react-app/src/components/ComboBox.tsx |  3 ++-
 extension/react-app/src/tabs/gui.tsx            | 11 +++--------
 2 files changed, 5 insertions(+), 9 deletions(-)

(limited to 'extension/react-app/src/components/ComboBox.tsx')

diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 585a0584..ac994b0a 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -378,7 +378,8 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
         </Ul>
       </div>
       {highlightedCodeSections.length === 0 &&
-        (downshiftProps.inputValue?.startsWith("/edit") || metaKeyPressed) && (
+        (downshiftProps.inputValue?.startsWith("/edit") ||
+          (metaKeyPressed && downshiftProps.inputValue?.length > 0)) && (
           <div className="text-trueGray-400 pr-4 text-xs text-right">
             Inserting at cursor
           </div>
diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx
index 0e60e05c..619b91e1 100644
--- a/extension/react-app/src/tabs/gui.tsx
+++ b/extension/react-app/src/tabs/gui.tsx
@@ -170,6 +170,7 @@ function GUI(props: GUIProps) {
       const waitingForSteps =
         state.active &&
         state.history.current_index < state.history.timeline.length &&
+        state.history.timeline[state.history.current_index] &&
         state.history.timeline[
           state.history.current_index
         ].step.description?.trim() === "";
@@ -236,14 +237,14 @@ function GUI(props: GUIProps) {
         history.current_index < history.timeline.length
       ) {
         if (
-          history.timeline[history.current_index].step.name ===
+          history.timeline[history.current_index]?.step.name ===
           "Waiting for user input"
         ) {
           if (input.trim() === "") return;
           onStepUserInput(input, history!.current_index);
           return;
         } else if (
-          history.timeline[history.current_index].step.name ===
+          history.timeline[history.current_index]?.step.name ===
           "Waiting for user confirmation"
         ) {
           onStepUserInput("ok", history!.current_index);
@@ -350,12 +351,6 @@ function GUI(props: GUIProps) {
         </div>
 
         <ComboBox
-          // disabled={
-          //   history?.timeline.length
-          //     ? history.timeline[history.current_index].step.name ===
-          //       "Waiting for user confirmation"
-          //     : false
-          // }
           ref={mainTextInputRef}
           onEnter={(e) => {
             onMainTextInput(e);
-- 
cgit v1.2.3-70-g09d2


From 391764f1371dab06af30a29e10a826a516b69bb3 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Wed, 12 Jul 2023 21:53:06 -0700
Subject: persist state and reconnect automatically

---
 continuedev/src/continuedev/core/autopilot.py      |  17 +++-
 continuedev/src/continuedev/libs/constants/main.py |   6 ++
 continuedev/src/continuedev/libs/util/paths.py     |  17 ++++
 continuedev/src/continuedev/server/gui.py          |  10 +-
 continuedev/src/continuedev/server/ide.py          |  25 +++--
 continuedev/src/continuedev/server/ide_protocol.py |  10 +-
 continuedev/src/continuedev/server/main.py         |  16 ++-
 .../src/continuedev/server/session_manager.py      |  41 ++++++--
 docs/docs/concepts/ide.md                          |   4 +-
 extension/react-app/src/components/ComboBox.tsx    |   1 -
 extension/react-app/src/hooks/messenger.ts         |  10 --
 extension/react-app/src/pages/gui.tsx              |   1 -
 extension/src/activation/activate.ts               |   2 +-
 extension/src/continueIdeClient.ts                 |  62 ++++++++---
 extension/src/debugPanel.ts                        | 113 ++++-----------------
 extension/src/util/messenger.ts                    |  10 ++
 16 files changed, 192 insertions(+), 153 deletions(-)
 create mode 100644 continuedev/src/continuedev/libs/constants/main.py
 create mode 100644 continuedev/src/continuedev/libs/util/paths.py

(limited to 'extension/react-app/src/components/ComboBox.tsx')

diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index 1b074435..e1c8a076 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -1,13 +1,13 @@
 from functools import cached_property
 import traceback
 import time
-from typing import Any, Callable, Coroutine, Dict, List
+from typing import Any, Callable, Coroutine, Dict, List, Union
 import os
 from aiohttp import ClientPayloadError
+from pydantic import root_validator
 
 from ..models.filesystem import RangeInFileWithContents
 from ..models.filesystem_edit import FileEditWithFullContents
-from ..libs.llm import LLM
 from .observation import Observation, InternalErrorObservation
 from ..server.ide_protocol import AbstractIdeProtocolServer
 from ..libs.util.queue import AsyncSubscriptionQueue
@@ -16,7 +16,6 @@ from .main import Context, ContinueCustomException, HighlightedRangeContext, Pol
 from ..steps.core.core import ReversibleStep, ManualEditStep, UserInputStep
 from ..libs.util.telemetry import capture_event
 from .sdk import ContinueSDK
-import asyncio
 from ..libs.util.step_name_to_steps import get_step_from_name
 from ..libs.util.traceback_parsers import get_python_traceback, get_javascript_traceback
 from openai import error as openai_errors
@@ -46,6 +45,7 @@ class Autopilot(ContinueBaseModel):
     ide: AbstractIdeProtocolServer
     history: History = History.from_empty()
     context: Context = Context()
+    full_state: Union[FullState, None] = None
     _on_update_callbacks: List[Callable[[FullState], None]] = []
 
     _active: bool = False
@@ -63,8 +63,15 @@ class Autopilot(ContinueBaseModel):
         arbitrary_types_allowed = True
         keep_untouched = (cached_property,)
 
+    @root_validator(pre=True)
+    def fill_in_values(cls, values):
+        full_state: FullState = values.get('full_state')
+        if full_state is not None:
+            values['history'] = full_state.history
+        return values
+
     def get_full_state(self) -> FullState:
-        return FullState(
+        full_state = FullState(
             history=self.history,
             active=self._active,
             user_input_queue=self._main_user_input_queue,
@@ -73,6 +80,8 @@ class Autopilot(ContinueBaseModel):
             slash_commands=self.get_available_slash_commands(),
             adding_highlighted_code=self._adding_highlighted_code,
         )
+        self.full_state = full_state
+        return full_state
 
     def get_available_slash_commands(self) -> List[Dict]:
         custom_commands = list(map(lambda x: {
diff --git a/continuedev/src/continuedev/libs/constants/main.py b/continuedev/src/continuedev/libs/constants/main.py
new file mode 100644
index 00000000..96eb6e69
--- /dev/null
+++ b/continuedev/src/continuedev/libs/constants/main.py
@@ -0,0 +1,6 @@
+## PATHS ##
+
+CONTINUE_GLOBAL_FOLDER = ".continue"
+CONTINUE_SESSIONS_FOLDER = "sessions"
+CONTINUE_SERVER_FOLDER = "server"
+
diff --git a/continuedev/src/continuedev/libs/util/paths.py b/continuedev/src/continuedev/libs/util/paths.py
new file mode 100644
index 00000000..fddef887
--- /dev/null
+++ b/continuedev/src/continuedev/libs/util/paths.py
@@ -0,0 +1,17 @@
+import os
+
+from ..constants.main import CONTINUE_SESSIONS_FOLDER, CONTINUE_GLOBAL_FOLDER, CONTINUE_SERVER_FOLDER
+
+def getGlobalFolderPath():
+    return os.path.join(os.path.expanduser("~"), CONTINUE_GLOBAL_FOLDER)
+
+
+
+def getSessionsFolderPath():
+    return os.path.join(getGlobalFolderPath(), CONTINUE_SESSIONS_FOLDER)
+
+def getServerFolderPath():
+    return os.path.join(getGlobalFolderPath(), CONTINUE_SERVER_FOLDER)
+
+def getSessionFilePath(session_id: str):
+    return os.path.join(getSessionsFolderPath(), f"{session_id}.json")
\ No newline at end of file
diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py
index 21089f30..8f6f68f6 100644
--- a/continuedev/src/continuedev/server/gui.py
+++ b/continuedev/src/continuedev/server/gui.py
@@ -31,12 +31,12 @@ class AppStatus:
 Server.handle_exit = AppStatus.handle_exit
 
 
-def session(x_continue_session_id: str = Header("anonymous")) -> Session:
-    return session_manager.get_session(x_continue_session_id)
+async def session(x_continue_session_id: str = Header("anonymous")) -> Session:
+    return await session_manager.get_session(x_continue_session_id)
 
 
-def websocket_session(session_id: str) -> Session:
-    return session_manager.get_session(session_id)
+async def websocket_session(session_id: str) -> Session:
+    return await session_manager.get_session(session_id)
 
 
 T = TypeVar("T", bound=BaseModel)
@@ -199,4 +199,6 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we
         print("Closing gui websocket")
         if websocket.client_state != WebSocketState.DISCONNECTED:
             await websocket.close()
+
+        session_manager.persist_session(session.session_id)
         session_manager.remove_session(session.session_id)
diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py
index 4645b49e..12a21f19 100644
--- a/continuedev/src/continuedev/server/ide.py
+++ b/continuedev/src/continuedev/server/ide.py
@@ -52,9 +52,11 @@ class FileEditsUpdate(BaseModel):
 class OpenFilesResponse(BaseModel):
     openFiles: List[str]
 
+
 class VisibleFilesResponse(BaseModel):
     visibleFiles: List[str]
 
+
 class HighlightedCodeResponse(BaseModel):
     highlightedCode: List[RangeInFile]
 
@@ -115,6 +117,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
     websocket: WebSocket
     session_manager: SessionManager
     sub_queue: AsyncSubscriptionQueue = AsyncSubscriptionQueue()
+    session_id: Union[str, None] = None
 
     def __init__(self, session_manager: SessionManager, websocket: WebSocket):
         self.websocket = websocket
@@ -132,8 +135,6 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
                 continue
             message_type = message["messageType"]
             data = message["data"]
-            # if message_type == "openGUI":
-            #     await self.openGUI()
             if message_type == "workspaceDirectory":
                 self.workspace_directory = data["workspaceDirectory"]
                 break
@@ -158,8 +159,8 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
         return resp_model.parse_obj(resp)
 
     async def handle_json(self, message_type: str, data: Any):
-        if message_type == "openGUI":
-            await self.openGUI()
+        if message_type == "getSessionId":
+            await self.getSessionId()
         elif message_type == "setFileOpen":
             await self.setFileOpen(data["filepath"], data["open"])
         elif message_type == "setSuggestionsLocked":
@@ -217,9 +218,10 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
             "locked": locked
         })
 
-    async def openGUI(self):
-        session_id = self.session_manager.new_session(self)
-        await self._send_json("openGUI", {
+    async def getSessionId(self):
+        session_id = self.session_manager.new_session(
+            self, self.session_id).session_id
+        await self._send_json("getSessionId", {
             "sessionId": session_id
         })
 
@@ -304,7 +306,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
     async def getOpenFiles(self) -> List[str]:
         resp = await self._send_and_receive_json({}, OpenFilesResponse, "openFiles")
         return resp.openFiles
-    
+
     async def getVisibleFiles(self) -> List[str]:
         resp = await self._send_and_receive_json({}, VisibleFilesResponse, "visibleFiles")
         return resp.visibleFiles
@@ -416,7 +418,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
 
 
 @router.websocket("/ws")
-async def websocket_endpoint(websocket: WebSocket):
+async def websocket_endpoint(websocket: WebSocket, session_id: str = None):
     try:
         await websocket.accept()
         print("Accepted websocket connection from, ", websocket.client)
@@ -434,6 +436,9 @@ async def websocket_endpoint(websocket: WebSocket):
                 ideProtocolServer.handle_json(message_type, data))
 
         ideProtocolServer = IdeProtocolServer(session_manager, websocket)
+        ideProtocolServer.session_id = session_id
+        if session_id is not None:
+            session_manager.registered_ides[session_id] = ideProtocolServer
         other_msgs = await ideProtocolServer.initialize()
 
         for other_msg in other_msgs:
@@ -454,3 +459,5 @@ async def websocket_endpoint(websocket: WebSocket):
     finally:
         if websocket.client_state != WebSocketState.DISCONNECTED:
             await websocket.close()
+
+        session_manager.registered_ides.pop(ideProtocolServer.session_id)
diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py
index 2783dc61..2f78cf0e 100644
--- a/continuedev/src/continuedev/server/ide_protocol.py
+++ b/continuedev/src/continuedev/server/ide_protocol.py
@@ -1,5 +1,6 @@
-from typing import Any, List
+from typing import Any, List, Union
 from abc import ABC, abstractmethod, abstractproperty
+from fastapi import WebSocket
 
 from ..models.main import Traceback
 from ..models.filesystem_edit import FileEdit, FileSystemEdit, EditDiff
@@ -7,6 +8,9 @@ from ..models.filesystem import RangeInFile, RangeInFileWithContents
 
 
 class AbstractIdeProtocolServer(ABC):
+    websocket: WebSocket
+    session_id: Union[str, None]
+
     @abstractmethod
     async def handle_json(self, data: Any):
         """Handle a json message"""
@@ -24,8 +28,8 @@ class AbstractIdeProtocolServer(ABC):
         """Set whether suggestions are locked"""
 
     @abstractmethod
-    async def openGUI(self):
-        """Open a GUI"""
+    async def getSessionId(self):
+        """Get a new session ID"""
 
     @abstractmethod
     async def showSuggestionsAndWait(self, suggestions: List[FileEdit]) -> bool:
diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py
index f4d82903..aa093853 100644
--- a/continuedev/src/continuedev/server/main.py
+++ b/continuedev/src/continuedev/server/main.py
@@ -4,7 +4,8 @@ from fastapi import FastAPI
 from fastapi.middleware.cors import CORSMiddleware
 from .ide import router as ide_router
 from .gui import router as gui_router
-import logging
+from .session_manager import session_manager
+import atexit
 import uvicorn
 import argparse
 
@@ -44,5 +45,16 @@ def run_server():
     uvicorn.run(app, host="0.0.0.0", port=args.port)
 
 
+def cleanup():
+    print("Cleaning up sessions")
+    for session_id in session_manager.sessions:
+        session_manager.persist_session(session_id)
+
+
+atexit.register(cleanup)
 if __name__ == "__main__":
-    run_server()
+    try:
+        run_server()
+    except Exception as e:
+        cleanup()
+        raise e
diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py
index 7147dcfa..fb8ac386 100644
--- a/continuedev/src/continuedev/server/session_manager.py
+++ b/continuedev/src/continuedev/server/session_manager.py
@@ -1,9 +1,12 @@
-from asyncio import BaseEventLoop
+import os
 from fastapi import WebSocket
 from typing import Any, Dict, List, Union
 from uuid import uuid4
+import json
 
+from ..libs.util.paths import getSessionFilePath, getSessionsFolderPath
 from ..models.filesystem_edit import FileEditWithFullContents
+from ..libs.constants.main import CONTINUE_SESSIONS_FOLDER
 from ..core.policy import DemoPolicy
 from ..core.main import FullState
 from ..core.autopilot import Autopilot
@@ -39,17 +42,35 @@ class DemoAutopilot(Autopilot):
 
 class SessionManager:
     sessions: Dict[str, Session] = {}
+    # Mapping of session_id to IDE, where the IDE is still alive
+    registered_ides: Dict[str, AbstractIdeProtocolServer] = {}
 
-    def get_session(self, session_id: str) -> Session:
+    async def get_session(self, session_id: str) -> Session:
         if session_id not in self.sessions:
+            # Check then whether it is persisted by listing all files in the sessions folder
+            # And only if the IDE is still alive
+            sessions_folder = getSessionsFolderPath()
+            session_files = os.listdir(sessions_folder)
+            if f"{session_id}.json" in session_files and session_id in self.registered_ides:
+                if self.registered_ides[session_id].session_id is not None:
+                    return self.new_session(self.registered_ides[session_id], session_id=session_id)
+
             raise KeyError("Session ID not recognized", session_id)
         return self.sessions[session_id]
 
-    def new_session(self, ide: AbstractIdeProtocolServer) -> str:
-        autopilot = DemoAutopilot(policy=DemoPolicy(), ide=ide)
-        session_id = str(uuid4())
+    def new_session(self, ide: AbstractIdeProtocolServer, session_id: Union[str, None] = None) -> Session:
+        full_state = None
+        if session_id is not None and os.path.exists(getSessionFilePath(session_id)):
+            with open(getSessionFilePath(session_id), "r") as f:
+                full_state = FullState(**json.load(f))
+
+        autopilot = DemoAutopilot(
+            policy=DemoPolicy(), ide=ide, full_state=full_state)
+        session_id = session_id or str(uuid4())
+        ide.session_id = session_id
         session = Session(session_id=session_id, autopilot=autopilot)
         self.sessions[session_id] = session
+        self.registered_ides[session_id] = ide
 
         async def on_update(state: FullState):
             await session_manager.send_ws_data(session_id, "state_update", {
@@ -58,11 +79,19 @@ class SessionManager:
 
         autopilot.on_update(on_update)
         create_async_task(autopilot.run_policy())
-        return session_id
+        return session
 
     def remove_session(self, session_id: str):
         del self.sessions[session_id]
 
+    def persist_session(self, session_id: str):
+        """Save the session's FullState as a json file"""
+        full_state = self.sessions[session_id].autopilot.get_full_state()
+        if not os.path.exists(getSessionsFolderPath()):
+            os.mkdir(getSessionsFolderPath())
+        with open(getSessionFilePath(session_id), "w") as f:
+            json.dump(full_state.dict(), f)
+
     def register_websocket(self, session_id: str, ws: WebSocket):
         self.sessions[session_id].ws = ws
         print("Registered websocket for session", session_id)
diff --git a/docs/docs/concepts/ide.md b/docs/docs/concepts/ide.md
index dc7b9e23..bd31481b 100644
--- a/docs/docs/concepts/ide.md
+++ b/docs/docs/concepts/ide.md
@@ -41,9 +41,9 @@ Get the workspace directory
 
 Set whether a file is open
 
-### openGUI
+### getSessionId
 
-Open a gui
+Get a new session ID
 
 ### showSuggestionsAndWait
 
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index ac994b0a..7d6541c7 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -331,7 +331,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
               ) {
                 (event.nativeEvent as any).preventDownshiftDefault = true;
               } else if (event.key === "ArrowUp") {
-                console.log("OWJFOIJO");
                 if (positionInHistory == 0) return;
                 else if (
                   positionInHistory == history.length &&
diff --git a/extension/react-app/src/hooks/messenger.ts b/extension/react-app/src/hooks/messenger.ts
index e2a0bab8..00ce1fbb 100644
--- a/extension/react-app/src/hooks/messenger.ts
+++ b/extension/react-app/src/hooks/messenger.ts
@@ -1,6 +1,3 @@
-// console.log("Websocket import");
-// const WebSocket = require("ws");
-
 export abstract class Messenger {
   abstract send(messageType: string, data: object): void;
 
@@ -28,13 +25,6 @@ export class WebsocketMessenger extends Messenger {
   private serverUrl: string;
 
   _newWebsocket(): WebSocket {
-    // // Dynamic import, because WebSocket is builtin with browser, but not with node. And can't use require in browser.
-    // if (typeof process === "object") {
-    //   console.log("Using node");
-    //   // process is only available in Node
-    //   var WebSocket = require("ws");
-    // }
-
     const newWebsocket = new WebSocket(this.serverUrl);
     for (const listener of this.onOpenListeners) {
       this.onOpen(listener);
diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
index b9382bd1..4ff260fa 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -262,7 +262,6 @@ function GUI(props: GUIProps) {
 
   const onStepUserInput = (input: string, index: number) => {
     if (!client) return;
-    console.log("Sending step user input", input, index);
     client.sendStepUserInput(input, index);
   };
 
diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts
index 559caf44..cd885b12 100644
--- a/extension/src/activation/activate.ts
+++ b/extension/src/activation/activate.ts
@@ -56,7 +56,7 @@ export async function activateExtension(context: vscode.ExtensionContext) {
   registerAllCodeLensProviders(context);
   registerAllCommands(context);
 
-  // Initialize IDE Protocol Client, then call "openGUI"
+  // Initialize IDE Protocol Client
   const serverUrl = getContinueServerUrl();
   ideProtocolClient = new IdeProtocolClient(
     `${serverUrl.replace("http", "ws")}/ide/ws`,
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts
index b728833f..4c1fdf1e 100644
--- a/extension/src/continueIdeClient.ts
+++ b/extension/src/continueIdeClient.ts
@@ -1,10 +1,9 @@
-// import { ShowSuggestionRequest } from "../schema/ShowSuggestionRequest";
 import {
   editorSuggestionsLocked,
   showSuggestion as showSuggestionInEditor,
   SuggestionRanges,
 } from "./suggestions";
-import { openEditorAndRevealRange, getRightViewColumn } from "./util/vscode";
+import { openEditorAndRevealRange } from "./util/vscode";
 import { FileEdit } from "../schema/FileEdit";
 import { RangeInFile } from "../schema/RangeInFile";
 import * as vscode from "vscode";
@@ -15,8 +14,6 @@ import {
 import { FileEditWithFullContents } from "../schema/FileEditWithFullContents";
 import fs = require("fs");
 import { WebsocketMessenger } from "./util/messenger";
-import * as path from "path";
-import * as os from "os";
 import { diffManager } from "./diffs";
 
 class IdeProtocolClient {
@@ -27,17 +24,54 @@ class IdeProtocolClient {
 
   private _highlightDebounce: NodeJS.Timeout | null = null;
 
-  constructor(serverUrl: string, context: vscode.ExtensionContext) {
-    this.context = context;
+  private _lastReloadTime: number = 16;
+  private _reconnectionTimeouts: NodeJS.Timeout[] = [];
+
+  private _sessionId: string | null = null;
+  private _serverUrl: string;
 
-    let messenger = new WebsocketMessenger(serverUrl);
+  private _newWebsocketMessenger() {
+    const requestUrl =
+      this._serverUrl +
+      (this._sessionId ? `?session_id=${this._sessionId}` : "");
+    const messenger = new WebsocketMessenger(requestUrl);
     this.messenger = messenger;
-    messenger.onClose(() => {
+
+    const reconnect = () => {
+      console.log("Trying to reconnect IDE protocol websocket...");
       this.messenger = null;
+
+      // Exponential backoff to reconnect
+      this._reconnectionTimeouts.forEach((to) => clearTimeout(to));
+
+      const timeout = setTimeout(() => {
+        if (this.messenger?.websocket?.readyState === 1) {
+          return;
+        }
+        this._newWebsocketMessenger();
+      }, this._lastReloadTime);
+
+      this._reconnectionTimeouts.push(timeout);
+      this._lastReloadTime = Math.min(2 * this._lastReloadTime, 5000);
+    };
+    messenger.onOpen(() => {
+      this._reconnectionTimeouts.forEach((to) => clearTimeout(to));
+    });
+    messenger.onClose(() => {
+      reconnect();
+    });
+    messenger.onError(() => {
+      reconnect();
     });
     messenger.onMessage((messageType, data, messenger) => {
       this.handleMessage(messageType, data, messenger);
     });
+  }
+
+  constructor(serverUrl: string, context: vscode.ExtensionContext) {
+    this.context = context;
+    this._serverUrl = serverUrl;
+    this._newWebsocketMessenger();
 
     // Setup listeners for any file changes in open editors
     // vscode.workspace.onDidChangeTextDocument((event) => {
@@ -171,7 +205,7 @@ class IdeProtocolClient {
       case "showDiff":
         this.showDiff(data.filepath, data.replacement, data.step_index);
         break;
-      case "openGUI":
+      case "getSessionId":
       case "connected":
         break;
       default:
@@ -284,10 +318,6 @@ class IdeProtocolClient {
   // ------------------------------------ //
   // Initiate Request
 
-  async openGUI(asRightWebviewPanel: boolean = false) {
-    // Open the webview panel
-  }
-
   async getSessionId(): Promise<string> {
     await new Promise((resolve, reject) => {
       // Repeatedly try to connect to the server
@@ -303,10 +333,10 @@ class IdeProtocolClient {
         }
       }, 1000);
     });
-    const resp = await this.messenger?.sendAndReceive("openGUI", {});
-    const sessionId = resp.sessionId;
+    const resp = await this.messenger?.sendAndReceive("getSessionId", {});
     // console.log("New Continue session with ID: ", sessionId);
-    return sessionId;
+    this._sessionId = resp.sessionId;
+    return resp.sessionId;
   }
 
   acceptRejectSuggestion(accept: boolean, key: SuggestionRanges) {
diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts
index 487bbedf..5e1689d1 100644
--- a/extension/src/debugPanel.ts
+++ b/extension/src/debugPanel.ts
@@ -8,76 +8,6 @@ import {
 import { RangeInFile } from "./client";
 const WebSocket = require("ws");
 
-class StreamManager {
-  private _fullText: string = "";
-  private _insertionPoint: vscode.Position | undefined;
-
-  private _addToEditor(update: string) {
-    let editor =
-      vscode.window.activeTextEditor || vscode.window.visibleTextEditors[0];
-
-    if (typeof this._insertionPoint === "undefined") {
-      if (editor?.selection.isEmpty) {
-        this._insertionPoint = editor?.selection.active;
-      } else {
-        this._insertionPoint = editor?.selection.end;
-      }
-    }
-    editor?.edit((editBuilder) => {
-      if (this._insertionPoint) {
-        editBuilder.insert(this._insertionPoint, update);
-        this._insertionPoint = this._insertionPoint.translate(
-          Array.from(update.matchAll(/\n/g)).length,
-          update.length
-        );
-      }
-    });
-  }
-
-  public closeStream() {
-    this._fullText = "";
-    this._insertionPoint = undefined;
-    this._codeBlockStatus = "closed";
-    this._pendingBackticks = 0;
-  }
-
-  private _codeBlockStatus: "open" | "closed" | "language-descriptor" =
-    "closed";
-  private _pendingBackticks: number = 0;
-  public onStreamUpdate(update: string) {
-    let textToInsert = "";
-    for (let i = 0; i < update.length; i++) {
-      switch (this._codeBlockStatus) {
-        case "closed":
-          if (update[i] === "`" && this._fullText.endsWith("``")) {
-            this._codeBlockStatus = "language-descriptor";
-          }
-          break;
-        case "language-descriptor":
-          if (update[i] === " " || update[i] === "\n") {
-            this._codeBlockStatus = "open";
-          }
-          break;
-        case "open":
-          if (update[i] === "`") {
-            if (this._fullText.endsWith("``")) {
-              this._codeBlockStatus = "closed";
-              this._pendingBackticks = 0;
-            } else {
-              this._pendingBackticks += 1;
-            }
-          } else {
-            textToInsert += "`".repeat(this._pendingBackticks) + update[i];
-            this._pendingBackticks = 0;
-          }
-          break;
-      }
-      this._fullText += update[i];
-    }
-    this._addToEditor(textToInsert);
-  }
-}
-
 let websocketConnections: { [url: string]: WebsocketConnection | undefined } =
   {};
 
@@ -127,8 +57,6 @@ class WebsocketConnection {
   }
 }
 
-let streamManager = new StreamManager();
-
 export let debugPanelWebview: vscode.Webview | undefined;
 export function setupDebugPanel(
   panel: vscode.WebviewPanel | vscode.WebviewView,
@@ -147,10 +75,7 @@ export function setupDebugPanel(
     .toString();
 
   const isProduction = true; // context?.extensionMode === vscode.ExtensionMode.Development;
-  if (!isProduction) {
-    scriptUri = "http://localhost:5173/src/main.tsx";
-    styleMainUri = "http://localhost:5173/src/main.css";
-  } else {
+  if (isProduction) {
     scriptUri = debugPanelWebview
       .asWebviewUri(
         vscode.Uri.joinPath(extensionUri, "react-app/dist/assets/index.js")
@@ -161,6 +86,9 @@ export function setupDebugPanel(
         vscode.Uri.joinPath(extensionUri, "react-app/dist/assets/index.css")
       )
       .toString();
+  } else {
+    scriptUri = "http://localhost:5173/src/main.tsx";
+    styleMainUri = "http://localhost:5173/src/main.css";
   }
 
   panel.webview.options = {
@@ -175,11 +103,11 @@ export function setupDebugPanel(
       return;
     }
 
-    let rangeInFile: RangeInFile = {
+    const rangeInFile: RangeInFile = {
       range: e.selections[0],
       filepath: e.textEditor.document.fileName,
     };
-    let filesystem = {
+    const filesystem = {
       [rangeInFile.filepath]: e.textEditor.document.getText(),
     };
     panel.webview.postMessage({
@@ -217,13 +145,19 @@ export function setupDebugPanel(
           url,
         });
       };
-      const connection = new WebsocketConnection(
-        url,
-        onMessage,
-        onOpen,
-        onClose
-      );
-      websocketConnections[url] = connection;
+      try {
+        const connection = new WebsocketConnection(
+          url,
+          onMessage,
+          onOpen,
+          onClose
+        );
+        websocketConnections[url] = connection;
+        resolve(null);
+      } catch (e) {
+        console.log("Caught it!: ", e);
+        reject(e);
+      }
     });
   }
 
@@ -292,15 +226,6 @@ export function setupDebugPanel(
         openEditorAndRevealRange(data.path, undefined, vscode.ViewColumn.One);
         break;
       }
-      case "streamUpdate": {
-        // Write code at the position of the cursor
-        streamManager.onStreamUpdate(data.update);
-        break;
-      }
-      case "closeStream": {
-        streamManager.closeStream();
-        break;
-      }
       case "withProgress": {
         // This message allows withProgress to be used in the webview
         if (data.done) {
diff --git a/extension/src/util/messenger.ts b/extension/src/util/messenger.ts
index b1df161b..7fd71ddd 100644
--- a/extension/src/util/messenger.ts
+++ b/extension/src/util/messenger.ts
@@ -15,6 +15,8 @@ export abstract class Messenger {
   abstract onOpen(callback: () => void): void;
 
   abstract onClose(callback: () => void): void;
+  
+  abstract onError(callback: () => void): void;
 
   abstract sendAndReceive(messageType: string, data: any): Promise<any>;
 }
@@ -26,6 +28,7 @@ export class WebsocketMessenger extends Messenger {
   } = {};
   private onOpenListeners: (() => void)[] = [];
   private onCloseListeners: (() => void)[] = [];
+  private onErrorListeners: (() => void)[] = [];
   private serverUrl: string;
 
   _newWebsocket(): WebSocket {
@@ -43,6 +46,9 @@ export class WebsocketMessenger extends Messenger {
     for (const listener of this.onCloseListeners) {
       this.onClose(listener);
     }
+    for (const listener of this.onErrorListeners) {
+      this.onError(listener);
+    }
     for (const messageType in this.onMessageListeners) {
       for (const listener of this.onMessageListeners[messageType]) {
         this.onMessageType(messageType, listener);
@@ -151,4 +157,8 @@ export class WebsocketMessenger extends Messenger {
   onClose(callback: () => void): void {
     this.websocket.addEventListener("close", callback);
   }
+
+  onError(callback: () => void): void {
+    this.websocket.addEventListener("error", callback);
+  }
 }
-- 
cgit v1.2.3-70-g09d2


From 5c6d2f1be8474d26124506e0c2a640fa68efe52d Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Fri, 14 Jul 2023 02:30:45 -0700
Subject: fixed unique_id being asyncio.run property

---
 continuedev/src/continuedev/server/ide.py          | 22 +++++++++++-----------
 continuedev/src/continuedev/server/ide_protocol.py |  5 +----
 continuedev/src/continuedev/steps/help.py          |  8 +++++---
 extension/package-lock.json                        |  4 ++--
 extension/package.json                             |  2 +-
 extension/react-app/src/components/ComboBox.tsx    | 17 ++---------------
 extension/react-app/src/components/Onboarding.tsx  |  1 +
 7 files changed, 23 insertions(+), 36 deletions(-)

(limited to 'extension/react-app/src/components/ComboBox.tsx')

diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py
index 12a21f19..73cce201 100644
--- a/continuedev/src/continuedev/server/ide.py
+++ b/continuedev/src/continuedev/server/ide.py
@@ -123,10 +123,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
         self.websocket = websocket
         self.session_manager = session_manager
 
-    workspace_directory: str
+    workspace_directory: str = None
+    unique_id: str = None
 
     async def initialize(self) -> List[str]:
         await self._send_json("workspaceDirectory", {})
+        await self._send_json("uniqueId", {})
         other_msgs = []
         while True:
             msg_string = await self.websocket.receive_text()
@@ -137,9 +139,13 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
             data = message["data"]
             if message_type == "workspaceDirectory":
                 self.workspace_directory = data["workspaceDirectory"]
-                break
+            elif message_type == "uniqueId":
+                self.unique_id = data["uniqueId"]
             else:
                 other_msgs.append(msg_string)
+
+            if self.workspace_directory is not None and self.unique_id is not None:
+                break
         return other_msgs
 
     async def _send_json(self, message_type: str, data: Any):
@@ -183,10 +189,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
             self.onMainUserInput(data["input"])
         elif message_type == "deleteAtIndex":
             self.onDeleteAtIndex(data["index"])
-        elif message_type in ["highlightedCode", "openFiles", "visibleFiles", "readFile", "editFile", "getUserSecret", "runCommand", "uniqueId"]:
+        elif message_type in ["highlightedCode", "openFiles", "visibleFiles", "readFile", "editFile", "getUserSecret", "runCommand"]:
             self.sub_queue.post(message_type, data)
         elif message_type == "workspaceDirectory":
             self.workspace_directory = data["workspaceDirectory"]
+        elif message_type == "uniqueId":
+            self.unique_id = data["uniqueId"]
         else:
             raise ValueError("Unknown message type", message_type)
 
@@ -311,14 +319,6 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
         resp = await self._send_and_receive_json({}, VisibleFilesResponse, "visibleFiles")
         return resp.visibleFiles
 
-    async def get_unique_id(self) -> str:
-        resp = await self._send_and_receive_json({}, UniqueIdResponse, "uniqueId")
-        return resp.uniqueId
-
-    @cached_property_no_none
-    def unique_id(self) -> str:
-        return asyncio.run(self.get_unique_id())
-
     async def getHighlightedCode(self) -> List[RangeInFile]:
         resp = await self._send_and_receive_json({}, HighlightedCodeResponse, "highlightedCode")
         return resp.highlightedCode
diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py
index 2f78cf0e..d0fb0bf8 100644
--- a/continuedev/src/continuedev/server/ide_protocol.py
+++ b/continuedev/src/continuedev/server/ide_protocol.py
@@ -108,7 +108,4 @@ class AbstractIdeProtocolServer(ABC):
         """Show a diff"""
 
     workspace_directory: str
-
-    @abstractproperty
-    def unique_id(self) -> str:
-        """Get a unique ID for this IDE"""
+    unique_id: str
diff --git a/continuedev/src/continuedev/steps/help.py b/continuedev/src/continuedev/steps/help.py
index fdfb986f..2dc3647c 100644
--- a/continuedev/src/continuedev/steps/help.py
+++ b/continuedev/src/continuedev/steps/help.py
@@ -6,7 +6,7 @@ from ..libs.util.telemetry import capture_event
 help = dedent("""\
         Continue is an open-source coding autopilot. It is a VS Code extension that brings the power of ChatGPT to your IDE.
               
-        It gathers context for you and stores your interactions automatically, so that you can avoid copy/paste now and benefit from a customized LLM later.
+        It gathers context for you and stores your interactions automatically, so that you can avoid copy/paste now and benefit from a customized Large Language Model (LLM) later.
 
         Continue can be used to...
         1. Edit chunks of code with specific instructions (e.g. "/edit migrate this digital ocean terraform file into one that works for GCP")
@@ -25,6 +25,7 @@ help = dedent("""\
 
         If you have feedback, please use /feedback to let us know how you would like to use Continue. We are excited to hear from you!""")
 
+
 class HelpStep(Step):
 
     name: str = "Help"
@@ -41,7 +42,7 @@ class HelpStep(Step):
                     Information:
 
                     {help}""")
-        
+
         self.chat_context.append(ChatMessage(
             role="user",
             content=prompt,
@@ -54,4 +55,5 @@ class HelpStep(Step):
                 self.description += chunk["content"]
                 await sdk.update_ui()
 
-        capture_event(sdk.ide.unique_id, "help", {"question": question, "answer": self.description})
\ No newline at end of file
+        capture_event(sdk.ide.unique_id, "help", {
+                      "question": question, "answer": self.description})
diff --git a/extension/package-lock.json b/extension/package-lock.json
index 65fdab12..9d5c73e1 100644
--- a/extension/package-lock.json
+++ b/extension/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "continue",
-  "version": "0.0.162",
+  "version": "0.0.163",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "continue",
-      "version": "0.0.162",
+      "version": "0.0.163",
       "license": "Apache-2.0",
       "dependencies": {
         "@electron/rebuild": "^3.2.10",
diff --git a/extension/package.json b/extension/package.json
index ef39582b..2b0f6b94 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.162",
+  "version": "0.0.163",
   "publisher": "Continue",
   "engines": {
     "vscode": "^1.67.0"
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 7d6541c7..73db33ca 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -1,29 +1,16 @@
-import React, {
-  useCallback,
-  useEffect,
-  useImperativeHandle,
-  useState,
-} from "react";
+import React, { useEffect, useImperativeHandle, useState } from "react";
 import { useCombobox } from "downshift";
 import styled from "styled-components";
 import {
-  buttonColor,
   defaultBorderRadius,
   lightGray,
   secondaryDark,
   vscBackground,
 } from ".";
 import CodeBlock from "./CodeBlock";
-import { RangeInFile } from "../../../src/client";
 import PillButton from "./PillButton";
 import HeaderButtonWithText from "./HeaderButtonWithText";
-import {
-  Trash,
-  LockClosed,
-  LockOpen,
-  Plus,
-  DocumentPlus,
-} from "@styled-icons/heroicons-outline";
+import { DocumentPlus } from "@styled-icons/heroicons-outline";
 import { HighlightedRangeContext } from "../../../schema/FullState";
 
 // #region styled components
diff --git a/extension/react-app/src/components/Onboarding.tsx b/extension/react-app/src/components/Onboarding.tsx
index e2dd6f57..6bfb0ccd 100644
--- a/extension/react-app/src/components/Onboarding.tsx
+++ b/extension/react-app/src/components/Onboarding.tsx
@@ -22,6 +22,7 @@ const StyledSpan = styled.span`
   &:hover {
     background-color: #ffffff33;
   }
+  white-space: nowrap;
 `;
 
 const Onboarding = () => {
-- 
cgit v1.2.3-70-g09d2


From 39cd2ef27d6ed439b00a9edec4a487343ff1c2c9 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Fri, 14 Jul 2023 03:24:46 -0700
Subject: warn of large highlighted ranges, cmd+k->m

---
 continuedev/src/continuedev/core/policy.py        |   2 +-
 continuedev/src/continuedev/steps/help.py         |   2 +-
 extension/package-lock.json                       |   4 +-
 extension/package.json                            |   6 +-
 extension/react-app/src/components/ComboBox.tsx   |   5 +
 extension/react-app/src/components/Onboarding.tsx |   1 +
 extension/react-app/src/components/PillButton.tsx | 167 +++++++++++++---------
 extension/react-app/src/pages/gui.tsx             |  27 ++--
 extension/src/commands.ts                         |  17 ++-
 extension/src/lang-server/codeLens.ts             |   4 +-
 10 files changed, 141 insertions(+), 94 deletions(-)

(limited to 'extension/react-app/src/components/ComboBox.tsx')

diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py
index 59ea78b1..bc897357 100644
--- a/continuedev/src/continuedev/core/policy.py
+++ b/continuedev/src/continuedev/core/policy.py
@@ -59,7 +59,7 @@ class DemoPolicy(Policy):
             return (
                 MessageStep(name="Welcome to Continue", message=dedent("""\
                     - Highlight code and ask a question or give instructions
-                    - Use `cmd+k` (Mac) / `ctrl+k` (Windows) to open Continue
+                    - Use `cmd+m` (Mac) / `ctrl+m` (Windows) to open Continue
                     - Use `/help` to ask questions about how to use Continue""")) >>
                 WelcomeStep() >>
                 # SetupContinueWorkspaceStep() >>
diff --git a/continuedev/src/continuedev/steps/help.py b/continuedev/src/continuedev/steps/help.py
index 2dc3647c..ba1e6087 100644
--- a/continuedev/src/continuedev/steps/help.py
+++ b/continuedev/src/continuedev/steps/help.py
@@ -19,7 +19,7 @@ help = dedent("""\
 
         Continue passes all of the sections of code you highlight, the code above and below the to-be edited highlighted code section, and all previous steps above input box as context to the LLM.
 
-        You can use cmd+k (Mac) / ctrl+k (Windows) to open Continue. You can use cmd+shift+e / ctrl+shift+e to open file Explorer. You can add your own OpenAI API key to VS Code Settings with `cmd+,`
+        You can use cmd+m (Mac) / ctrl+m (Windows) to open Continue. You can use cmd+shift+e / ctrl+shift+e to open file Explorer. You can add your own OpenAI API key to VS Code Settings with `cmd+,`
 
         If Continue is stuck loading, try using `cmd+shift+p` to open the command palette, search "Reload Window", and then select it. This will reload VS Code and Continue and often fixes issues.
 
diff --git a/extension/package-lock.json b/extension/package-lock.json
index 9d5c73e1..a79dd6b4 100644
--- a/extension/package-lock.json
+++ b/extension/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "continue",
-  "version": "0.0.163",
+  "version": "0.0.164",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "continue",
-      "version": "0.0.163",
+      "version": "0.0.164",
       "license": "Apache-2.0",
       "dependencies": {
         "@electron/rebuild": "^3.2.10",
diff --git a/extension/package.json b/extension/package.json
index 2b0f6b94..de1f395d 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.163",
+  "version": "0.0.164",
   "publisher": "Continue",
   "engines": {
     "vscode": "^1.67.0"
@@ -111,8 +111,8 @@
     "keybindings": [
       {
         "command": "continue.focusContinueInput",
-        "mac": "cmd+k",
-        "key": "ctrl+k"
+        "mac": "cmd+m",
+        "key": "ctrl+m"
       },
       {
         "command": "continue.suggestionDown",
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 73db33ca..bd0d59b5 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -228,6 +228,11 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
         )} */}
         {highlightedCodeSections.map((section, idx) => (
           <PillButton
+            warning={
+              section.range.contents.length > 4000 && section.editing
+                ? "Editing such a large range may be slow"
+                : undefined
+            }
             editing={section.editing}
             pinned={section.pinned}
             index={idx}
diff --git a/extension/react-app/src/components/Onboarding.tsx b/extension/react-app/src/components/Onboarding.tsx
index 6bfb0ccd..231c1e93 100644
--- a/extension/react-app/src/components/Onboarding.tsx
+++ b/extension/react-app/src/components/Onboarding.tsx
@@ -109,6 +109,7 @@ const Onboarding = () => {
             paddingBottom: "50px",
             textAlign: "center",
             cursor: "pointer",
+            whiteSpace: "nowrap",
           }}
         >
           <StyledSpan
diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx
index 31d98c0f..d9d779d1 100644
--- a/extension/react-app/src/components/PillButton.tsx
+++ b/extension/react-app/src/components/PillButton.tsx
@@ -1,12 +1,11 @@
 import { useContext, useState } from "react";
 import styled from "styled-components";
+import { StyledTooltip, defaultBorderRadius, secondaryDark } from ".";
 import {
-  StyledTooltip,
-  defaultBorderRadius,
-  lightGray,
-  secondaryDark,
-} from ".";
-import { Trash, PaintBrush, MapPin } from "@styled-icons/heroicons-outline";
+  Trash,
+  PaintBrush,
+  ExclamationTriangle,
+} from "@styled-icons/heroicons-outline";
 import { GUIClientContext } from "../App";
 
 const Button = styled.button`
@@ -31,7 +30,6 @@ const GridDiv = styled.div`
   grid-template-columns: 1fr 1fr;
   align-items: center;
   border-radius: ${defaultBorderRadius};
-  overflow: hidden;
 
   background-color: ${secondaryDark};
 `;
@@ -48,6 +46,21 @@ const ButtonDiv = styled.div<{ backgroundColor: string }>`
   }
 `;
 
+const CircleDiv = styled.div`
+  position: absolute;
+  top: -10px;
+  right: -10px;
+  width: 20px;
+  height: 20px;
+  border-radius: 50%;
+  background-color: red;
+  color: white;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 2px;
+`;
+
 interface PillButtonProps {
   onHover?: (arg0: boolean) => void;
   onDelete?: () => void;
@@ -55,6 +68,7 @@ interface PillButtonProps {
   index: number;
   editing: boolean;
   pinned: boolean;
+  warning?: string;
 }
 
 const PillButton = (props: PillButtonProps) => {
@@ -63,75 +77,96 @@ const PillButton = (props: PillButtonProps) => {
 
   return (
     <>
-      <Button
-        style={{
-          position: "relative",
-          borderColor: props.editing
-            ? "#8800aa"
-            : props.pinned
-            ? "#ffff0099"
-            : "transparent",
-          borderWidth: "1px",
-          borderStyle: "solid",
-        }}
-        onMouseEnter={() => {
-          setIsHovered(true);
-          if (props.onHover) {
-            props.onHover(true);
-          }
-        }}
-        onMouseLeave={() => {
-          setIsHovered(false);
-          if (props.onHover) {
-            props.onHover(false);
-          }
-        }}
-      >
-        {isHovered && (
-          <GridDiv>
-            <ButtonDiv
-              data-tooltip-id={`edit-${props.index}`}
-              backgroundColor={"#8800aa55"}
-              onClick={() => {
-                client?.setEditingAtIndices([props.index]);
-              }}
-            >
-              <PaintBrush style={{ margin: "auto" }} width="1.6em"></PaintBrush>
-            </ButtonDiv>
+      <div style={{ position: "relative" }}>
+        <Button
+          style={{
+            position: "relative",
+            borderColor: props.warning
+              ? "red"
+              : props.editing
+              ? "#8800aa"
+              : props.pinned
+              ? "#ffff0099"
+              : "transparent",
+            borderWidth: "1px",
+            borderStyle: "solid",
+          }}
+          onMouseEnter={() => {
+            setIsHovered(true);
+            if (props.onHover) {
+              props.onHover(true);
+            }
+          }}
+          onMouseLeave={() => {
+            setIsHovered(false);
+            if (props.onHover) {
+              props.onHover(false);
+            }
+          }}
+        >
+          {isHovered && (
+            <GridDiv>
+              <ButtonDiv
+                data-tooltip-id={`edit-${props.index}`}
+                backgroundColor={"#8800aa55"}
+                onClick={() => {
+                  client?.setEditingAtIndices([props.index]);
+                }}
+              >
+                <PaintBrush
+                  style={{ margin: "auto" }}
+                  width="1.6em"
+                ></PaintBrush>
+              </ButtonDiv>
 
-            {/* <ButtonDiv
+              {/* <ButtonDiv
             data-tooltip-id={`pin-${props.index}`}
             backgroundColor={"#ffff0055"}
             onClick={() => {
               client?.setPinnedAtIndices([props.index]);
             }}
-          >
+            >
             <MapPin style={{ margin: "auto" }} width="1.6em"></MapPin>
           </ButtonDiv> */}
-            <StyledTooltip id={`pin-${props.index}`}>
-              Edit this range
+              <StyledTooltip id={`pin-${props.index}`}>
+                Edit this range
+              </StyledTooltip>
+              <ButtonDiv
+                data-tooltip-id={`delete-${props.index}`}
+                backgroundColor={"#cc000055"}
+                onClick={() => {
+                  if (props.onDelete) {
+                    props.onDelete();
+                  }
+                }}
+              >
+                <Trash style={{ margin: "auto" }} width="1.6em"></Trash>
+              </ButtonDiv>
+            </GridDiv>
+          )}
+          {props.title}
+        </Button>
+        <StyledTooltip id={`edit-${props.index}`}>
+          {props.editing
+            ? "Editing this range (with rest of file as context)"
+            : "Edit this range"}
+        </StyledTooltip>
+        <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip>
+        {props.warning && (
+          <>
+            <CircleDiv data-tooltip-id={`circle-div-${props.title}`}>
+              <ExclamationTriangle
+                style={{ margin: "auto" }}
+                width="1.0em"
+                strokeWidth={2}
+              />
+            </CircleDiv>
+            <StyledTooltip id={`circle-div-${props.title}`}>
+              {props.warning}
             </StyledTooltip>
-            <ButtonDiv
-              data-tooltip-id={`delete-${props.index}`}
-              backgroundColor={"#cc000055"}
-              onClick={() => {
-                if (props.onDelete) {
-                  props.onDelete();
-                }
-              }}
-            >
-              <Trash style={{ margin: "auto" }} width="1.6em"></Trash>
-            </ButtonDiv>
-          </GridDiv>
+          </>
         )}
-        {props.title}
-      </Button>
-      <StyledTooltip id={`edit-${props.index}`}>
-        {props.editing
-          ? "Editing this range (with rest of file as context)"
-          : "Edit this range"}
-      </StyledTooltip>
-      <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip>
+      </div>
     </>
   );
 };
diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx
index 4ff260fa..57cebac3 100644
--- a/extension/react-app/src/pages/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -95,11 +95,8 @@ function GUI(props: GUIProps) {
           name: "Welcome to Continue",
           hide: false,
           description: `- Highlight code and ask a question or give instructions
-- Use \`cmd+k\` (Mac) / \`ctrl+k\` (Windows) to open Continue
-- Use \`cmd+shift+e\` / \`ctrl+shift+e\` to open file Explorer
-- Add your own OpenAI API key to VS Code Settings with \`cmd+,\`
-- Use slash commands when you want fine-grained control
-- Past steps are included as part of the context by default`,
+          - Use \`cmd+m\` (Mac) / \`ctrl+m\` (Windows) to open Continue
+          - Use \`/help\` to ask questions about how to use Continue`,
           system_message: null,
           chat_context: [],
           manage_own_chat_context: false,
@@ -269,15 +266,17 @@ function GUI(props: GUIProps) {
   return (
     <>
       <Onboarding />
-      <TextDialog showDialog={showFeedbackDialog}
-      onEnter={(text) => {
-        client?.sendMainInput(`/feedback ${text}`);
-        setShowFeedbackDialog(false);
-      }}
-      onClose={() => {
-        setShowFeedbackDialog(false);
-      }}
-      message={feedbackDialogMessage} />
+      <TextDialog
+        showDialog={showFeedbackDialog}
+        onEnter={(text) => {
+          client?.sendMainInput(`/feedback ${text}`);
+          setShowFeedbackDialog(false);
+        }}
+        onClose={() => {
+          setShowFeedbackDialog(false);
+        }}
+        message={feedbackDialogMessage}
+      />
 
       <TopGUIDiv
         ref={topGuiDivRef}
diff --git a/extension/src/commands.ts b/extension/src/commands.ts
index 0b002549..0025340a 100644
--- a/extension/src/commands.ts
+++ b/extension/src/commands.ts
@@ -19,6 +19,8 @@ import { debugPanelWebview } from "./debugPanel";
 import { sendTelemetryEvent, TelemetryEvent } from "./telemetry";
 import { ideProtocolClient } from "./activation/activate";
 
+let focusedOnContinueInput = false;
+
 // Copy everything over from extension.ts
 const commandsMap: { [command: string]: (...args: any) => any } = {
   "continue.suggestionDown": suggestionDownCommand,
@@ -30,10 +32,15 @@ const commandsMap: { [command: string]: (...args: any) => any } = {
   "continue.acceptAllSuggestions": acceptAllSuggestionsCommand,
   "continue.rejectAllSuggestions": rejectAllSuggestionsCommand,
   "continue.focusContinueInput": async () => {
-    vscode.commands.executeCommand("continue.continueGUIView.focus");
-    debugPanelWebview?.postMessage({
-      type: "focusContinueInput",
-    });
+    if (focusedOnContinueInput) {
+      vscode.commands.executeCommand("workbench.action.focusActiveEditorGroup");
+    } else {
+      vscode.commands.executeCommand("continue.continueGUIView.focus");
+      debugPanelWebview?.postMessage({
+        type: "focusContinueInput",
+      });
+    }
+    focusedOnContinueInput = !focusedOnContinueInput;
   },
   "continue.quickTextEntry": async () => {
     const text = await vscode.window.showInputBox({
@@ -53,4 +60,4 @@ export function registerAllCommands(context: vscode.ExtensionContext) {
       vscode.commands.registerCommand(command, callback)
     );
   }
-}
\ No newline at end of file
+}
diff --git a/extension/src/lang-server/codeLens.ts b/extension/src/lang-server/codeLens.ts
index 5800a00e..1cfef5d5 100644
--- a/extension/src/lang-server/codeLens.ts
+++ b/extension/src/lang-server/codeLens.ts
@@ -60,12 +60,12 @@ class DiffViewerCodeLensProvider implements vscode.CodeLensProvider {
       }
       codeLenses.push(
         new vscode.CodeLens(range, {
-          title: "Accept ✅ (⌘⇧↩)",
+          title: "Accept All ✅ (⌘⇧↩)",
           command: "continue.acceptDiff",
           arguments: [document.uri.fsPath],
         }),
         new vscode.CodeLens(range, {
-          title: "Reject ❌ (⌘⇧⌫)",
+          title: "Reject All ❌ (⌘⇧⌫)",
           command: "continue.rejectDiff",
           arguments: [document.uri.fsPath],
         })
-- 
cgit v1.2.3-70-g09d2


From b19076ddb6d11acb5ffd54046d9e5cad549c00c1 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Fri, 14 Jul 2023 11:01:06 -0700
Subject: command m reliable toggle

---
 extension/package-lock.json                     |  4 ++--
 extension/package.json                          |  2 +-
 extension/react-app/src/components/ComboBox.tsx |  4 ++++
 extension/src/commands.ts                       |  5 ++++-
 extension/src/debugPanel.ts                     |  5 +++++
 extension/src/diffs.ts                          | 21 ++++++++++++---------
 6 files changed, 28 insertions(+), 13 deletions(-)

(limited to 'extension/react-app/src/components/ComboBox.tsx')

diff --git a/extension/package-lock.json b/extension/package-lock.json
index a79dd6b4..12aa27c9 100644
--- a/extension/package-lock.json
+++ b/extension/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "continue",
-  "version": "0.0.164",
+  "version": "0.0.165",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "continue",
-      "version": "0.0.164",
+      "version": "0.0.165",
       "license": "Apache-2.0",
       "dependencies": {
         "@electron/rebuild": "^3.2.10",
diff --git a/extension/package.json b/extension/package.json
index de1f395d..05bd4d84 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.164",
+  "version": "0.0.165",
   "publisher": "Continue",
   "engines": {
     "vscode": "^1.67.0"
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index bd0d59b5..5d9b5109 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -12,6 +12,7 @@ import PillButton from "./PillButton";
 import HeaderButtonWithText from "./HeaderButtonWithText";
 import { DocumentPlus } from "@styled-icons/heroicons-outline";
 import { HighlightedRangeContext } from "../../../schema/FullState";
+import { postVscMessage } from "../vscode";
 
 // #region styled components
 const mainInputFontSize = 13;
@@ -297,6 +298,9 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
 
               // setShowContextDropdown(target.value.endsWith("@"));
             },
+            onBlur: (e) => {
+              postVscMessage("blurContinueInput", {});
+            },
             onKeyDown: (event) => {
               if (event.key === "Enter" && event.shiftKey) {
                 // Prevent Downshift's default 'Enter' behavior.
diff --git a/extension/src/commands.ts b/extension/src/commands.ts
index 0025340a..888f01ed 100644
--- a/extension/src/commands.ts
+++ b/extension/src/commands.ts
@@ -16,11 +16,14 @@ import {
 import { acceptDiffCommand, rejectDiffCommand } from "./diffs";
 import * as bridge from "./bridge";
 import { debugPanelWebview } from "./debugPanel";
-import { sendTelemetryEvent, TelemetryEvent } from "./telemetry";
 import { ideProtocolClient } from "./activation/activate";
 
 let focusedOnContinueInput = false;
 
+export const setFocusedOnContinueInput = (value: boolean) => {
+  focusedOnContinueInput = value;
+};
+
 // Copy everything over from extension.ts
 const commandsMap: { [command: string]: (...args: any) => any } = {
   "continue.suggestionDown": suggestionDownCommand,
diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts
index 5e1689d1..dd24a8d8 100644
--- a/extension/src/debugPanel.ts
+++ b/extension/src/debugPanel.ts
@@ -6,6 +6,7 @@ import {
   openEditorAndRevealRange,
 } from "./util/vscode";
 import { RangeInFile } from "./client";
+import { setFocusedOnContinueInput } from "./commands";
 const WebSocket = require("ws");
 
 let websocketConnections: { [url: string]: WebsocketConnection | undefined } =
@@ -226,6 +227,10 @@ export function setupDebugPanel(
         openEditorAndRevealRange(data.path, undefined, vscode.ViewColumn.One);
         break;
       }
+      case "blurContinueInput": {
+        setFocusedOnContinueInput(false);
+        break;
+      }
       case "withProgress": {
         // This message allows withProgress to be used in the webview
         if (data.done) {
diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts
index 910c30f2..37943de4 100644
--- a/extension/src/diffs.ts
+++ b/extension/src/diffs.ts
@@ -104,6 +104,17 @@ class DiffManager {
     return editor;
   }
 
+  private _findFirstDifferentLine(contentA: string, contentB: string): number {
+    const linesA = contentA.split("\n");
+    const linesB = contentB.split("\n");
+    for (let i = 0; i < linesA.length && i < linesB.length; i++) {
+      if (linesA[i] !== linesB[i]) {
+        return i;
+      }
+    }
+    return 0;
+  }
+
   writeDiff(
     originalFilepath: string,
     newContent: string,
@@ -119,15 +130,7 @@ class DiffManager {
     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 line = this._findFirstDifferentLine(oldContent, newContent);
 
       const diffInfo: DiffInfo = {
         originalFilepath,
-- 
cgit v1.2.3-70-g09d2


From c5102b0997baa81ce544514d6b5b4d5a2eae804f Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Fri, 14 Jul 2023 13:45:10 -0700
Subject: insidious client_state vs application_state err

---
 continuedev/src/continuedev/core/autopilot.py        | 2 ++
 continuedev/src/continuedev/server/gui.py            | 2 +-
 continuedev/src/continuedev/server/ide.py            | 2 +-
 continuedev/src/continuedev/steps/core/core.py       | 9 ++++++++-
 extension/react-app/src/components/ComboBox.tsx      | 9 ++++++++-
 extension/react-app/src/components/StepContainer.tsx | 9 ++++++---
 extension/src/continueIdeClient.ts                   | 8 ++++++--
 extension/src/diffs.ts                               | 2 +-
 extension/src/util/messenger.ts                      | 2 +-
 9 files changed, 34 insertions(+), 11 deletions(-)

(limited to 'extension/react-app/src/components/ComboBox.tsx')

diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index e1c8a076..82439f49 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -37,6 +37,8 @@ def get_error_title(e: Exception) -> str:
         return "The request failed. Please check your internet connection and try again. If this issue persists, you can use our API key for free by going to VS Code settings and changing the value of continue.OPENAI_API_KEY to \"\""
     elif isinstance(e, openai_errors.InvalidRequestError):
         return 'Your API key does not have access to GPT-4. You can use ours for free by going to VS Code settings and changing the value of continue.OPENAI_API_KEY to ""'
+    elif e.__str__().startswith("Cannot connect to host"):
+        return "The request failed. Please check your internet connection and try again."
     return e.__str__() or e.__repr__()
 
 
diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py
index 238273b2..9a411fbe 100644
--- a/continuedev/src/continuedev/server/gui.py
+++ b/continuedev/src/continuedev/server/gui.py
@@ -53,7 +53,7 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
         self.session = session
 
     async def _send_json(self, message_type: str, data: Any):
-        if self.websocket.client_state == WebSocketState.DISCONNECTED:
+        if self.websocket.application_state == WebSocketState.DISCONNECTED:
             return
         await self.websocket.send_json({
             "messageType": message_type,
diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py
index 73cce201..7875c94d 100644
--- a/continuedev/src/continuedev/server/ide.py
+++ b/continuedev/src/continuedev/server/ide.py
@@ -149,7 +149,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
         return other_msgs
 
     async def _send_json(self, message_type: str, data: Any):
-        if self.websocket.client_state == WebSocketState.DISCONNECTED:
+        if self.websocket.application_state == WebSocketState.DISCONNECTED:
             return
         await self.websocket.send_json({
             "messageType": message_type,
diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py
index 787da316..75f8e460 100644
--- a/continuedev/src/continuedev/steps/core/core.py
+++ b/continuedev/src/continuedev/steps/core/core.py
@@ -9,7 +9,7 @@ from ...libs.llm.prompt_utils import MarkdownStyleEncoderDecoder
 from ...models.filesystem_edit import EditDiff, FileEdit, FileEditWithFullContents, FileSystemEdit
 from ...models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents
 from ...core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation
-from ...core.main import ChatMessage, Step, SequentialStep
+from ...core.main import ChatMessage, ContinueCustomException, Step, SequentialStep
 from ...libs.util.count_tokens import MAX_TOKENS_FOR_MODEL, DEFAULT_MAX_TOKENS
 from ...libs.util.dedent import dedent_and_get_common_whitespace
 import difflib
@@ -608,6 +608,13 @@ Please output the code to be inserted at the cursor in order to fulfill the user
             rif_dict[rif.filepath] = rif.contents
 
         for rif in rif_with_contents:
+            # If the file doesn't exist, ask them to save it first
+            if not os.path.exists(rif.filepath):
+                message = f"The file {rif.filepath} does not exist. Please save it first."
+                raise ContinueCustomException(
+                    title=message, message=message
+                )
+
             await sdk.ide.setFileOpen(rif.filepath)
             await sdk.ide.setSuggestionsLocked(rif.filepath, True)
             await self.stream_rif(rif, sdk)
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 5d9b5109..754c9445 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -169,6 +169,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
   useImperativeHandle(ref, () => downshiftProps, [downshiftProps]);
 
   const [metaKeyPressed, setMetaKeyPressed] = useState(false);
+  const [focused, setFocused] = useState(false);
   useEffect(() => {
     const handleKeyDown = (e: KeyboardEvent) => {
       if (e.key === "Meta") {
@@ -298,7 +299,11 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
 
               // setShowContextDropdown(target.value.endsWith("@"));
             },
+            onFocus: (e) => {
+              setFocused(true);
+            },
             onBlur: (e) => {
+              setFocused(false);
               postVscMessage("blurContinueInput", {});
             },
             onKeyDown: (event) => {
@@ -374,7 +379,9 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
       </div>
       {highlightedCodeSections.length === 0 &&
         (downshiftProps.inputValue?.startsWith("/edit") ||
-          (metaKeyPressed && downshiftProps.inputValue?.length > 0)) && (
+          (focused &&
+            metaKeyPressed &&
+            downshiftProps.inputValue?.length > 0)) && (
           <div className="text-trueGray-400 pr-4 text-xs text-right">
             Inserting at cursor
           </div>
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index 6fa4ba13..14e9b854 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -253,9 +253,12 @@ function StepContainer(props: StepContainerProps) {
           )}
 
           {props.historyNode.observation?.error ? (
-            <pre className="overflow-x-scroll">
-              {props.historyNode.observation.error as string}
-            </pre>
+            <details>
+              <summary>View Traceback</summary>
+              <pre className="overflow-x-scroll">
+                {props.historyNode.observation.error as string}
+              </pre>
+            </details>
           ) : (
             <StyledMarkdownPreview
               source={props.historyNode.step.description || ""}
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts
index 4c1fdf1e..6dd117d3 100644
--- a/extension/src/continueIdeClient.ts
+++ b/extension/src/continueIdeClient.ts
@@ -386,8 +386,12 @@ class IdeProtocolClient {
         contents = editor.document.getText();
       }
     });
-    if (!contents) {
-      contents = fs.readFileSync(filepath, "utf-8");
+    if (typeof contents === "undefined") {
+      if (fs.existsSync(filepath)) {
+        contents = fs.readFileSync(filepath, "utf-8");
+      } else {
+        contents = "";
+      }
     }
     return contents;
   }
diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts
index 37943de4..d04f9bdb 100644
--- a/extension/src/diffs.ts
+++ b/extension/src/diffs.ts
@@ -129,7 +129,7 @@ class DiffManager {
     // 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");
+      const oldContent = ideProtocolClient.readFile(originalFilepath);
       const line = this._findFirstDifferentLine(oldContent, newContent);
 
       const diffInfo: DiffInfo = {
diff --git a/extension/src/util/messenger.ts b/extension/src/util/messenger.ts
index 7fd71ddd..3044898e 100644
--- a/extension/src/util/messenger.ts
+++ b/extension/src/util/messenger.ts
@@ -15,7 +15,7 @@ export abstract class Messenger {
   abstract onOpen(callback: () => void): void;
 
   abstract onClose(callback: () => void): void;
-  
+
   abstract onError(callback: () => void): void;
 
   abstract sendAndReceive(messageType: string, data: any): Promise<any>;
-- 
cgit v1.2.3-70-g09d2


From 45ee33f7fd84c0bc49d35d9d1a7a3a8e9f6addd7 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Sat, 15 Jul 2023 15:06:32 -0700
Subject: use correct label for meta key

---
 extension/react-app/src/components/ComboBox.tsx    |  3 ++-
 .../react-app/src/components/StepContainer.tsx     |  7 +++++-
 extension/react-app/src/util/index.ts              | 17 +++++++++++--
 extension/src/diffs.ts                             |  3 ++-
 extension/src/lang-server/codeLens.ts              |  7 +++---
 extension/src/util/util.ts                         | 29 ++++++++++++++++++++++
 6 files changed, 58 insertions(+), 8 deletions(-)

(limited to 'extension/react-app/src/components/ComboBox.tsx')

diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 754c9445..f11e07af 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -13,6 +13,7 @@ import HeaderButtonWithText from "./HeaderButtonWithText";
 import { DocumentPlus } from "@styled-icons/heroicons-outline";
 import { HighlightedRangeContext } from "../../../schema/FullState";
 import { postVscMessage } from "../vscode";
+import { getMetaKeyLabel } from "../util";
 
 // #region styled components
 const mainInputFontSize = 13;
@@ -286,7 +287,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
       <div className="flex px-2" ref={divRef} hidden={!downshiftProps.isOpen}>
         <MainTextInput
           disabled={props.disabled}
-          placeholder="Ask a question, give instructions, or type '/' to see slash commands. ⌘⏎ to edit."
+          placeholder={`Ask a question, give instructions, or type '/' to see slash commands. ${getMetaKeyLabel()}⏎ to edit.`}
           {...getInputProps({
             onChange: (e) => {
               const target = e.target as HTMLTextAreaElement;
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index 7f23e333..93bdbc89 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -17,6 +17,7 @@ import { StopCircle } from "@styled-icons/heroicons-solid";
 import { HistoryNode } from "../../../schema/HistoryNode";
 import HeaderButtonWithText from "./HeaderButtonWithText";
 import MarkdownPreview from "@uiw/react-markdown-preview";
+import { getMetaKeyLabel, isMetaEquivalentKeyPressed } from "../util";
 
 interface StepContainerProps {
   historyNode: HistoryNode;
@@ -217,7 +218,11 @@ function StepContainer(props: StepContainerProps) {
                   e.stopPropagation();
                   props.onDelete();
                 }}
-                text={props.historyNode.active ? "Stop (⌘⌫)" : "Delete"}
+                text={
+                  props.historyNode.active
+                    ? `Stop (${getMetaKeyLabel()}⌫)`
+                    : "Delete"
+                }
               >
                 {props.historyNode.active ? (
                   <StopCircle size="1.6em" onClick={props.onDelete} />
diff --git a/extension/react-app/src/util/index.ts b/extension/react-app/src/util/index.ts
index ad711321..c4168e13 100644
--- a/extension/react-app/src/util/index.ts
+++ b/extension/react-app/src/util/index.ts
@@ -1,6 +1,6 @@
 type Platform = "mac" | "linux" | "windows" | "unknown";
 
-function getPlatform(): Platform {
+export function getPlatform(): Platform {
   const platform = window.navigator.platform.toUpperCase();
   if (platform.indexOf("MAC") >= 0) {
     return "mac";
@@ -13,7 +13,7 @@ function getPlatform(): Platform {
   }
 }
 
-function isMetaEquivalentKeyPressed(event: {
+export function isMetaEquivalentKeyPressed(event: {
   metaKey: boolean;
   ctrlKey: boolean;
 }): boolean {
@@ -28,3 +28,16 @@ function isMetaEquivalentKeyPressed(event: {
       return event.metaKey;
   }
 }
+
+export function getMetaKeyLabel(): string {
+  const platform = getPlatform();
+  switch (platform) {
+    case "mac":
+      return "⌘";
+    case "linux":
+    case "windows":
+      return "^";
+    default:
+      return "⌘";
+  }
+}
diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts
index d04f9bdb..9d0c9fe7 100644
--- a/extension/src/diffs.ts
+++ b/extension/src/diffs.ts
@@ -3,6 +3,7 @@ import * as path from "path";
 import * as fs from "fs";
 import * as vscode from "vscode";
 import { extensionContext, ideProtocolClient } from "./activation/activate";
+import { getMetaKeyLabel } from "./util/util";
 
 interface DiffInfo {
   originalFilepath: string;
@@ -86,7 +87,7 @@ class DiffManager {
     ) {
       vscode.window
         .showInformationMessage(
-          "Accept (⌘⇧↩) or reject (⌘⇧⌫) at the top of the file.",
+          `Accept (${getMetaKeyLabel()}⇧↩) or reject (${getMetaKeyLabel()}⇧⌫) at the top of the file.`,
           "Got it",
           "Don't show again"
         )
diff --git a/extension/src/lang-server/codeLens.ts b/extension/src/lang-server/codeLens.ts
index 1cfef5d5..ba80e557 100644
--- a/extension/src/lang-server/codeLens.ts
+++ b/extension/src/lang-server/codeLens.ts
@@ -3,6 +3,7 @@ import { editorToSuggestions, editorSuggestionsLocked } from "../suggestions";
 import * as path from "path";
 import * as os from "os";
 import { DIFF_DIRECTORY, diffManager } from "../diffs";
+import { getMetaKeyLabel } from "../util/util";
 class SuggestionsCodeLensProvider implements vscode.CodeLensProvider {
   public provideCodeLenses(
     document: vscode.TextDocument,
@@ -35,7 +36,7 @@ class SuggestionsCodeLensProvider implements vscode.CodeLensProvider {
       if (codeLenses.length === 2) {
         codeLenses.push(
           new vscode.CodeLens(range, {
-            title: "(⌘⇧↩/⌘⇧⌫ to accept/reject all)",
+            title: `(${getMetaKeyLabel()}⇧↩/${getMetaKeyLabel()}⇧⌫ to accept/reject all)`,
             command: "",
           })
         );
@@ -60,12 +61,12 @@ class DiffViewerCodeLensProvider implements vscode.CodeLensProvider {
       }
       codeLenses.push(
         new vscode.CodeLens(range, {
-          title: "Accept All ✅ (⌘⇧↩)",
+          title: `Accept All ✅ (${getMetaKeyLabel()}⇧↩)`,
           command: "continue.acceptDiff",
           arguments: [document.uri.fsPath],
         }),
         new vscode.CodeLens(range, {
-          title: "Reject All ❌ (⌘⇧⌫)",
+          title: `Reject All ❌ (${getMetaKeyLabel()}⇧⌫)`,
           command: "continue.rejectDiff",
           arguments: [document.uri.fsPath],
         })
diff --git a/extension/src/util/util.ts b/extension/src/util/util.ts
index d33593e1..dfc10c90 100644
--- a/extension/src/util/util.ts
+++ b/extension/src/util/util.ts
@@ -1,5 +1,6 @@
 import { RangeInFile, SerializedDebugContext } from "../client";
 import * as fs from "fs";
+const os = require("os");
 
 function charIsEscapedAtIndex(index: number, str: string): boolean {
   if (index === 0) return false;
@@ -113,3 +114,31 @@ export function debounced(delay: number, fn: Function) {
     }, delay);
   };
 }
+
+type Platform = "mac" | "linux" | "windows" | "unknown";
+
+function getPlatform(): Platform {
+  const platform = os.platform();
+  if (platform === "darwin") {
+    return "mac";
+  } else if (platform === "linux") {
+    return "linux";
+  } else if (platform === "win32") {
+    return "windows";
+  } else {
+    return "unknown";
+  }
+}
+
+export function getMetaKeyLabel() {
+  const platform = getPlatform();
+  switch (platform) {
+    case "mac":
+      return "⌘";
+    case "linux":
+    case "windows":
+      return "^";
+    default:
+      return "⌘";
+  }
+}
-- 
cgit v1.2.3-70-g09d2

