Skip to content

Instantly share code, notes, and snippets.

@li-helen
Last active January 29, 2020 22:45
Show Gist options
  • Select an option

  • Save li-helen/cb0518d234e90cd1baf4f0c4ea24a559 to your computer and use it in GitHub Desktop.

Select an option

Save li-helen/cb0518d234e90cd1baf4f0c4ea24a559 to your computer and use it in GitHub Desktop.

Prompt

We are going to design our own code editor. For this exercise, we’ll assume that we are implementing this project using React and Redux. We are specifically interested in the design decisions we would make to allow users to manipulate the layout of various panels on their screen, and to keep track of user edits to their files.

Interviewees are encouraged to begin by drawing a diagram of what this code editor would look like.

Solution

These kinds of design questions are meant to foster a thoughtful conversation between you and your interviewer, and help your interviewer understanding how you approach and solve problems. By nature, they do not have a single correct answer. Therefore, the following offers just one of many possible solutions.

Please note that all the following is PSEUDOCODE.

Here are some suggested questions for interviewers to ask to help interviewees if they're stuck:

  • What React components would you create? How would you organize them in a tree structure?
  • How would you keep track of what the workspace looks like? Which files are open? Which panel/tab is active?
  • What kinds of actions should a user be able to perform? How would you keep track of user edits?

Components

To start, it may be helpful to refer to a code editor many of us are familiar with: VS Code. Broadly, we have a Menu at the top of the window, a navigation panel on the side, the editor itself, and a lower panel where we can see our program output, useful debugging information, etc:

<App>
  <Menu />
  <FileTree />
  <Editor />
</App>

Layout

We need some way of keeping track of the current layout of our code editor, as well as any changes a user may make to it. We can do this by creating a nested tree structure in our Redux store to keep track of the various ways the screen is "split":

{
  workspace: {
    root: {
      type: "V-SPLIT",
      left: {
        type: "FILETREE",
        width: 20%,
        height: 100%
      },
      right: {
        type: "H-SPLIT",
        width: 80%,
        height: 100%
        top: {
          type: "V-SPLIT",
          left: {
            type: "EDITOR",
            id: 1,
            path: "../src/index.js"
            width: 50%,
            height: 100%
          },
          right: {
            type: "EDITOR",
            id: 2,
            path: "../src/Editor.js",
            width: 50%,
            height: 100%
          }
        },
        bottom: {
          type: "PANEL",
          width: 100%,
          height: 30%,
        }
      }
    }
  }
}

Edits

We also need keep track of the edits a user makes to their files. We'll add an "activeEditor" key set to the value of the id of the Editor being used at any given time, and a "buffers" key to our Redux store that will hold representations of the text in our open files, and update this part of the store as a user makes changes to their files.

{
  workspace: {...},
  activeEditor: 1,
  buffers: {
    "../src/index.js": {
      commands: [...],
      contents: [...],
      commandIdx: 17
    },
    "../src/Editor.js": {
      commands: [...],
      contents: [...],
      commandIdx: 84
    }
  }
}

Most commands in code editors can be boiled down to some combination of insertion or deletion. We'll hold these commands in each of the commands arrays for the corresponding files. Each buffer also contains a contents array that stores each line in the file as a string. And finally, the commandIdx helps us perform the undo and redo functions.

Example:

Suppose we begin with an empty file.

{
  "../src/Editor.js": {
      commands: [],
      contents: [],
      commandIdx: -1
    }
}

We then type (insert) "A", "B", and "C".

{
  "../src/Editor.js": {
      commands: [INSERT A, INSERT B, INSERT C],
      contents: ["ABC"],
      commandIdx: 2
    }
}

Now, we want to hit undo. commandIdx is currently pointing to the last command, INSERT C. We want to perform the inverse of that, which would be to remove C. Then we decrement commandIdx so that it points to B.

{
  "../src/Editor.js": {
      commands: [INSERT A, INSERT B, INSERT C],
      contents: ["AB"],
      commandIdx: 1
    }
}

If we want to redo that action, we would increment commandIdx, and perform that action commands[commandIdx] (INSERT C). The contents would then return to being "ABC".

If we wanted to delete C, we would push the DELETE C command into the commands array, and increment commandIdx.

Selection

The last thing we'll cover in this exercise is we might implement the ability to select a block of text. To do this, we'll need to know where the selection starts and ends.

{
  workspace: {...},
  activeEditor: 1,
  buffers: {...},
  selectStart: {line: 1, char: 22},
  selectEnd: {line: 1, char: 57}
}

When a user is just clicking around, selectStart and selectEnd will be the same. But when a user selects a block of text, selectStart will indicate the line number and character offset at which the selection begins, and selectEnd will indicate the line number and character offset at which the selection ends.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment