Development Environment and Tools

A development environment is a set of tools for developing software. At the heart of a development environment is text editing functionality, along with accompanying features such as syntax highlighting, type checking, code formatting, and autocomplete. Integrated development environments (IDEs) such as VS Code bring together all of this functionality into a single application. Terminal-based development workflows combine tools such as tmux (a terminal multiplexer), Vim (a text editor), Zsh (a shell), and language-specific command-line tools, such as Ruff (a Python linter and code formatter) and Mypy (a Python type checker).

IDEs and terminal-based workflows each have their strengths and weaknesses. For example, graphical IDEs can be easier to learn, and today’s IDEs generally have better out-of-the-box AI integrations like AI autocomplete; on the other hand, terminal-based workflows are lightweight, and they may be your only option in environments where you don’t have a GUI or can’t install software. We recommend you develop basic familiarity with both and develop mastery of at least one. If you don’t already have a preferred IDE, we recommend starting with VS Code.

In this lecture, we’ll cover:

Text editing and Vim

When programming, you spend most of your time navigating through code, reading snippets of code, and making edits to code, rather than writing long streams or reading files top-to-bottom. Vim is a text editor that is optimized for this distribution of tasks.

The philosophy of Vim. Vim has a beautiful idea as its foundation: its interface is itself a programming language, designed for navigating and editing text. Keystrokes (with mnemonic names) are commands, and these commands are composable. Vim avoids the use of the mouse, because it’s too slow; Vim even avoids use of the arrow keys because it requires too much movement. The result: an editor that feels like a brain-computer interface and matches the speed at which you think.

Vim support in other software. You don’t have to use Vim itself to benefit from the ideas at its core. Many programs that involve any kind of text editing support “Vim mode”, either as built-in functionality or as a plugin. For example, VS Code has the VSCodeVim plugin, Zsh has built-in support for Vim emulation, and even Claude Code has built-in support for Vim editor mode. Chances are that any tool you use that involves text editing supports Vim mode in one way or another.

Vim is a modal editor: it has different operating modes for different classes of tasks.

Keystrokes have different meanings in different operating modes. For example, the letter x in Insert mode will just insert a literal character “x”, but in Normal mode, it will delete the character under the cursor, and in Visual mode, it will delete the selection.

In its default configuration, Vim shows the current mode in the bottom left. The initial/default mode is Normal mode. You’ll generally spend most of your time between Normal mode and Insert mode.

You change modes by pressing <ESC> (the escape key) to switch from any mode back to Normal mode. From Normal mode, enter Insert mode with i, Replace mode with R, Visual mode with v, Visual Line mode with V, Visual Block mode with <C-v> (Ctrl-V, sometimes also written ^V), and Command-line mode with :.

You use the <ESC> key a lot when using Vim: consider remapping Caps Lock to Escape (macOS instructions) or create an alternative mapping for <ESC> with a simple key sequence.

Basics: inserting text

From Normal mode, press i to enter Insert mode. Now, Vim behaves like any other text editor, until you press <ESC> to return to Normal mode. This, along with the basics explained above, are all you need to start editing files using Vim (though not particularly efficiently, if you’re spending all your time editing from Insert mode).

Vim’s interface is a programming language

Vim’s interface is a programming language. Keystrokes (with mnemonic names) are commands, and these commands compose. This enables efficient movement and edits, especially once the commands become muscle memory, just like typing becomes super efficient once you’ve learned your keyboard layout.

Movement

You should spend most of your time in Normal mode, using movement commands to navigate the file. Movements in Vim are also called “nouns”, because they refer to chunks of text.

Selection

Visual modes:

Can use movement keys to make selection.

Edits

Everything that you used to do with the mouse, you now do with the keyboard using editing commands that compose with movement commands. Here’s where Vim’s interface starts to look like a programming language. Vim’s editing commands are also called “verbs”, because verbs act on nouns.

Counts

You can combine nouns and verbs with a count, which will perform a given action a number of times.

Modifiers

You can use modifiers to change the meaning of a noun. Some modifiers are i, which means “inner” or “inside”, and a, which means “around”.

Putting it all together

Here is a broken fizz buzz implementation:

def fizz_buzz(limit):
    for i in range(limit):
        if i % 3 == 0:
            print("fizz", end="")
        if i % 5 == 0:
            print("fizz", end="")
        if i % 3 and i % 5:
            print(i, end="")
        print()

def main():
    fizz_buzz(20)

We use the following sequence of commands to fix the issues, beginning in Normal mode:

Learning Vim

The best way to learn Vim is to learn the fundamentals (what we’ve covered so far) and then just enable Vim mode in all your software and start using it in practice. Avoid the temptation to use the mouse or the arrow keys; in some editors, you can unbind the arrow keys to force yourself to build good habits.

Additional resources

Code intelligence and language servers

IDEs generally offer language-specific support that requires semantic understanding of the code through IDE extensions that connect to language servers that implement Language Server Protocol. For example, the Python extension for VS Code relies on Pylance, and the Go extension for VS Code relies on the first-party gopls. By installing the extension and language server for the languages you work with, you can enable many language-specific features in your IDE, such as:

Configuring language servers

For some languages, all you need to do is install the extension and language server, and you’ll be all set. For others, to get the maximum benefit from the language server, you need to tell the IDE about your environment. For example, pointing VS Code to your Python environment will enable the language server to see your installed packages. Environments are covered in more depth in our lecture on packaging and shipping code.

Depending on the language, there might be some settings you can configure for your language server. For example, using the Python support in VS Code, you can disable static type checking for projects that don’t make use of Python’s optional type annotations.

AI-powered development

Since the introduction of GitHub Copilot using OpenAI’s Codex model in mid 2021, LLMs have become widely adopted in software engineering. There are three main form factors in use right now: autocomplete, inline chat, and coding agents.

Autocomplete

AI-powered autocomplete has the same form factor as traditional autocomplete in your IDE, suggesting completions at your cursor position as you type. Sometimes, it’s used as a passive feature that “just works”. Beyond that, AI autocomplete is generally prompted using code comments.

For example, let’s write a script to download the contents of these lecture notes and extract all the links. We can start with:

url = "https://raw.githubusercontent.com/missing-semester/missing-semester/refs/heads/master/_2026/development-environment.md"

Next, we can prompt the autocomplete model by writing a comment:

# download the url and get its contents as a string

The model will suggest something like:

import requests
response = requests.get(url)
content = response.text

We can also write some partial code, using a descriptive variable name to guide the model::

