Sublime Text + clang C++ setup with portable installer
Build systems, Makefile, and a setup script that reproduces this environment on another machine. - CP.sublime-build: incremental make-based build, clang++, flags shared with clangd via compile_flags.txt - compete_CPP.sublime-build: competitive-programming workflow, runs against inputf.in and diffs the result against expectedf.out - Makefile: builds every .cpp in the tree into a binary beside its source - setup.sh: export/install/check, path-neutral payload, idempotent - README: setup notes, the Sublime $-expansion trap, and the -MMD -MP header-dependency limitation to fix later The .sublime-workspace is deliberately excluded: it holds Sublime's global recent-file history and absolute paths. Only the pane layout is portable, extracted to sublime/layout.json. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KCtkkFDkSGi868JWuinwcF
This commit is contained in:
commit
40c23f9c6b
18 changed files with 756 additions and 0 deletions
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Compiled binaries have no file extension, so a normal pattern cannot catch
|
||||||
|
# them. Ignore everything, then whitelist the file types worth committing.
|
||||||
|
*
|
||||||
|
!*/
|
||||||
|
|
||||||
|
# sources and headers
|
||||||
|
!*.cpp
|
||||||
|
!*.h
|
||||||
|
!*.hpp
|
||||||
|
|
||||||
|
# config that should travel with the repo
|
||||||
|
!Makefile
|
||||||
|
!setup.sh
|
||||||
|
!.gitignore
|
||||||
|
!.clangd
|
||||||
|
!*.md
|
||||||
|
!*.txt
|
||||||
|
!*.json
|
||||||
|
!*.sublime-project
|
||||||
|
!*.sublime-build
|
||||||
|
!*.sublime-settings
|
||||||
|
!*.sublime-keymap
|
||||||
|
|
||||||
|
# contest input / expected answers are worth keeping
|
||||||
|
!*.in
|
||||||
|
!expectedf.out
|
||||||
|
|
||||||
|
# ...but never these
|
||||||
|
*.sublime-workspace
|
||||||
|
*.d
|
||||||
|
*.bak-*
|
||||||
|
outputf.out
|
||||||
24
Makefile
Normal file
24
Makefile
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Builds every .cpp in this tree into a binary next to its source.
|
||||||
|
# Incremental: a file is recompiled only when its .cpp is newer than its binary.
|
||||||
|
# Flags come from the compile_flags.txt nearest the source (the file clangd
|
||||||
|
# reads), falling back to the one at the repo root.
|
||||||
|
CXX := clang++
|
||||||
|
ROOT := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||||
|
SRCS := $(shell find $(ROOT) -name '*.cpp')
|
||||||
|
BINS := $(basename $(SRCS))
|
||||||
|
|
||||||
|
# The `@:` recipe is a deliberate no-op. Without it, make prints
|
||||||
|
# "Nothing to be done for 'all'" whenever everything is already up to date.
|
||||||
|
all: $(BINS)
|
||||||
|
@:
|
||||||
|
|
||||||
|
%: %.cpp
|
||||||
|
@flags=$$(cat $(dir $<)compile_flags.txt 2>/dev/null || cat $(ROOT)compile_flags.txt 2>/dev/null); \
|
||||||
|
echo "compiling $(notdir $<)"; \
|
||||||
|
$(CXX) $$flags $< -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@rm -f $(BINS)
|
||||||
|
@echo "cleaned"
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
19
Mocpp.sublime-project
Normal file
19
Mocpp.sublime-project
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"folders":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"path": ".",
|
||||||
|
"folder_exclude_patterns": [".cache", "build"],
|
||||||
|
// binaries have no extension, so exclude them by name as you add them
|
||||||
|
"file_exclude_patterns": ["*.o", "*.d", "outputf.out"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings":
|
||||||
|
{
|
||||||
|
// clangd already indexes C++; Sublime's own indexer just duplicates it
|
||||||
|
"index_files": false,
|
||||||
|
"tab_size": 4,
|
||||||
|
"translate_tabs_to_spaces": false,
|
||||||
|
"rulers": [80]
|
||||||
|
}
|
||||||
|
}
|
||||||
290
README.md
Normal file
290
README.md
Normal file
|
|
@ -0,0 +1,290 @@
|
||||||
|
# Mocpp — C++ learning exercises
|
||||||
|
|
||||||
|
Chapter exercises, one binary per `.cpp`. Built with **clang++**, edited in
|
||||||
|
Sublime Text with **clangd** for diagnostics and completion.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
Mocpp/
|
||||||
|
├── Makefile # builds every .cpp in the tree
|
||||||
|
├── compile_flags.txt # fallback flags (used if a chapter dir has none)
|
||||||
|
├── README.md
|
||||||
|
├── source.cpp
|
||||||
|
└── ch1/
|
||||||
|
├── compile_flags.txt # flags for this chapter — clangd reads this too
|
||||||
|
├── 1-1.cpp → 1-1 # each .cpp builds to a binary beside it
|
||||||
|
└── 1-4.cpp → 1-4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
From Sublime:
|
||||||
|
|
||||||
|
| Key | Action |
|
||||||
|
|-----|--------|
|
||||||
|
| `Ctrl+B` | build everything (incremental — silent if nothing changed) |
|
||||||
|
| `Ctrl+Shift+B` → *Build all + Run this file* | build, then run the focused file |
|
||||||
|
| `Ctrl+Shift+B` → *Build all + Run this file (with stdin)* | same, for `std::cin` exercises |
|
||||||
|
| `Ctrl+Shift+B` → *Run this file only* | compile just the focused file, skip make |
|
||||||
|
| `Ctrl+Shift+B` → *Clean* | delete all binaries, force a full rebuild |
|
||||||
|
|
||||||
|
From a terminal — identical result, `make` is the single source of truth:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make # build what changed
|
||||||
|
make clean # remove all binaries
|
||||||
|
```
|
||||||
|
|
||||||
|
The Sublime build file lives at
|
||||||
|
`~/.config/sublime-text/Packages/User/CP.sublime-build`.
|
||||||
|
|
||||||
|
## Flags: one file, two consumers
|
||||||
|
|
||||||
|
`compile_flags.txt` is read by **both** clangd (editor squiggles) and the
|
||||||
|
Makefile (the actual compile). That is deliberate — it is why the warnings you
|
||||||
|
see while typing are the warnings you get when building. Edit flags there and
|
||||||
|
nowhere else.
|
||||||
|
|
||||||
|
Current flags: `-std=c++20 -Wall -Wextra -Wconversion`
|
||||||
|
|
||||||
|
### Starting a new chapter
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir ch2
|
||||||
|
cp ch1/compile_flags.txt ch2/
|
||||||
|
```
|
||||||
|
|
||||||
|
Without its own copy a directory falls back to the root `compile_flags.txt`, so
|
||||||
|
it still gets warnings — but keeping a copy per chapter means you can vary the
|
||||||
|
standard per chapter later.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TODO: add `-MMD -MP` when you start writing your own headers
|
||||||
|
|
||||||
|
**This is the one known limitation of the current Makefile.** Read this before
|
||||||
|
you create your first `.h` file.
|
||||||
|
|
||||||
|
The build tracks only `.cpp` timestamps. It has no idea headers exist. So if you
|
||||||
|
edit a header, **make does nothing and leaves you running a stale binary** — with
|
||||||
|
no error and no warning. Verified behaviour, not theory:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./ch1/1-1
|
||||||
|
hello v1
|
||||||
|
$ sed -i 's/hello v1/hello v2/' ch1/greet.h # edit ONLY the header
|
||||||
|
$ make
|
||||||
|
# ← silence. nothing rebuilt.
|
||||||
|
$ ./ch1/1-1
|
||||||
|
hello v1 # ← WRONG. still the old code.
|
||||||
|
```
|
||||||
|
|
||||||
|
This will cost you an hour of debugging code that is already correct.
|
||||||
|
|
||||||
|
### The fix (tested, works)
|
||||||
|
|
||||||
|
`-MMD` makes clang emit a `.d` file listing every header the source included.
|
||||||
|
`-MP` adds a dummy target for each header, so a *deleted* header does not break
|
||||||
|
the build. `-include $(DEPS)` feeds those back to make, which then knows the
|
||||||
|
binary depends on the headers too.
|
||||||
|
|
||||||
|
Three changes to the `Makefile`:
|
||||||
|
|
||||||
|
```make
|
||||||
|
BINS := $(basename $(SRCS))
|
||||||
|
DEPS := $(addsuffix .d,$(BINS)) # 1. add this line
|
||||||
|
|
||||||
|
%: %.cpp
|
||||||
|
@flags=$$(cat $(dir $<)compile_flags.txt 2>/dev/null || cat $(ROOT)compile_flags.txt 2>/dev/null); \
|
||||||
|
echo "compiling $(notdir $<)"; \
|
||||||
|
$(CXX) $$flags -MMD -MP $< -o $@ # 2. add -MMD -MP here
|
||||||
|
|
||||||
|
-include $(DEPS) # 3. add this line
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@rm -f $(BINS) $(DEPS) # 4. also remove .d files
|
||||||
|
@echo "cleaned"
|
||||||
|
```
|
||||||
|
|
||||||
|
After that, editing a header rebuilds what depends on it:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sed -i 's/hello v1/hello v2/' ch1/greet.h
|
||||||
|
$ make
|
||||||
|
compiling 1-1.cpp
|
||||||
|
$ ./ch1/1-1
|
||||||
|
hello v2
|
||||||
|
```
|
||||||
|
|
||||||
|
The generated `ch1/1-1.d` is just a make rule:
|
||||||
|
|
||||||
|
```
|
||||||
|
ch1/1-1: ch1/1-1.cpp ch1/greet.h
|
||||||
|
ch1/greet.h:
|
||||||
|
```
|
||||||
|
|
||||||
|
The leading `-` in `-include` matters: it stops make erroring on the first build,
|
||||||
|
when no `.d` files exist yet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `compete_CPP` — the contest workflow
|
||||||
|
|
||||||
|
A second build system, for problems that read input from stdin (Codeforces,
|
||||||
|
LeetCode, or any book exercise using `std::cin`). Select it with
|
||||||
|
**Tools → Build System → compete_CPP**.
|
||||||
|
|
||||||
|
It compiles the focused `.cpp`, feeds it `inputf.in` on stdin, writes stdout to
|
||||||
|
`outputf.out`, and prints both — so you never retype a sample input.
|
||||||
|
|
||||||
|
| Variant | Does |
|
||||||
|
|---|---|
|
||||||
|
| `Ctrl+B` | compile, run with `inputf.in`, show input and output |
|
||||||
|
| *Run + check against expectedf.out* | same, then `diff` the answer — prints PASS or FAIL |
|
||||||
|
| *Run (type input live)* | no redirect; type input into the build panel |
|
||||||
|
| *Setup: create inputf.in / expectedf.out here* | creates the three files in the current folder |
|
||||||
|
| *DIAGNOSTIC* | prints Sublime's variables |
|
||||||
|
|
||||||
|
The three files live **next to the source**, not at the repo root. That was the
|
||||||
|
bug in the original tutorial build: it looked for `inputf.in` in the source's
|
||||||
|
folder while the files sat one level up, so they were never read.
|
||||||
|
|
||||||
|
Typical loop: open a problem, run *Setup*, paste the sample input into
|
||||||
|
`inputf.in` and the expected answer into `expectedf.out`, then hit
|
||||||
|
*Run + check* until it says PASS.
|
||||||
|
|
||||||
|
```
|
||||||
|
--- outputf.out ---
|
||||||
|
6
|
||||||
|
--- diff (expected vs actual) ---
|
||||||
|
PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
A wrong answer shows the diff and exits non-zero:
|
||||||
|
|
||||||
|
```
|
||||||
|
-99
|
||||||
|
+6
|
||||||
|
FAIL
|
||||||
|
```
|
||||||
|
|
||||||
|
A compile error stops before running, so you never diff a stale binary.
|
||||||
|
|
||||||
|
Note this build compiles **only the focused file** — it does not use the
|
||||||
|
Makefile. That is intentional: contest problems are self-contained single files.
|
||||||
|
Use the `CP` build system for the chapter exercises.
|
||||||
|
|
||||||
|
## Gotcha: `$` in the Sublime build file
|
||||||
|
|
||||||
|
Sublime expands **both** `${name}` and bare `$name` in `cmd`, `shell_cmd`, and
|
||||||
|
`working_dir`. Any name it does not recognise becomes an empty string *before*
|
||||||
|
bash runs.
|
||||||
|
|
||||||
|
So a shell variable like `$SRC` is silently deleted, and a guard such as
|
||||||
|
`if [ -z "$SRC" ]` becomes `if [ -z "" ]` — always true. The symptom is a build
|
||||||
|
that insists there is no active file no matter what you do.
|
||||||
|
|
||||||
|
Rules for editing `CP.sublime-build`:
|
||||||
|
|
||||||
|
- Prefer Sublime's own `${file}`, `${file_path}`, `${file_base_name}` plus
|
||||||
|
`$(...)`, and use no bash variables at all.
|
||||||
|
- Any `$` meant for bash must be escaped `\$` (written `\\$` in the JSON).
|
||||||
|
- Test a variant with the *DIAGNOSTIC* build, which prints what Sublime actually
|
||||||
|
substituted.
|
||||||
|
|
||||||
|
Reference: https://www.sublimetext.com/docs/build_systems.html
|
||||||
|
|
||||||
|
## Saving the window layout
|
||||||
|
|
||||||
|
Three mechanisms, easy to confuse:
|
||||||
|
|
||||||
|
| What | Where | Saves |
|
||||||
|
|---|---|---|
|
||||||
|
| **Project** | `Mocpp.sublime-project` | folders, settings, excludes — commit this |
|
||||||
|
| **Workspace** | `Mocpp.sublime-workspace` (auto) | open tabs, pane layout, cursors — do NOT commit |
|
||||||
|
| **Hot exit** | automatic | last session, even with no project |
|
||||||
|
|
||||||
|
Open it once with **Project → Open Project → `Mocpp.sublime-project`**. From then
|
||||||
|
on Sublime writes a `.sublime-workspace` beside it holding your exact pane
|
||||||
|
arrangement and open files, and restores them next launch.
|
||||||
|
|
||||||
|
Keybindings for the contest layout (code left, input top-right, output
|
||||||
|
bottom-right), in `Packages/User/Default (Linux).sublime-keymap`:
|
||||||
|
|
||||||
|
| Key | Layout |
|
||||||
|
|---|---|
|
||||||
|
| `Ctrl+Alt+Shift+C` | three-pane contest layout |
|
||||||
|
| `Ctrl+Alt+Shift+1` | back to a single pane |
|
||||||
|
|
||||||
|
Sublime's built-in `Alt+Shift+1..5` (columns) and `Alt+Shift+8/9` (grids) still
|
||||||
|
work; the bindings above just add a split the menu does not offer.
|
||||||
|
|
||||||
|
If you commit this directory to git:
|
||||||
|
|
||||||
|
```
|
||||||
|
*.sublime-workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installing this setup on another machine
|
||||||
|
|
||||||
|
`setup.sh` moves the whole environment. Clone the repo, run install:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <this repo> ~/git/Mocpp
|
||||||
|
cd ~/git/Mocpp
|
||||||
|
./setup.sh install
|
||||||
|
```
|
||||||
|
|
||||||
|
| Command | Does |
|
||||||
|
|---|---|
|
||||||
|
| `./setup.sh check` | verify the toolchain, change nothing |
|
||||||
|
| `./setup.sh install` | install `sublime/` onto this machine, then build |
|
||||||
|
| `./setup.sh export` | re-capture this machine's Sublime config into `sublime/` |
|
||||||
|
|
||||||
|
**Run `export` after changing a build system**, so the payload in `sublime/`
|
||||||
|
stays current, then commit. The script never embeds copies of the config — it
|
||||||
|
installs from `sublime/`, so there is nothing to drift out of sync.
|
||||||
|
|
||||||
|
What install does:
|
||||||
|
|
||||||
|
- checks for `clang++`, `clangd`, `make`, `git`, and prints the right
|
||||||
|
package-manager command for your distro if any are missing
|
||||||
|
- finds Sublime's config dir (differs on Linux and macOS) and names the keymap
|
||||||
|
per platform
|
||||||
|
- substitutes `__PROJECT_ROOT__` in the build files with wherever you cloned to,
|
||||||
|
so the repo need not live at the same path
|
||||||
|
- merges the Package Control list rather than overwriting yours, so Sublime
|
||||||
|
offers to install the missing packages on next start
|
||||||
|
- runs `make` as a smoke test and fails loudly if the toolchain is broken
|
||||||
|
|
||||||
|
Re-running is a no-op: files are compared after substitution, so nothing is
|
||||||
|
rewritten and no `.bak` files accumulate. Anything it would genuinely overwrite
|
||||||
|
is backed up as `<name>.bak-<timestamp>` first.
|
||||||
|
|
||||||
|
### Why the workspace is not copied
|
||||||
|
|
||||||
|
`compete.sublime-workspace` is deliberately **not** part of the payload and is
|
||||||
|
gitignored. Sublime stores your global recent-file history in it — on this
|
||||||
|
machine 17 of its 21 entries were unrelated personal paths, including documents
|
||||||
|
and TLS certificates. It also hardcodes absolute paths.
|
||||||
|
|
||||||
|
Only the pane layout is portable, so `export` extracts just that into
|
||||||
|
`sublime/layout.json`, and `install` builds a clean workspace from it. The same
|
||||||
|
layout is also bound to `Ctrl+Alt+Shift+C`, so it survives even without a
|
||||||
|
workspace file.
|
||||||
|
|
||||||
|
## When this setup runs out
|
||||||
|
|
||||||
|
It is built for single-file exercises. Move to CMake generating
|
||||||
|
`compile_commands.json` once you have multiple source files linking into one
|
||||||
|
program, external libraries, or more than one build configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -S . -B build -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
||||||
|
ln -sf build/compile_commands.json .
|
||||||
|
```
|
||||||
|
|
||||||
|
Then delete `compile_flags.txt` — if both exist in a directory,
|
||||||
|
`compile_commands.json` wins and the other becomes a silently ignored second
|
||||||
|
source of truth. Point the Sublime build at `ninja -C build`.
|
||||||
7
ch1/.clangd
Normal file
7
ch1/.clangd
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
Diagnostics:
|
||||||
|
ClangTidy:
|
||||||
|
Add:
|
||||||
|
- performance-*
|
||||||
|
- cppcoreguidelines-*
|
||||||
|
Remove:
|
||||||
|
- cppcoreguidelines-avoid-magic-numbers
|
||||||
9
ch1/1-1.cpp
Normal file
9
ch1/1-1.cpp
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::vector<int> values{1,2,3,4,5};
|
||||||
|
for (int v : values) {
|
||||||
|
std::cout << v << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
7
ch1/1-4.cpp
Normal file
7
ch1/1-4.cpp
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
int main() {
|
||||||
|
std::vector<int> v{1,2,3,4};
|
||||||
|
auto count = std::count_if(v.begin(), v.end(),
|
||||||
|
[](int x){ return x % 2 == 0; });
|
||||||
|
}
|
||||||
4
ch1/compile_flags.txt
Normal file
4
ch1/compile_flags.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
-std=c++20
|
||||||
|
-Wall
|
||||||
|
-Wextra
|
||||||
|
-Wconversion
|
||||||
4
compile_flags.txt
Normal file
4
compile_flags.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
-std=c++20
|
||||||
|
-Wall
|
||||||
|
-Wextra
|
||||||
|
-Wconversion
|
||||||
0
inputf.in
Normal file
0
inputf.in
Normal file
206
setup.sh
Executable file
206
setup.sh
Executable file
|
|
@ -0,0 +1,206 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Portable installer for this C++ / Sublime Text setup.
|
||||||
|
#
|
||||||
|
# ./setup.sh export copy this machine's Sublime config into ./sublime/
|
||||||
|
# ./setup.sh install install ./sublime/ onto this machine
|
||||||
|
# ./setup.sh check verify the toolchain and report, change nothing
|
||||||
|
#
|
||||||
|
# Design notes:
|
||||||
|
# - Configs live in ./sublime/ so they are version-controlled and travel with
|
||||||
|
# the repo. The script never embeds copies that could drift.
|
||||||
|
# - The .sublime-workspace is NEVER copied. It holds absolute paths and
|
||||||
|
# Sublime's global recent-file history, which is machine-specific and
|
||||||
|
# frequently personal. Only the pane layout is portable, so install
|
||||||
|
# regenerates a clean workspace from sublime/layout.json.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PAYLOAD="$REPO/sublime"
|
||||||
|
STAMP="$(date +%Y%m%d-%H%M%S)"
|
||||||
|
|
||||||
|
c_ok() { printf ' \033[32mok\033[0m %s\n' "$*"; }
|
||||||
|
c_warn() { printf ' \033[33mwarn\033[0m %s\n' "$*"; }
|
||||||
|
c_err() { printf ' \033[31mFAIL\033[0m %s\n' "$*"; }
|
||||||
|
head_() { printf '\n\033[1m%s\033[0m\n' "$*"; }
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- locate ST --
|
||||||
|
sublime_dir() {
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Darwin) echo "$HOME/Library/Application Support/Sublime Text" ;;
|
||||||
|
*) echo "$HOME/.config/sublime-text" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
# Keymaps are named per platform.
|
||||||
|
keymap_name() {
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Darwin) echo "Default (OSX).sublime-keymap" ;;
|
||||||
|
*) echo "Default (Linux).sublime-keymap" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- export -----
|
||||||
|
do_export() {
|
||||||
|
local user_dir; user_dir="$(sublime_dir)/Packages/User"
|
||||||
|
head_ "Exporting Sublime config from $user_dir"
|
||||||
|
[ -d "$user_dir" ] || { c_err "not found: $user_dir"; exit 1; }
|
||||||
|
mkdir -p "$PAYLOAD"
|
||||||
|
|
||||||
|
local f
|
||||||
|
for f in CP.sublime-build compete_CPP.sublime-build \
|
||||||
|
LSP-clangd.sublime-settings; do
|
||||||
|
if [ -f "$user_dir/$f" ]; then
|
||||||
|
# Replace this machine's project path with a placeholder so the
|
||||||
|
# payload is portable and contains no absolute paths.
|
||||||
|
sed "s|$REPO|__PROJECT_ROOT__|g" "$user_dir/$f" > "$PAYLOAD/$f"
|
||||||
|
c_ok "$f"
|
||||||
|
else c_warn "missing, skipped: $f"; fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Keymap is stored platform-neutral; install renames it per OS.
|
||||||
|
if [ -f "$user_dir/$(keymap_name)" ]; then
|
||||||
|
cp "$user_dir/$(keymap_name)" "$PAYLOAD/keymap.json"; c_ok "keymap.json"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Package list, for Package Control to auto-install on the new machine.
|
||||||
|
if [ -f "$user_dir/Package Control.sublime-settings" ]; then
|
||||||
|
python3 - "$user_dir/Package Control.sublime-settings" "$PAYLOAD/packages.json" <<'PY'
|
||||||
|
import json,re,sys
|
||||||
|
s=open(sys.argv[1]).read(); s=re.sub(r'^\s*//.*$','',s,flags=re.M)
|
||||||
|
s=re.sub(r',(\s*[\]}])',r'\1',s) # tolerate trailing commas
|
||||||
|
pkgs=json.load(open(sys.argv[1]).name and __import__('io').StringIO(s)).get('installed_packages',[])
|
||||||
|
json.dump(sorted(pkgs), open(sys.argv[2],'w'), indent=2)
|
||||||
|
print(" ok packages.json (%d packages)" % len(pkgs))
|
||||||
|
PY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Layout only -- extracted from the workspace, stripped of everything else.
|
||||||
|
local ws; ws="$(ls "$REPO"/*.sublime-workspace 2>/dev/null | head -1 || true)"
|
||||||
|
if [ -n "$ws" ]; then
|
||||||
|
python3 - "$ws" "$PAYLOAD/layout.json" <<'PY'
|
||||||
|
import json,sys
|
||||||
|
d=json.load(open(sys.argv[1]))
|
||||||
|
json.dump(d.get("layout",{}), open(sys.argv[2],"w"), indent=2)
|
||||||
|
print(" ok layout.json (pane layout only; history discarded)")
|
||||||
|
PY
|
||||||
|
fi
|
||||||
|
head_ "Exported to $PAYLOAD"
|
||||||
|
ls -1 "$PAYLOAD"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- check ------
|
||||||
|
do_check() {
|
||||||
|
head_ "Toolchain"
|
||||||
|
local missing=0 t
|
||||||
|
for t in clang++ clangd make git; do
|
||||||
|
if command -v "$t" >/dev/null 2>&1; then c_ok "$t $("$t" --version 2>&1 | head -1)"
|
||||||
|
else c_err "$t not found"; missing=1; fi
|
||||||
|
done
|
||||||
|
command -v cmake >/dev/null 2>&1 && c_ok "cmake (optional)" || c_warn "cmake not found (optional)"
|
||||||
|
|
||||||
|
head_ "Sublime Text"
|
||||||
|
local sd; sd="$(sublime_dir)"
|
||||||
|
[ -d "$sd" ] && c_ok "config dir: $sd" || c_warn "config dir absent: $sd (start Sublime once)"
|
||||||
|
command -v subl >/dev/null 2>&1 && c_ok "subl on PATH" || c_warn "subl not on PATH (optional)"
|
||||||
|
|
||||||
|
if [ "$missing" -ne 0 ]; then
|
||||||
|
head_ "Install the missing tools"
|
||||||
|
if command -v pacman >/dev/null 2>&1; then echo " sudo pacman -S --needed clang make git"
|
||||||
|
elif command -v apt-get >/dev/null 2>&1; then echo " sudo apt-get install -y clang clangd make git"
|
||||||
|
elif command -v dnf >/dev/null 2>&1; then echo " sudo dnf install -y clang clang-tools-extra make git"
|
||||||
|
elif command -v brew >/dev/null 2>&1; then echo " brew install llvm make git"
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- install ----
|
||||||
|
# Render a payload file for this machine (placeholder -> real project path),
|
||||||
|
# then install it only if the result differs from what is already there.
|
||||||
|
# This makes re-running install a no-op instead of piling up backups.
|
||||||
|
install_file() {
|
||||||
|
local src="$1" dst="$2" tmp
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
sed "s|__PROJECT_ROOT__|$REPO|g" "$src" > "$tmp"
|
||||||
|
if [ -f "$dst" ] && cmp -s "$tmp" "$dst"; then
|
||||||
|
c_ok "$(basename "$dst") (unchanged)"; rm -f "$tmp"; return 0
|
||||||
|
fi
|
||||||
|
if [ -f "$dst" ]; then
|
||||||
|
cp "$dst" "$dst.bak-$STAMP"
|
||||||
|
c_warn "existing file backed up: $(basename "$dst").bak-$STAMP"
|
||||||
|
fi
|
||||||
|
mv "$tmp" "$dst"; chmod 644 "$dst"
|
||||||
|
c_ok "$(basename "$dst")"
|
||||||
|
}
|
||||||
|
|
||||||
|
do_install() {
|
||||||
|
[ -d "$PAYLOAD" ] || { c_err "no payload at $PAYLOAD -- run './setup.sh export' on the source machine first"; exit 1; }
|
||||||
|
do_check || { c_err "toolchain incomplete; install the tools above and re-run"; exit 1; }
|
||||||
|
|
||||||
|
local user_dir; user_dir="$(sublime_dir)/Packages/User"
|
||||||
|
head_ "Installing Sublime config to $user_dir"
|
||||||
|
mkdir -p "$user_dir"
|
||||||
|
|
||||||
|
local f
|
||||||
|
for f in CP.sublime-build compete_CPP.sublime-build LSP-clangd.sublime-settings; do
|
||||||
|
[ -f "$PAYLOAD/$f" ] || continue
|
||||||
|
install_file "$PAYLOAD/$f" "$user_dir/$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -f "$PAYLOAD/keymap.json" ] && install_file "$PAYLOAD/keymap.json" "$user_dir/$(keymap_name)"
|
||||||
|
|
||||||
|
# Merge the package list rather than clobbering the user's own.
|
||||||
|
if [ -f "$PAYLOAD/packages.json" ]; then
|
||||||
|
python3 - "$PAYLOAD/packages.json" "$user_dir/Package Control.sublime-settings" <<'PY'
|
||||||
|
import json,re,os,sys,io
|
||||||
|
want=json.load(open(sys.argv[1])); dst=sys.argv[2]
|
||||||
|
have=[]
|
||||||
|
if os.path.exists(dst):
|
||||||
|
s=re.sub(r'^\s*//.*$','',open(dst).read(),flags=re.M)
|
||||||
|
s=re.sub(r',(\s*[\]}])',r'\1',s)
|
||||||
|
try: have=json.load(io.StringIO(s)).get('installed_packages',[])
|
||||||
|
except Exception: have=[]
|
||||||
|
merged=sorted(set(have)|set(want))
|
||||||
|
json.dump({"bootstrapped":True,"installed_packages":merged}, open(dst,'w'), indent=2)
|
||||||
|
added=sorted(set(want)-set(have))
|
||||||
|
print(" ok Package Control list merged (%d total%s)" %
|
||||||
|
(len(merged), (", added: "+", ".join(added)) if added else ", nothing new"))
|
||||||
|
PY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fresh workspace with the saved layout and no inherited history.
|
||||||
|
if [ -f "$PAYLOAD/layout.json" ]; then
|
||||||
|
python3 - "$PAYLOAD/layout.json" "$REPO/compete.sublime-workspace" "$REPO" <<'PY'
|
||||||
|
import json,os,sys
|
||||||
|
layout=json.load(open(sys.argv[1])); out,root=sys.argv[2],sys.argv[3]
|
||||||
|
if os.path.exists(out):
|
||||||
|
print(" warn workspace already exists, left alone: %s" % os.path.basename(out))
|
||||||
|
else:
|
||||||
|
json.dump({"layout":layout,"buffers":[],"groups":[{"sheets":[]} for _ in layout.get("cells",[[]])],
|
||||||
|
"folders":[{"path":root}],"file_history":[],
|
||||||
|
"build_system":"Packages/User/CP.sublime-build"}, open(out,'w'), indent=2)
|
||||||
|
print(" ok clean workspace created (layout only, no file history)")
|
||||||
|
PY
|
||||||
|
fi
|
||||||
|
|
||||||
|
head_ "Building the project"
|
||||||
|
if make -C "$REPO" --no-print-directory; then c_ok "make succeeded"; else c_err "make failed"; exit 1; fi
|
||||||
|
|
||||||
|
head_ "Done"
|
||||||
|
cat <<EOF
|
||||||
|
Open the project: subl --project "$REPO/Mocpp.sublime-project"
|
||||||
|
Build: Ctrl+B
|
||||||
|
Contest layout: Ctrl+Alt+Shift+C
|
||||||
|
Select build: Tools > Build System > CP (or compete_CPP)
|
||||||
|
|
||||||
|
Sublime will prompt to install missing packages via Package Control
|
||||||
|
on first start. Restart it once after that.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
export) do_export ;;
|
||||||
|
install) do_install ;;
|
||||||
|
check) do_check ;;
|
||||||
|
*) sed -n '2,9p' "${BASH_SOURCE[0]}"; exit 1 ;;
|
||||||
|
esac
|
||||||
8
source.cpp
Normal file
8
source.cpp
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
int main() {
|
||||||
|
std::vector<int> values{1,2,3,4,5};
|
||||||
|
for (int v : values) {
|
||||||
|
std::cout << v << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
35
sublime/CP.sublime-build
Normal file
35
sublime/CP.sublime-build
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
// Sublime expands BOTH ${var} and $var in cmd/shell_cmd/working_dir.
|
||||||
|
// Any $ meant for bash MUST be escaped as \$ -- see
|
||||||
|
// https://www.sublimetext.com/docs/build_systems.html
|
||||||
|
// Below, ${file} is Sublime's; every \$ is bash's.
|
||||||
|
"shell_cmd": "make --no-print-directory -C __PROJECT_ROOT__",
|
||||||
|
"file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
|
||||||
|
"working_dir": "__PROJECT_ROOT__",
|
||||||
|
"selector": "source.c++",
|
||||||
|
|
||||||
|
"variants":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Build all + Run this file",
|
||||||
|
"shell_cmd": "test -n \"${file}\" || { echo 'Click into a .cpp tab, then build.'; exit 1; }; make --no-print-directory -C __PROJECT_ROOT__ && \"\\$(dirname \"${file}\")/\\$(basename \"${file}\" .cpp)\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Build all + Run this file (with stdin)",
|
||||||
|
"shell_cmd": "test -n \"${file}\" || { echo 'Click into a .cpp tab, then build.'; exit 1; }; make --no-print-directory -C __PROJECT_ROOT__ && \"\\$(dirname \"${file}\")/\\$(basename \"${file}\" .cpp)\"",
|
||||||
|
"interactive": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Run this file only (no rebuild of others)",
|
||||||
|
"shell_cmd": "test -n \"${file}\" || { echo 'Click into a .cpp tab, then build.'; exit 1; }; clang++ \\$(cat \"${file_path}/compile_flags.txt\" 2>/dev/null) \"${file}\" -o \"${file_path}/${file_base_name}\" && \"${file_path}/${file_base_name}\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Clean",
|
||||||
|
"shell_cmd": "make --no-print-directory -C __PROJECT_ROOT__ clean && echo cleaned"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DIAGNOSTIC - show Sublime variables",
|
||||||
|
"shell_cmd": "echo \"file = [${file}]\"; echo \"file_path = [${file_path}]\"; echo \"base_name = [${file_base_name}]\"; echo \"folder = [${folder}]\""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
10
sublime/LSP-clangd.sublime-settings
Normal file
10
sublime/LSP-clangd.sublime-settings
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Settings in here override those in "LSP-clangd/LSP-clangd.sublime-settings"
|
||||||
|
|
||||||
|
{
|
||||||
|
"initializationOptions":{
|
||||||
|
"clangd.clang-tidy":true,
|
||||||
|
"clangd.background-index":true,
|
||||||
|
"clangd.header-insertion":"iwyu",
|
||||||
|
"clangd.completion-style":"detailed"
|
||||||
|
}
|
||||||
|
}
|
||||||
37
sublime/compete_CPP.sublime-build
Normal file
37
sublime/compete_CPP.sublime-build
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
// Competitive-programming workflow: compile the focused .cpp, feed it
|
||||||
|
// inputf.in on stdin, capture stdout to outputf.out, and print the result.
|
||||||
|
//
|
||||||
|
// Both files live NEXT TO THE SOURCE (working_dir is the file's folder),
|
||||||
|
// not at the repo root. Use "Setup" below to create them.
|
||||||
|
//
|
||||||
|
// Sublime expands ${name} AND bare $name in shell_cmd. Every $ meant for
|
||||||
|
// bash is escaped \$ -- see https://www.sublimetext.com/docs/build_systems.html
|
||||||
|
"shell_cmd": "test -n \"${file}\" || { echo 'Click into a .cpp tab, then build.'; exit 1; }; clang++ \\$(cat compile_flags.txt 2>/dev/null) \"${file}\" -o \"${file_base_name}\" || exit 1; touch inputf.in; ./\"${file_base_name}\" < inputf.in > outputf.out; echo '--- inputf.in ---'; cat inputf.in; echo '--- outputf.out ---'; cat outputf.out",
|
||||||
|
"file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
|
||||||
|
"working_dir": "${file_path:${folder}}",
|
||||||
|
"selector": "source.c++",
|
||||||
|
|
||||||
|
"variants":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
// Same as above, then check the answer against expectedf.out.
|
||||||
|
"name": "Run + check against expectedf.out",
|
||||||
|
"shell_cmd": "test -n \"${file}\" || { echo 'Click into a .cpp tab, then build.'; exit 1; }; clang++ \\$(cat compile_flags.txt 2>/dev/null) \"${file}\" -o \"${file_base_name}\" || exit 1; touch inputf.in; ./\"${file_base_name}\" < inputf.in > outputf.out; echo '--- outputf.out ---'; cat outputf.out; if [ -f expectedf.out ]; then echo '--- diff (expected vs actual) ---'; if diff -u expectedf.out outputf.out; then echo 'PASS'; else echo 'FAIL'; exit 1; fi; else echo '(no expectedf.out here - run Setup to make one)'; fi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// No redirect: type the input straight into the build panel.
|
||||||
|
"name": "Run (type input live)",
|
||||||
|
"shell_cmd": "test -n \"${file}\" || { echo 'Click into a .cpp tab, then build.'; exit 1; }; clang++ \\$(cat compile_flags.txt 2>/dev/null) \"${file}\" -o \"${file_base_name}\" || exit 1; ./\"${file_base_name}\"",
|
||||||
|
"interactive": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Setup: create inputf.in / expectedf.out here",
|
||||||
|
"shell_cmd": "touch inputf.in expectedf.out outputf.out; echo \"ready in \\$(pwd):\"; ls -l inputf.in expectedf.out outputf.out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DIAGNOSTIC - show Sublime variables",
|
||||||
|
"shell_cmd": "echo \"file = [${file}]\"; echo \"file_path = [${file_path}]\"; echo \"base_name = [${file_base_name}]\"; echo \"cwd = \\$(pwd)\""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
20
sublime/keymap.json
Normal file
20
sublime/keymap.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
[
|
||||||
|
// Competitive-programming layout: code on the left, inputf.in top-right,
|
||||||
|
// outputf.out bottom-right. Cells are [col_start, row_start, col_end, row_end].
|
||||||
|
{
|
||||||
|
"keys": ["ctrl+alt+shift+c"],
|
||||||
|
"command": "set_layout",
|
||||||
|
"args":
|
||||||
|
{
|
||||||
|
"cols": [0.0, 0.5, 1.0],
|
||||||
|
"rows": [0.0, 0.5, 1.0],
|
||||||
|
"cells": [[0, 0, 1, 2], [1, 0, 2, 1], [1, 1, 2, 2]]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Back to a single pane.
|
||||||
|
{
|
||||||
|
"keys": ["ctrl+alt+shift+1"],
|
||||||
|
"command": "set_layout",
|
||||||
|
"args": { "cols": [0.0, 1.0], "rows": [0.0, 1.0], "cells": [[0, 0, 1, 1]] }
|
||||||
|
}
|
||||||
|
]
|
||||||
32
sublime/layout.json
Normal file
32
sublime/layout.json
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"cols": [
|
||||||
|
0.0,
|
||||||
|
0.5,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"rows": [
|
||||||
|
0.0,
|
||||||
|
0.5,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
}
|
||||||
12
sublime/packages.json
Normal file
12
sublime/packages.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[
|
||||||
|
"LSP",
|
||||||
|
"LSP-clangd",
|
||||||
|
"MarkdownPreview",
|
||||||
|
"MarkdownPreviewEnhanced",
|
||||||
|
"Package Control",
|
||||||
|
"Phpactor",
|
||||||
|
"Python 3",
|
||||||
|
"Theme - Midnight",
|
||||||
|
"Theme - Monokai Pro",
|
||||||
|
"phpfmt"
|
||||||
|
]
|
||||||
Loading…
Reference in a new issue