filesystem

package
v1.56.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Index

Constants

View Source
const MaxEditableBytes = 16 << 20 // 16 MiB

MaxEditableBytes caps how large a file may be to be read into the editor.

View Source
const MaxListedFiles = 5000

MaxListedFiles caps the recursive file list ListFilesUnder returns.

View Source
const MaxSearchMatches = 200

MaxSearchMatches caps how many matching lines a project search returns. This is a limit on the answer, not on the search: every file is scanned, and this only decides how much of the result travels to the browser.

View Source
const QuickOpenLimit = 100

QuickOpenLimit caps how many matches one quick open query returns. The palette never renders more than this, so finding more is wasted work.

Variables

View Source
var (
	ErrTooLarge = errors.New("File is too large to edit in the browser.")
	ErrBinary   = errors.New("Binary files cannot be edited.")
)

ErrTooLarge and ErrBinary mark files the browser editor cannot edit; callers can offer a viewer or a download instead of a plain error.

View Source
var (
	ErrFileChanged = errors.New("The file changed on disk after it was opened.")
	ErrFileDeleted = errors.New("The file no longer exists on disk.")
)

ErrFileChanged and ErrFileDeleted are the two ways a versioned write is refused, and they are told apart because the ways out are not the same: a changed file can be read back over the buffer, a deleted one cannot and can only be written again as a new file. Neither of them ever writes.

View Source
var DefaultExclusions = []string{".git"}

DefaultExclusions are what applies when nothing has been configured: only git's own storage, which is never something you open in an editor and whose loose object files can outnumber the project itself.

Dependency folders are deliberately not in here. They are code you do read, stepping into a library to see what it does is a normal thing to want, and the index carries them without trouble. What they cost is relevance rather than time, so excluding them is a choice each project makes on the search settings.

View Source
var ErrExists = errors.New("A file or folder with that name already exists.")

ErrExists reports a name that is already taken. The editor answers it with a 409 so the browser can offer to overwrite instead of showing a dead end.

Functions

func AbsDir

func AbsDir(p string) string

AbsDir spells a directory the one way everybody who derives something from it has to spell it: a command may name it relative or with a ~, and two spellings of one directory must not become two answers. It is what the unix socket paths are derived from, where a relative path would be resolved against whatever working directory the reader happens to have, and where the length of the path decides whether it fits an address at all.

A path that cannot be expanded is answered as it came: nothing here refuses, the caller's own step does that if it has to.

func ArchiveExt

func ArchiveExt(name string) (string, bool)

ArchiveExt reports whether the name looks like an archive the editor can unpack, and returns the name without that extension.

func CheckEditableText

func CheckEditableText(data []byte) error

CheckEditableText rejects content the browser editor cannot handle: anything over MaxEditableBytes and binary data (containing a NUL byte).

func CleanBaseName

func CleanBaseName(raw string) (string, error)

func ExpandHome

func ExpandHome(p string) (string, error)

func HomeDir

func HomeDir() (string, error)

func HumanSize

func HumanSize(n int64) string

HumanSize formats a byte count for display; negative counts are "unknown".

func IsUnder

func IsUnder(path, root string) bool

IsUnder reports whether path is root itself or inside root.

func ListFilesUnder

func ListFilesUnder(root, rel string, ex Exclusions) (files []string, truncated bool, err error)

ListFilesUnder walks one directory inside root and answers the relative paths of every regular file in it. An empty rel walks the whole project.

The excluded directories are skipped, symlinked directories are not followed, and the walk stops after MaxListedFiles files and reports truncation. The paths stay relative to root, which is what every client path is, so a caller that only cares about a subtree pays for that subtree and the cap counts there.

The quick open palette does not use this: it answers from an index of the whole project instead, because a capped list cannot find what sits past the cap. See QuickOpenCache.

func ReadFileText

func ReadFileText(root, rel string) (content string, version string, err error)

ReadFileText reads a regular file for editing and answers the version token of exactly the bytes it returns, which is what the save carries back. The token is taken from those bytes and never from a second look at the disk: a write landing between the read and that look would hand the caller a token for content it never saw. It rejects directories, files over MaxEditableBytes, and binary content.

func ResolveExistingFile

func ResolveExistingFile(root, rel string) (string, os.FileInfo, error)

ResolveExistingFile resolves rel to an existing regular file under root and returns its absolute path along with its file info.

func ResolveUnder

func ResolveUnder(root, rel string) (string, error)

ResolveUnder cleans a slash-separated, client-supplied relative path and joins it onto root, refusing anything that would escape root (lexically or via symlinks). An empty rel resolves to root itself.

func SnippetAround

func SnippetAround(text string, idx int) string

SnippetAround trims a line for transport, keeping a window around the byte offset idx when the line is longer than the snippet cap. The search cuts around its match and the LSP usage previews around the usage's column, so the two lists that share one look share one cutting rule; idx counts into the given text, leading whitespace included, and is clamped.

func ToDirectoryName

