Read PDF and EPUB e‑books inside VS Code — rendered as source code, Markdown docs, or log output, so reading blends into a normal editor session.
Plain Sight Reader is a Visual Studio Code extension for people who want to read a book in their development environment without it looking like they are reading a book. Import an e‑book, and its text appears in a normal editor tab — as commented source code (in the language of your choice), as a Markdown document, or as a stream of timestamped log lines. Page through it with the keyboard, jump between chapters, and hit a panic key to close it instantly.
It is a focused, offline, no‑telemetry e‑book reader for developers — a discreet way to read novels, non‑fiction, RFCs, documentation, or long‑form articles during downtime, commutes on a remote box, or a slow CI run.
- Why use it
- Features
- Requirements
- Installation
- Quick start
- Disguise modes
- Commands
- Keyboard shortcuts
- Settings
- How it works
- Privacy
- Supported formats
- FAQ
- Troubleshooting
- Limitations
- Roadmap
- Contributing
- License
- Read without context‑switching. No second app, no browser tab, no phone. The book is a tab next to your code.
- Low profile. A glance at your screen reads as "documentation" or "logs," not Dune.
- Keyboard‑driven. Turn pages and jump chapters without touching the mouse.
- Yours, offline. Books live on your machine. The extension makes zero network requests.
- It resumes. Close the tab, restart VS Code, reopen the book — you are back where you left off.
Typical uses: reading a novel on a break, getting through a technical book a page at a time, reviewing a long PDF spec, or catching up on saved long‑form articles exported to EPUB.
| 📥 Import PDF & EPUB | Text is extracted, split into sections, and stored locally in a personal library. |
| 🎭 Three disguise modes | code (8 languages), markdown, and log — switch any time with one keystroke. |
| ⌨️ Keyboard navigation | Next/previous page, next/previous section, jump‑to‑section quick pick. |
| 🔖 Automatic bookmarks | Reading position is saved per book and restored on reopen. |
| 🚨 Panic key | One shortcut closes the reader tab immediately (optionally runs another command). |
| 🧩 No sidebar, no clutter | Everything runs through the Command Palette and quick picks. |
| 📴 Fully offline | No accounts, no telemetry, no external calls. |
- VS Code 1.84 or newer.
- To build from source: Node.js 18+ and npm.
No other runtime dependencies — the extension is bundled into a single file.
- In VS Code: open Extensions (
Ctrl+Shift+X), search "Plain Sight Reader", click Install. - Or from a terminal:
code --install-extension saad-works.plain-sight-reader - Or open the Marketplace listing and click Install.
- Download
plain-sight-reader-<version>.vsixfrom the Releases page (or build it — see below). - Command Palette (
Ctrl+Shift+P/Cmd+Shift+P) → Extensions: Install from VSIX… → choose the file. - Developer: Reload Window.
git clone https://github.com/saad-works/plain-sight-reader.git
cd plain-sight-reader
npm install
npm run compile # bundle to dist/extension.js
npx @vscode/vsce package # produces plain-sight-reader-<version>.vsixThen install the generated .vsix as described above.
Open the folder in VS Code and press F5. A second VS Code window (the Extension Development Host) launches with the extension loaded. Use npm run watch for incremental rebuilds.
- Command Palette →
Plain Sight Reader: Import Book…and pick a.pdfor.epub. - When prompted, choose Open now (or later:
Plain Sight Reader: Open Book…). - The book opens as an editor tab. Turn pages with Ctrl+Alt+. and Ctrl+Alt+,.
Change how it looks with Cycle Disguise Mode (Ctrl+Alt+M), and set the code language in Settings → plainSightReader.codeLanguage.
Cycle modes with Ctrl+Alt+M, or set plainSightReader.disguiseMode. The reader tab's file extension and syntax highlighting follow the mode automatically.
Each page is split into small groups of paragraphs. Every group becomes a documentation comment attached to a plausible, synthetic declaration — a function, struct, guard clause, property, or constant — so the page reads as a heavily‑documented source file rather than a wall of comments.
Set the language with plainSightReader.codeLanguage. Supported: typescript (default), javascript, csharp, java, go, rust, ruby, python. Any other value falls back to typescript.
TypeScript
// ThePierAtLowTide — Northbound
// part 3 of 9
import { normalize } from "./normalize";
import type { Frame, Node, Segment } from "./types";
/**
* The morning fog had not yet lifted from the harbour when Elena stepped onto the pier,
* her boots loud on the wet planks. She had rehearsed the conversation a dozen times on
* the train, and every version ended badly.
*/
export function resolveEntry(input: Entry, offset = 0): Entry {
const entry = input.slice(offset);
return normalize(entry);
}C#
internal static class ThePierAtLowTide
{
/// <summary>
/// When he finally appeared at the end of the pier he was not alone. The second figure
/// hung back, half hidden by the mist, and Elena felt the shape of the afternoon change.
/// </summary>
public static IReadOnlyList<Scope> Scopes { get; } = new List<Scope>();
}Renders as a clean Markdown document — a section heading, a subtle progress line, then the prose. Looks like reading project docs or a design note.
# The Pier at Low Tide
_Northbound — part 3 of 9_
The morning fog had not yet lifted from the harbour when Elena stepped onto the pier…Renders as timestamped application logs. Each sentence is a log line with a rotating level (INFO, DEBUG, WARN, TRACE).
2026-01-01 09:00:00.000 INFO reader.stream The morning fog had not yet lifted from the harbour.
2026-01-01 09:00:01.373 DEBUG reader.stream She had rehearsed the conversation a dozen times on the train.
2026-01-01 09:00:02.746 INFO reader.stream Every version ended badly.
All commands are under the Plain Sight Reader category in the Command Palette (Ctrl+Shift+P).
| Command | ID | Description |
|---|---|---|
| Import Book… | plainSightReader.importBook |
Pick a .pdf / .epub and add it to your library |
| Open Book… | plainSightReader.openBook |
Choose a book and start (or resume) reading |
| Go to Section… | plainSightReader.goToChapter |
Jump to a section via quick pick |
| Remove Book… | plainSightReader.removeBook |
Delete a book and its saved position |
| Next Page | plainSightReader.nextPage |
Turn forward (wraps into the next section) |
| Previous Page | plainSightReader.prevPage |
Turn back (wraps into the previous section) |
| Next Section | plainSightReader.nextChapter |
Go to the start of the next section |
| Previous Section | plainSightReader.prevChapter |
Go to the start of the previous section |
| Cycle Disguise Mode | plainSightReader.cycleDisguise |
Rotate code → markdown → log |
| Panic (Close Reader) | plainSightReader.panic |
Close the reader tab immediately |
| Keys | Action | Active when |
|---|---|---|
| Ctrl+Alt+. | Next page | reader tab focused |
| Ctrl+Alt+, | Previous page | reader tab focused |
| Ctrl+Alt+Shift+. | Next section | reader tab focused |
| Ctrl+Alt+Shift+, | Previous section | reader tab focused |
| Ctrl+Alt+M | Cycle disguise mode | reader tab focused |
| Ctrl+Alt+Q | Panic — close reader | always |
Rebind any of these in Preferences: Open Keyboard Shortcuts (search for plainSightReader). On macOS, substitute Cmd where appropriate via your own bindings.
Open Settings (Ctrl+,) and search for Plain Sight Reader, or edit settings.json.
| Setting | Type | Default | Description |
|---|---|---|---|
plainSightReader.disguiseMode |
code | markdown | log |
code |
How book text is displayed. |
plainSightReader.codeLanguage |
string | typescript |
Language for the code disguise: typescript, javascript, csharp, java, go, rust, ruby, python. |
plainSightReader.charsPerPage |
number | 1600 |
Approximate characters per page. Lower = more frequent page turns. |
plainSightReader.wrapColumn |
number | 100 |
Line‑wrap width for the code disguise. |
plainSightReader.showStatusBar |
boolean | false |
Show a subtle reading‑progress indicator in the status bar. |
plainSightReader.panicCommand |
string | "" |
Optional command ID to run after the panic key closes the reader tab (e.g. workbench.action.quickOpen). |
Changes apply live — the open reader re‑renders and keeps your place.
import ─▶ parse (pdf-parse / jszip + node-html-parser)
─▶ extract text, split into sections
─▶ store as JSON in the extension's global storage
│
open ─▶ load JSON ─▶ paginate ─▶ render through the active disguise
─▶ show as a read-only virtual document (custom `psr:` URI scheme)
navigate ─▶ each page turn re-renders and saves { section, page } to your library
- Parsing. PDFs go through
pdf-parsepage by page; chapter headings ("Chapter 3", "Part II", …) are detected where present, otherwise pages are grouped in blocks. EPUBs are unzipped withjszip; the OPF spine is read for order and metadata, and each XHTML document is converted to text. - Storage. Book text is written as a JSON file under VS Code's per‑extension global storage; the library index and per‑book reading position live in
globalState. Uninstalling and reinstalling keeps your library (it is keyed by extension ID, not bundled in the.vsix). - Rendering. The reader is a read‑only virtual document. It cannot be edited or accidentally saved, and it never touches your workspace files.
Plain Sight Reader is fully local:
- No network requests. Nothing is uploaded, checked, or phoned home.
- No telemetry or analytics.
- No accounts.
- Your books and reading positions never leave your machine.
| Format | Support | Notes |
|---|---|---|
EPUB (.epub) |
✅ EPUB 2 & 3 | Spine order and metadata (title, author) are read from the OPF. Footnotes, tables, and complex layouts are flattened to text. |
PDF (.pdf) |
✅ text‑based PDFs | Text is extracted per page. Scanned / image‑only PDFs have no text layer and cannot be read (no OCR). Multi‑column and heavily designed layouts may extract imperfectly. |
| MOBI / AZW3 | ❌ | Convert to EPUB first (e.g. with Calibre). |
Is this a way to read books at work without anyone noticing?
That is the design goal. In code or log mode a passing glance reads as ordinary editor content. It is not invisible to someone who stops and reads your screen.
Can I read novels, not just technical books? Yes. Any text‑based EPUB or PDF works — fiction, non‑fiction, articles, specs.
Does it work over SSH / in a remote or container workspace? It runs wherever the extension host runs. Import and library storage happen on that host.
Will my place be saved if I close VS Code? Yes. Position is saved on every page turn and restored when you reopen the book.
Does it change or create files in my project? No. The reader is a read‑only virtual document; book data is kept in the extension's private global storage.
Why is a VS Code extension written in TypeScript and not C#?
VS Code's extension host only runs JavaScript/TypeScript. You can still display the book as C# (or Go, Rust, Python, …) via plainSightReader.codeLanguage.
Can I sync my library across machines?
Not automatically. Copy the same .vsix to each machine and re‑import, or copy the extension's global storage folder manually.
Is it on the VS Code Marketplace?
Yes — search "Plain Sight Reader" in the Extensions view, or install saad-works.plain-sight-reader. A .vsix is also attached to each GitHub release.
"No selectable text found in this PDF."
The PDF is a scanned image with no text layer. Run it through OCR (e.g. Calibre, ocrmypdf) or find a text‑based copy.
The code disguise still looks like a wall of comments with an empty class.
You are running an older build. Uninstall the extension, Developer: Reload Window, install the current .vsix, and reload again. If a second VS Code window (or an F5 dev host) is open, it may be running the old build.
Page‑turn shortcuts do nothing.
They only fire while the reader tab is focused (when: plainSightReader.active). Click the tab first, or use the Command Palette.
Sections are just "Pages 1–8", "Pages 9–16", … No chapter headings were detected in the PDF, so pages were grouped in fixed blocks. This is expected for many PDFs.
- No OCR — scanned PDFs are not supported.
- PDF chapter detection is heuristic; results vary by document.
- Text extraction favours readability over exact layout; footnotes, endnotes, tables, and multi‑column pages may be reflowed or reordered.
- Images, figures, and formatting (bold/italic, links) are dropped.
- Library sync across machines is manual.
- Quick‑pick command to switch code language without opening Settings
- Configurable panic behaviour (switch tab vs. close vs. run command)
- Per‑book disguise/language overrides
- Adjustable "words per page" and font‑size‑aware pagination
- Better PDF structure detection (table of contents / outline)
- Publish to Open VSX (Cursor / VSCodium / Gitpod)
Suggestions welcome — open an issue.
npm install
npm run watch # incremental bundle
npm run typecheck # tsc --noEmitPress F5 to launch the Extension Development Host. Project layout:
src/
extension.ts activation + command wiring
library.ts library index + book storage + reading position
importer/
index.ts dispatch by file type
pdf.ts PDF text extraction + section heuristics
epub.ts EPUB unzip + OPF spine + XHTML → text
types.ts
reader/
pagination.ts split section text into pages
disguise.ts code / markdown / log renderers
readerController.ts session state + virtual document + navigation
util/html.ts XHTML → plain text
Please keep changes focused and run npm run typecheck before opening a PR.
MIT © 2026 saad-works