import re
links = re.findall(

And the model will complete the line:

links = re.findall(r'\[([^\]]+)\]\((https?://[^\)]+)\)', content)

Inline chat

Inline chat lets you select a line or block and then directly prompt the AI model to propose an edit. In this interaction mode, the model can make changes to existing code (which differs from autocomplete, which only completes code beyond the cursor).

Continuing the example from above, supposed we don’t like that the model is using the third-party requests library. We could select the relevant three lines of code, invoke inline chat, and say something like:

use built-in libraries instead

The model proposes:

import urllib.request
with urllib.request.urlopen(url) as response:
    content = response.read().decode('utf-8')

Coding agents

Coding agents are conversational AI models with access to tools such as reading/writing files, web search, and invoking shell commands. They live either in the IDE or in standalone command-line or GUI tools. Coding agents are highly autonomous and powerful tools, enabling a wide variety of use cases.

Continuing the example from above, we can try prompting a coding agent with the following task:

Turn this into a proper command-line program, with argparse for argument parsing. Add type annotations, and make sure the program passes type checking.

The agent will read the file to understand it, then make some edits, and finally invoke the type checker to make sure the type annotations are correct. If it makes a mistake such that it fails type checking, it will likely iterate, though this is a simple task so that is unlikely to happen. Because coding agents have access to tools that may be harmful, by default, agent harnesses prompt the user to confirm tool calls.

Coding agents support multi-turn interaction, so you can iterate on work over a back-and-forth conversation with the agent. You can even interrupt the agent if it’s going down the wrong track. One helpful mental model might be that of a manager of an intern: the intern will do the nitty gritty work, but will require guidance, and will occasionally do the wrong thing and need to be corrected.

How AI models and agents work

Fully explaining the inner workings of modern large language models (LLMs) and infrastructure such as agent harnesses is beyond the scope of this course. However, having a high-level understanding of some of the key ideas is helpful for effectively using this bleeding edge technology and understanding its limitations.

LLMs can be viewed as modeling the probability distribution of completion strings (outputs) given prompt strings (inputs). LLM inference (what happens when you, e.g., supply a query to a conversational chat app) samples from this probability distribution. LLMs have a fixed context window, the maximum length of the input and output strings.

AI tools such as conversational chat and coding agents build on top of this primitive. For multi-turn interactions, chat apps and agents use turn markers and supply the entire conversation history as the prompt string every time there is a new user prompt, invoking LLM inference once per user prompt. For tool-calling agents, the harness interprets certain LLM outputs as requests to invoke a tool, and the harness supplies the results of the tool call back to the model as part of the prompt string (so LLM inference runs again every time there is a tool call/response).

Use cases

Coding agents can be helpful for a wide variety of tasks. Some examples:

Advanced agents

Here, we give a brief overview of some more advanced usage patterns and capabilities of coding agents.

What to watch out for

AI tools can make mistakes. They are built on LLMs, which are just probabilistic next-token-prediction models. They are not “intelligent” in the same way as humans. Review AI output for correctness and security bugs. Sometimes verifying code can be harder than writing the code yourself; for critical code, consider writing it by hand. AI can go down rabbit holes and try to gaslight you; be aware of debugging spirals. Don’t use AI as a crutch, and be wary of overreliance or having a shallow understanding. There’s still a huge class of programming tasks that AI is still incapable of doing. Computational thinking is still valuable.

Some popular AI IDEs are VS Code with the GitHub Copilot extension and Cursor. GitHub Copilot is currently available for free for students, teachers, and maintainers of popular open source projects. These IDEs include coding agents; other popular coding agents include Anthropic’s Claude Code and OpenAI’s Codex. This is a rapidly evolving space. Many of the leading products have roughly equivalent functionality.

Regular expressions for search and replace

Regular expressions, commonly abbreviated as “regex”, is a language used to represent sets of strings. IDEs support regex for pattern-based search and search-and-replace. Regex patterns are also used commonly in other contexts such as command-line tools. For example, ag supports regex patterns for codebase-wide search (e.g., ag "import .* as .*" will find all renamed imports in Python), and go test supports a -run [regexp] option for selecting a subset of tests. Furthermore, programming languages have built-in support or third-party libraries for regular expression matching, so you can use regexes for functionality such as pattern matching, validation, and parsing.

To help build intuition, below are some examples of regex patterns. In this lecture, we use Python regex syntax. There are many flavors of regex, with slight variation between them, especially in the more sophisticated functionality. You can use an online regex tester like regex101 to develop and debug regular expressions.

Regex syntax

You can find a comprehensive guide to regex syntax in this documentation (or one of many other resources available online). Here are some of the basic building blocks:

Capture groups and references

If you use regex groups (...), you can refer to sub-parts of the match for extraction or search-and-replace purposes. For example, to extract just the month from a YYYY-MM-DD style date, you can use the following Python code:

>>> import re
>>> re.match(r"\d{4}-(\d{2})-\d{2}", "2026-01-14").group(1)
'01'

In your text editor, you can use reference capture groups in replace patterns. The syntax might vary between IDE. For example, in VS Code, you can use variables like $1, $2, etc., and in Vim, you can use \1, \2, etc., to reference groups.

Learning regex

We recommend learning the fundamentals (what we have covered in this lecture), and then looking at regex references as you need them, rather than memorizing the entirety of the language.

Conversational AI tools can be effective at helping you generating regex patterns. For example, try prompting your favorite LLM with the following query:

Write a Python-style regex pattern that matches the requested path from log lines from Nginx. Here is an example log line:

169.254.1.1 - - [09/Jan/2026:21:28:51 +0000] "GET /feed.xml HTTP/2.0" 200 2995 "-" "python-requests/2.32.3"

Extensions and other IDE functionality

IDEs are powerful tools, made even more powerful by extensions. We can’t cover all of these features in a single lecture, but here we provide some pointers to a couple popular extensions. We encourage you to explore this space on your own; there are many lists of popular IDE extensions available online, such as Vim Awesome for Vim plugins and VS Code extensions sorted by popularity.

Exercises

  1. Enable Vim mode in all the software you use that supports it, such as your editor and your shell, and use Vim mode for all your text editing for the next month. Whenever something seems inefficient, or when you think “there must be a better way”, try Googling it, there probably is a better way.
  2. Complete a challenge from VimGolf.
  3. Configure an IDE extension and language server for a project that you’re working on. Ensure that all the expected functionality, such as jump-to-definition for library dependencies, works as expected. If you don’t have code that you can use for this exercise, you can use some open-source project from GitHub (such as this one).
  4. Compare the experience of coding by hand, using AI autocomplete, inline chat, and agents by doing the same programming task four times. The best candidate is a small-sized feature from a project you’re already working on. If you’re looking for other ideas, you could consider completing “good first issue” style tasks in open-source projects on GitHub, or Advent of Code or LeetCode problems.
  5. Use an AI coding agent to navigate an unfamiliar codebase. This is best done in the context of wanting to debug or add a new feature to a project you actually care about. If you don’t have any that come to mind, try using an AI agent to understand how security-related features work in the opencode agent.
  6. Vibe code a small app from scratch. Do not write a single line of code by hand.
  7. Practice regex search-and-replace by replacing the - Markdown bullet markers with * bullet markers in the lecture notes for today. Note that just replacing all the “-“ characters in the file would be incorrect, as there are many uses of that character that are not bullet markers.
  8. Use an AI agent to accomplish the same goal as in the exercise above.
  9. Browse a list of IDE extensions and install one that seems useful to you.

Edit this page.

Licensed under CC BY-NC-SA.