func ToDirectoryName(raw string) string

ToDirectoryName normalizes user input to a lowercase alnum-dash slug. Invalid characters collapse to '-', and invalid/empty inputs become "".

func WriteTarGz

func WriteTarGz(w io.Writer, dir, prefix string) error

WriteTarGz streams dir as a gzipped tar, with prefix as the single top level folder inside the archive. Only directories and regular files go in, anything else (symlinks, sockets) is skipped so the archive stays inside the project.

Types

type EditorConfig

type EditorConfig struct {
	IndentStyle string `json:"indentStyle,omitempty"` // "tab" or "space"
	IndentSize  int    `json:"indentSize,omitempty"`  // columns; 0 when unset or "tab"
	TabWidth    int    `json:"tabWidth,omitempty"`
}

EditorConfig holds the indentation properties EditorConfig resolves for one file. A zero field means the property is unset, so the client keeps its own default for it.

func EditorConfigForFile

func EditorConfigForFile(root, rel string) EditorConfig

EditorConfigForFile resolves the EditorConfig indentation properties that apply to root/rel by walking the .editorconfig cascade upward from the file. A missing, unreadable or malformed config yields a zero EditorConfig, never an error: editorconfig is advisory and must not block opening a file.

type Entry

type Entry struct {
	Name     string `json:"name"`
	RelPath  string `json:"path"`
	IsDir    bool   `json:"isDir"`
	Size     int64  `json:"size"`
	SizeText string `json:"sizeText"`
	ModTime  string `json:"modTime"`
}

Entry is one item (file or directory) inside a project tree. RelPath is the slash-separated path relative to the tree root and is what the client sends back to identify the item.

func CopyEntry

func CopyEntry(root, rel, dirRel string, overwrite bool) (Entry, error)

CopyEntry copies the file or directory at root/rel into the directory root/dirRel. Copying into its own folder duplicates the entry under a free "name copy" name; anywhere else a taken name is ErrExists unless overwrite replaces the file there.

func CreateDir

func CreateDir(root, rel string) (Entry, error)

CreateDir creates a directory at root/rel (and any missing parents). It fails if the path already exists.

func CreateFile

func CreateFile(root, rel string) (Entry, error)

CreateFile creates an empty regular file at root/rel, creating any missing parent directories. It fails if the path already exists.

func DeleteEntry

func DeleteEntry(root, rel string) (Entry, error)

DeleteEntry removes a file or directory (recursively) under root. It refuses to delete the root itself.

func ExtractArchive

func ExtractArchive(root, rel string) (Entry, error)

ExtractArchive unpacks root/rel into a new folder next to it, named after the archive. The folder name is free by construction, so nothing is overwritten. Only directories and regular files are written; anything else in the archive (symlinks, devices, paths that would leave the folder) is skipped.

func ListDir

func ListDir(root, rel string) ([]Entry, error)

ListDir returns the directories and regular files directly inside root/rel, directories first, then files, each ordered case-insensitively by name.

func MoveEntry

func MoveEntry(root, rel, dirRel string, overwrite bool) (Entry, error)

MoveEntry moves the file or directory at root/rel into the directory root/dirRel, keeping its base name. An empty dirRel is the project root. Without overwrite a taken name is reported as ErrExists, with it the file there is replaced.

func RenameEntry

func RenameEntry(root, rel, newName string) (Entry, error)

RenameEntry renames the file or directory at root/rel to newName inside the same parent directory. newName must be a bare name without path separators.

func SaveUpload

func SaveUpload(root, dirRel, filename string, src io.Reader, overwrite, createDirs bool) (Entry, error)

SaveUpload stores an uploaded file as dirRel/filename under root, writing to a temp file first and renaming into place. A taken name is reported as ErrExists unless overwrite replaces the file there; createDirs makes the missing folders on the way, which is what a folder upload needs.

func WriteFileText

func WriteFileText(root, rel string, content []byte) (Entry, error)

WriteFileText writes content to root/rel atomically, preserving the existing file mode when the target already exists. The parent directory must exist. It writes whatever is there: a caller that has to keep somebody else's work goes through WriteFileTextIfUnchanged.

func WriteFileTextIfUnchanged

func WriteFileTextIfUnchanged(root, rel string, content []byte, version string) (Entry, string, error)

WriteFileTextIfUnchanged writes content to root/rel, but only while the file on disk is still the one version was taken from. A file somebody else wrote in the meantime is ErrFileChanged and a file that is gone is ErrFileDeleted; both leave the disk exactly as it is. The version of what was written comes back with the entry, so a second save right after the first asks nothing.

An empty version is the create path and writes whatever is there. That is what a file created in the editor takes on its first save, before anything ever read it back, and it is what the answer to a deleted file is: writing the buffer as a new file is a decision somebody made in front of the dialog, not a save that overwrote something silently.

type Exclusions

type Exclusions struct {
	// contains filtered or unexported fields
}

Exclusions are the directories a recursive walk stays out of: version control internals, vendored dependencies, build output. They are configured per install on the editor's search settings, because which folders are noise is a property of the project, not something the editor can know.

Two kinds of entry:

  • a bare name like "vendor" or "node_modules" excludes every directory with that name, at any depth
  • a path like "tests/_output" excludes exactly that directory, relative to the project root

Excluding a directory keeps the walk out of it entirely, so nothing below it is indexed or searched either.

func DefaultExclusionSet

func DefaultExclusionSet() Exclusions

DefaultExclusionSet is ParseExclusions over DefaultExclusions.

func ParseExclusions

func ParseExclusions(raw string) Exclusions

ParseExclusions reads a stored or submitted list. Entries are separated by newlines or commas; surrounding whitespace and slashes are trimmed, blank entries and duplicates are dropped. Anything that would exclude the project root itself is ignored, because a walk of nothing is never what someone meant.

func (Exclusions) Equal

func (e Exclusions) Equal(other Exclusions) bool

Equal reports whether two sets exclude the same things. The quick open index uses it to notice that the setting changed and rebuild.

func (Exclusions) Len

func (e Exclusions) Len() int

Len reports how many entries there are.

func (Exclusions) List

func (e Exclusions) List() []string

List returns the entries in a stable order, names first, then paths, each alphabetically. It is what the settings form shows and what gets stored, so the value a person sees is the value that applies.

func (Exclusions) SkipDir

func (e Exclusions) SkipDir(rel, name string) bool

SkipDir reports whether a walk should stay out of the directory at the given root-relative path. name is its base name.

func (Exclusions) String

func (e Exclusions) String() string

String is the canonical stored form: one entry per line.

type File

type File struct {
	Name     string
	Path     string
	Size     int64
	SizeText string
	ModTime  string
}

func DeleteFile

func DeleteFile(dir, rawName string) (File, error)

func ListFiles

func ListFiles(dir string) ([]File, error)

func SaveFile

func SaveFile(dir, rawName string, src io.Reader) (File, error)

type OpenedFile

type OpenedFile struct {
	File
	io.ReadCloser
}

func OpenFile

func OpenFile(dir, rawName string) (OpenedFile, error)

type QuickOpenCache

type QuickOpenCache struct {
	// contains filtered or unexported fields
}

QuickOpenCache holds one index per project. Queries are answered from the index that is there, so only the very first query of a project waits for a walk. An index the editor itself invalidated is rebuilt on the next query; an index that merely went stale is served as it is and refreshed in the background.

func NewQuickOpenCache

func NewQuickOpenCache() *QuickOpenCache

NewQuickOpenCache returns an empty cache.

func (*QuickOpenCache) Forget

func (c *QuickOpenCache) Forget(root string)

Forget drops root from the cache entirely. Invalidate is for a project that changed and will be queried again, so it keeps the entry and only clears the index; a project that is gone has no next query, and the sweep that would have collected it runs on a query. Its index would sit there until somebody happened to search in another project.

func (*QuickOpenCache) Invalidate

func (c *QuickOpenCache) Invalidate(root string)

Invalidate drops the index for root so the next query rebuilds it. The editor calls this after it changed the tree itself, which is what makes a file findable immediately after being created.

func (*QuickOpenCache) Query

func (c *QuickOpenCache) Query(root, query, scope string, ex Exclusions, limit int) (QuickOpenMatches, error)

Query answers a quick open query for the project at root, building or refreshing the index as needed.

type QuickOpenMatches

type QuickOpenMatches struct {
	Paths   []string
	Total   int
	Indexed int
}

QuickOpenMatches is one answer to a quick open query: the paths to show and how many candidates there were in total, so the palette can say that it is showing only the first few.

type SearchMatch

type SearchMatch struct {
	Path       string `json:"path"`
	Line       int    `json:"line"`
	Text       string `json:"text"`
	MatchStart int    `json:"start"`
	MatchLen   int    `json:"len"`
}

SearchMatch is one matching line of a project wide text search. MatchStart and MatchLen locate the hit inside Text, in UTF-16 units, the coordinate the browser slices strings in, so the palette can mark the hit by position instead of searching the snippet again.

func SearchFiles

func SearchFiles(root, query string, useRegex bool, ex Exclusions) ([]SearchMatch, bool, error)

SearchFiles scans every regular file under root for a case insensitive substring match, or with useRegex for a case insensitive RE2 match, staying out of the excluded directories and ignoring binary and oversized files.

It used to stop after the first 5000 files as well as after 200 matches, which meant a search could report "no matches" while the file it was looking for sat unread a few thousand entries further down. In a large project, searching for a symbol in your own code spent the whole budget inside generated code and answered, quickly and confidently, that nothing matched. The file limit is gone; only the answer is capped.

Files are read on a pool of workers because the scan is bound by reading tens of thousands of files rather than by matching bytes. The result is still the first MaxSearchMatches matches in path order, exactly what a single threaded in-order scan would have returned.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL