buffer

package
v0.0.0-...-b361468 Latest Latest
Warning

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

Go to latest
Published: Jan 3, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DSUnchanged    = 0
	DSAdded        = 1
	DSModified     = 2
	DSDeletedAbove = 3
)
View Source
const (

	// TextEventInsert represents an insertion event
	TextEventInsert = 1
	// TextEventRemove represents a deletion event
	TextEventRemove = -1
	// TextEventReplace represents a replace event
	TextEventReplace = 0
)
View Source
const (
	// Line ending file formats
	FFAuto = 0 // Autodetect format
	FFUnix = 1 // LF line endings (unix style '\n')
	FFDos  = 2 // CRLF line endings (dos style '\r\n')
)
View Source
const BookmarkResultNotFound = 0
View Source
const BookmarkResultOk = 1
View Source
const BookmarkResultWrap = 2
View Source
const LargeFileThreshold = 50000

LargeFileThreshold is the number of bytes when fastdirty is forced because hashing is too slow

Variables

View Source
var BracePairs = [][2]rune{
	{'(', ')'},
	{'{', '}'},
	{'[', ']'},
}

Functions

func ByteOffset

func ByteOffset(pos Loc, buf *Buffer) int

ByteOffset is just like ToCharPos except it counts bytes instead of runes

func CreateRegex

func CreateRegex(s string, useRegex bool, caseSensitive bool) (*regexp.Regexp, error)

func DiffLA

func DiffLA(a, b Loc, buf *LineArray) int

Diff returns the distance between two locations

func ExecuteTextEvent

func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer)

ExecuteTextEvent runs a text event

func InBounds

func InBounds(pos Loc, buf *Buffer) bool

InBounds returns whether the given location is a valid character position in the given buffer

Types

type Buffer

type Buffer struct {
	*EventHandler
	*SharedBuffer

	StartCursor Loc

	ManualSelection *Cursor
	Bookmarks       []*Cursor

	// OptionCallback is called after a buffer option value is changed.
	// The display module registers its OptionCallback to ensure the buffer window
	// is properly updated when needed. This is a workaround for the fact that
	// the buffer module cannot directly call the display's API (it would mean
	// a circular dependency between packages).
	OptionCallback func(option string, nativeValue any)

	// The display module registers its own GetVisualX function for getting
	// the correct visual x location of a cursor when softwrap is used.
	// This is hacky. Maybe it would be better to move all the visual x logic
	// from buffer to display, but it would require rewriting a lot of code.
	GetVisualX func(loc Loc) int

	// Last search stores the last successful search
	LastSearch      string
	LastSearchRegex bool
	// HighlightSearch enables highlighting all instances of the last successful search
	HighlightSearch bool

	// OverwriteMode indicates that we are in overwrite mode (toggled by
	// Insert key by default) i.e. that typing a character shall replace the
	// character under the cursor instead of inserting a character before it.
	OverwriteMode bool
	// contains filtered or unexported fields
}

Buffer stores the main information about a currently open file including the actual text (in a LineArray), the undo/redo stack (in an EventHandler) all the cursors, the syntax highlighting info, the settings for the buffer and some misc info about modification time and path location. The syntax highlighting info must be stored with the buffer because the syntax highlighter attaches information to each line of the buffer for optimization purposes so it doesn't have to rehighlight everything on every update. Likewise for the search highlighting.

func NewBuffer

func NewBuffer(r io.Reader, size int64, path string) *Buffer

NewBuffer creates a new buffer from a given reader with a given path Ensure that ReadSettings and InitGlobalSettings have been called before creating a new buffer Places the cursor at startcursor. If startcursor is -1, -1 places the cursor at an autodetected location (based on savecursor or :LINE:COL)

func NewBufferFromString

func NewBufferFromString(text, path string) *Buffer

NewBufferFromString creates a new buffer containing the given string

func NewBufferFromStringWithCommand

func NewBufferFromStringWithCommand(text, path string, screenRedrawCallback func()) *Buffer

NewBufferFromStringWithCommand creates a new buffer containing the given string with a cursor loc and a search text

func (*Buffer) AddCursor

func (b *Buffer) AddCursor(c *Cursor)

AddCursor adds a new cursor to the list

func (*Buffer) BookmarkLinesSet

func (b *Buffer) BookmarkLinesSet() map[int]bool

func (*Buffer) ClearCursors

func (b *Buffer) ClearCursors()

ClearCursors removes all extra cursors

func (*Buffer) ClearMatches

func (b *Buffer) ClearMatches()

ClearMatches clears all of the syntax highlighting for the buffer

func (*Buffer) DeselectCursors

func (b *Buffer) DeselectCursors()

DeselectCursors removes selection from all cursors

func (*Buffer) DiffStatus

func (b *Buffer) DiffStatus(lineN int) DiffStatus

DiffStatus returns the diff status for a line in the buffer

func (*Buffer) DoSetOptionNative

func (b *Buffer) DoSetOptionNative(option string, nativeValue any)

func (*Buffer) FileType

func (b *Buffer) FileType() string

FileType returns the buffer's filetype

func (*Buffer) FindMatchingBrace

func (b *Buffer) FindMatchingBrace(start Loc) (Loc, bool, bool)

If there is a brace character (for example '{' or ']') at the given start location, FindMatchingBrace returns the location of the matching brace for it (for example '}' or '['). The second returned value is true if there was no matching brace found for given starting location but it was found for the location one character left of it. The third returned value is true if the matching brace was found at all.

func (*Buffer) FindNext

func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bool, caseSensitive bool) ([2]Loc, bool, error)

FindNext finds the next occurrence of a given string in the buffer It returns the start and end location of the match (if found) and a boolean indicating if it was found May also return an error if the search regex is invalid

func (*Buffer) FindNextDiffLine

func (b *Buffer) FindNextDiffLine(startLine int, forward bool) (int, error)

FindNextDiffLine returns the line number of the next block of diffs. If `startLine` is already in a block of diffs, lines in that block are skipped.

func (*Buffer) GetActiveCursor

func (b *Buffer) GetActiveCursor() *Cursor

GetActiveCursor returns the main cursor in this buffer

func (*Buffer) GetCursor

func (b *Buffer) GetCursor(n int) *Cursor

GetCursor returns the nth cursor

func (*Buffer) GetCursors

func (b *Buffer) GetCursors() []*Cursor

GetCursors returns the list of cursors in this buffer

func (*Buffer) IndentString

func (b *Buffer) IndentString(tabsize int) string

IndentString returns this buffer's indent method (a tabstop or n spaces depending on the settings)

func (*Buffer) Insert

func (b *Buffer) Insert(start Loc, text string)

Insert inserts the given string of text at the start location

func (*Buffer) Line

func (b *Buffer) Line(i int) string

Line returns the string representation of the given line number

func (*Buffer) MergeCursors

func (b *Buffer) MergeCursors()

MergeCursors merges any cursors that are at the same position into one cursor

func (*Buffer) Modified

func (b *Buffer) Modified() bool

Modified returns if this buffer has been modified since being opened

func (*Buffer) MoveLinesDown

func (b *Buffer) MoveLinesDown(start int, end int)

MoveLinesDown moves the range of lines down one row

func (*Buffer) MoveLinesUp

func (b *Buffer) MoveLinesUp(start int, end int)

MoveLinesUp moves the range of lines up one row

func (*Buffer) NextBookmark

func (b *Buffer) NextBookmark(row int) (int, int)

func (*Buffer) NumCursors

func (b *Buffer) NumCursors() int

NumCursors returns the number of cursors

func (*Buffer) PreviousBookmark

func (b *Buffer) PreviousBookmark(row int) (int, int)

func (*Buffer) RelocateCursors

func (b *Buffer) RelocateCursors()

RelocateCursors relocates all cursors (makes sure they are in the buffer)

func (*Buffer) Remove

func (b *Buffer) Remove(start, end Loc)

Remove removes the characters between the start and end locations

func (*Buffer) RemoveCursor

func (b *Buffer) RemoveCursor(i int)

func (*Buffer) ReplaceRegex

func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte, captureGroups bool) (int, int)

ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area and returns the number of replacements made and the number of characters added or removed on the last line of the range

func (*Buffer) Retab

func (b *Buffer) Retab()

Retab changes all tabs to spaces or vice versa

func (*Buffer) RuneAt

func (b *Buffer) RuneAt(loc Loc) rune

RuneAt returns the rune at a given location in the buffer

func (*Buffer) SearchMatch

func (b *Buffer) SearchMatch(pos Loc) bool

SearchMatch returns true if the given location is within a match of the last search. It is used for search highlighting

func (*Buffer) SetCurCursor

func (b *Buffer) SetCurCursor(n int)

SetCurCursor sets the current cursor

func (*Buffer) SetCursors

func (b *Buffer) SetCursors(c []*Cursor)

SetCursors resets this buffer's cursors to a new list

func (*Buffer) SetDiffBase

func (b *Buffer) SetDiffBase(diffBase []byte)

SetDiffBase sets the text that is used as the base for diffing the buffer content

func (*Buffer) SetOptionNative

func (b *Buffer) SetOptionNative(option string, nativeValue any) error

func (*Buffer) Size

func (b *Buffer) Size() int

Size returns the number of bytes in the current buffer

func (*Buffer) ToggleBookmark

func (b *Buffer) ToggleBookmark(row int)

func (*Buffer) UpdateCursors

func (b *Buffer) UpdateCursors()

UpdateCursors updates all the cursors indices

func (*Buffer) UpdateDiff

func (b *Buffer) UpdateDiff()

UpdateDiff computes the diff between the diff base and the buffer content. The update may be performed synchronously or asynchronously. If an asynchronous update is already pending when UpdateDiff is called, UpdateDiff does not schedule another update.

func (*Buffer) UpdateRules

func (b *Buffer) UpdateRules()

UpdateRules updates the syntax rules and filetype for this buffer This is called when the colorscheme changes

func (*Buffer) WordAt

func (b *Buffer) WordAt(loc Loc) []byte

WordAt returns the word around a given location in the buffer

func (*Buffer) Write

func (b *Buffer) Write(bytes []byte) (n int, err error)

type Command

type Command struct {
	StartCursor      Loc
	SearchRegex      string
	SearchAfterStart bool
}

type Cursor

type Cursor struct {
	Loc

	// Last visual x position of the cursor. Used in cursor up/down movements
	// for remembering the original x position when moving to a line that is
	// shorter than current x position.
	LastVisualX int
	// Similar to LastVisualX but takes softwrapping into account, i.e. last
	// visual x position in a visual (wrapped) line on the screen, which may be
	// different from the line in the buffer.
	LastWrappedVisualX int

	// The current selection as a range of character numbers (inclusive)
	CurSelection [2]Loc
	// The original selection as a range of character numbers
	// This is used for line and word selection where it is necessary
	// to know what the original selection was
	OrigSelection [2]Loc

	// The line number where a new trailing whitespace has been added
	// or -1 if there is no new trailing whitespace at this cursor.
	// This is used for checking if a trailing whitespace should be highlighted
	NewTrailingWsY int

	// Which cursor index is this (for multiple cursors)
	Num int
	// contains filtered or unexported fields
}

The Cursor struct stores the location of the cursor in the buffer as well as the selection

func NewCursor

func NewCursor(b *Buffer, l Loc) *Cursor

func (*Cursor) AddLineToSelection

func (c *Cursor) AddLineToSelection()

AddLineToSelection adds the current line to the selection

func (*Cursor) AddWordToSelection

func (c *Cursor) AddWordToSelection()

AddWordToSelection adds the word the cursor is currently on to the selection

func (*Cursor) Buf

func (c *Cursor) Buf() *Buffer

func (*Cursor) CopySelection

func (c *Cursor) CopySelection()

CopySelection copies the user's selection to either "primary" or "clipboard"

func (*Cursor) DeleteSelection

func (c *Cursor) DeleteSelection()

DeleteSelection deletes the currently selected text

func (*Cursor) Deselect

func (c *Cursor) Deselect(start bool)

Deselect closes the cursor's current selection Start indicates whether the cursor should be placed at the start or end of the selection

func (*Cursor) Down

func (c *Cursor) Down()

Down moves the cursor down one line (if possible)

func (*Cursor) DownN

func (c *Cursor) DownN(amount int)

DownN moves the cursor down N lines (if possible)

func (*Cursor) End

func (c *Cursor) End()

End moves the cursor to the end of the line it is on

func (*Cursor) GetCharPosInLine

func (c *Cursor) GetCharPosInLine(b []byte, visualPos int) int

GetCharPosInLine gets the char position of a visual x y coordinate (this is necessary because tabs are 1 char but 4 visual spaces)

func (*Cursor) GetSelection

func (c *Cursor) GetSelection() []byte

GetSelection returns the cursor's selection

func (*Cursor) GetSelectionLines

func (c *Cursor) GetSelectionLines() [][]byte

GetSelection returns the cursor's selection

func (*Cursor) GetVisualX

func (c *Cursor) GetVisualX(wrap bool) int

GetVisualX returns the x value of the cursor in visual spaces

func (*Cursor) Goto

func (c *Cursor) Goto(b Cursor)

Goto puts the cursor at the given cursor's location and gives the current cursor its selection too

func (*Cursor) GotoLoc

func (c *Cursor) GotoLoc(l Loc)

GotoLoc puts the cursor at the given cursor's location and gives the current cursor its selection too

func (*Cursor) HasSelection

func (c *Cursor) HasSelection() bool

HasSelection returns whether or not the user has selected anything

func (*Cursor) IsStartOfText

func (c *Cursor) IsStartOfText() bool

IsStartOfText returns whether the cursor is at the first non-whitespace rune of the line it is on

func (*Cursor) Left

func (c *Cursor) Left()

Left moves the cursor left one cell (if possible) or to the previous line if it is at the beginning

func (*Cursor) Relocate

func (c *Cursor) Relocate()

Relocate makes sure that the cursor is inside the bounds of the buffer If it isn't, it moves it to be within the buffer's lines

func (*Cursor) ResetSelection

func (c *Cursor) ResetSelection()

ResetSelection resets the user's selection

func (*Cursor) Right

func (c *Cursor) Right()

Right moves the cursor right one cell (if possible) or to the next line if it is at the end

func (*Cursor) RuneUnder

func (c *Cursor) RuneUnder(x int) rune

RuneUnder returns the rune under the given x position

func (*Cursor) SelectLine

func (c *Cursor) SelectLine()

SelectLine selects the current line

func (*Cursor) SelectTo

func (c *Cursor) SelectTo(loc Loc)

SelectTo selects from the current cursor location to the given location

func (*Cursor) SelectWord

func (c *Cursor) SelectWord()

SelectWord selects the word the cursor is currently on

func (*Cursor) SetBuf

func (c *Cursor) SetBuf(b *Buffer)

func (*Cursor) SetSelectionEnd

func (c *Cursor) SetSelectionEnd(pos Loc)

SetSelectionEnd sets the end of the selection

func (*Cursor) SetSelectionStart

func (c *Cursor) SetSelectionStart(pos Loc)

SetSelectionStart sets the start of the selection

func (*Cursor) Start

func (c *Cursor) Start()

Start moves the cursor to the start of the line it is on

func (*Cursor) StartOfText

func (c *Cursor) StartOfText()

StartOfText moves the cursor to the first non-whitespace rune of the line it is on

func (*Cursor) StoreVisualX

func (c *Cursor) StoreVisualX()

func (*Cursor) SubWordLeft

func (c *Cursor) SubWordLeft()

SubWordLeft moves the cursor one sub-word to the left

func (*Cursor) SubWordRight

func (c *Cursor) SubWordRight()

SubWordRight moves the cursor one sub-word to the right

func (*Cursor) Up

func (c *Cursor) Up()

Up moves the cursor up one line (if possible)

func (*Cursor) UpN

func (c *Cursor) UpN(amount int)

UpN moves the cursor up N lines (if possible)

func (*Cursor) WordLeft

func (c *Cursor) WordLeft()

WordLeft moves the cursor one word to the left

func (*Cursor) WordRight

func (c *Cursor) WordRight()

WordRight moves the cursor one word to the right

type Delta

type Delta struct {
	Text  []byte
	Start Loc
	End   Loc
}

A Delta is a change to the buffer

type DiffStatus

type DiffStatus byte

type Element

type Element struct {
	Value *TextEvent
	Next  *Element
}

An Element which is stored in the Stack

type EventHandler

type EventHandler struct {
	UndoStack *TEStack
	RedoStack *TEStack
	// contains filtered or unexported fields
}

EventHandler executes text manipulations and allows undoing and redoing

func NewEventHandler

func NewEventHandler(buf *SharedBuffer, cursors []*Cursor) *EventHandler

NewEventHandler returns a new EventHandler

func (*EventHandler) ApplyDiff

func (eh *EventHandler) ApplyDiff(new string)

ApplyDiff takes a string and runs the necessary insertion and deletion events to make the buffer equal to that string This means that we can transform the buffer into any string and still preserve undo/redo through insert and delete events

func (*EventHandler) DoTextEvent

func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool)

DoTextEvent runs a text event

func (*EventHandler) Execute

func (eh *EventHandler) Execute(t *TextEvent)

Execute a textevent and add it to the undo stack

func (*EventHandler) Insert

func (eh *EventHandler) Insert(start Loc, textStr string)

Insert creates an insert text event and executes it

func (*EventHandler) InsertBytes

func (eh *EventHandler) InsertBytes(start Loc, text []byte)

InsertBytes creates an insert text event and executes it

func (*EventHandler) MultipleReplace

func (eh *EventHandler) MultipleReplace(deltas []Delta)

MultipleReplace creates an multiple insertions executes them

func (*EventHandler) Redo

func (eh *EventHandler) Redo() bool

Redo the first event in the redo stack. Returns false if the stack is empty.

func (*EventHandler) RedoOneEvent

func (eh *EventHandler) RedoOneEvent()

RedoOneEvent redoes one event

func (*EventHandler) Remove

func (eh *EventHandler) Remove(start, end Loc)

Remove creates a remove text event and executes it

func (*EventHandler) Replace

func (eh *EventHandler) Replace(start, end Loc, replace string)

Replace deletes from start to end and replaces it with the given string

func (*EventHandler) Undo

func (eh *EventHandler) Undo() bool

Undo the first event in the undo stack. Returns false if the stack is empty.

func (*EventHandler) UndoOneEvent

func (eh *EventHandler) UndoOneEvent()

UndoOneEvent undoes one event

func (*EventHandler) UndoTextEvent

func (eh *EventHandler) UndoTextEvent(t *TextEvent)

UndoTextEvent undoes a text event

type FileFormat

type FileFormat byte

type Line

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

A Line contains the data in bytes as well as a highlight state, match and a flag for whether the highlighting needs to be updated

func Append

func Append(slice []Line, data ...Line) []Line

Append efficiently appends lines together It allocates an additional 10000 lines if the original estimate is incorrect

type LineArray

type LineArray struct {
	Endings FileFormat
	// contains filtered or unexported fields
}

A LineArray simply stores and array of lines and makes it easy to insert and delete in it

func NewLineArray

func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray

NewLineArray returns a new line array from an array of bytes

func (*LineArray) Bytes

func (la *LineArray) Bytes() []byte

Bytes returns the string that should be written to disk when the line array is saved

func (*LineArray) End

func (la *LineArray) End() Loc

End returns the location of the last character in the buffer

func (*LineArray) LineBytes

func (la *LineArray) LineBytes(lineN int) []byte

LineBytes returns line n as an array of bytes

func (*LineArray) LinesNum

func (la *LineArray) LinesNum() int

LinesNum returns the number of lines in the buffer

func (*LineArray) Lock

func (la *LineArray) Lock()

Locks the whole LineArray

func (*LineArray) Match

func (la *LineArray) Match(lineN int) highlight.LineMatch

Match retrieves the match for the given line number

func (*LineArray) SearchMatch

func (la *LineArray) SearchMatch(b *Buffer, pos Loc) bool

SearchMatch returns true if the location `pos` is within a match of the last search for the buffer `b`. It is used for efficient highlighting of search matches (separately from the syntax highlighting). SearchMatch searches for the matches if it is called first time for the given line or if the line was modified. Otherwise the previously found matches are used.

The buffer `b` needs to be passed because the line array may be shared between multiple buffers (multiple instances of the same file opened in different edit panes) which have distinct searches, so SearchMatch needs to know which search to match against.

func (*LineArray) SetMatch

func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch)

SetMatch sets the match at the given line number

func (*LineArray) SetState

func (la *LineArray) SetState(lineN int, s highlight.State)

SetState sets the highlight state at the given line number

func (*LineArray) Start

func (la *LineArray) Start() Loc

Start returns the start of the buffer

func (*LineArray) State

func (la *LineArray) State(lineN int) highlight.State

State gets the highlight state for the given line number

func (*LineArray) Substr

func (la *LineArray) Substr(start, end Loc) []byte

Substr returns the string representation between two locations

func (*LineArray) SubstrLines

func (la *LineArray) SubstrLines(start, end Loc) [][]byte

SubstrLines returns an array of bytes between between two locations.

func (*LineArray) Unlock

func (la *LineArray) Unlock()

Unlocks the whole LineArray

type Loc

type Loc struct {
	X, Y int
}

Loc stores a location

func ParseCursorLocation

func ParseCursorLocation(cursorPositions []string) (Loc, error)

ParseCursorLocation turns a cursor location like 10:5 (LINE:COL) into a loc

func (Loc) Clamp

func (l Loc) Clamp(start, end Loc) Loc

Clamp clamps a loc between start and end

func (Loc) Diff

func (l Loc) Diff(b Loc, buf *Buffer) int

Diff returns the difference between two locs

func (Loc) GreaterEqual

func (l Loc) GreaterEqual(b Loc) bool

GreaterEqual returns true if b is greater than or equal to b

func (Loc) GreaterThan

func (l Loc) GreaterThan(b Loc) bool

GreaterThan returns true if b is bigger

func (Loc) LessEqual

func (l Loc) LessEqual(b Loc) bool

LessEqual returns true if b is less than or equal to b

func (Loc) LessThan

func (l Loc) LessThan(b Loc) bool

LessThan returns true if b is smaller

func (Loc) Move

func (l Loc) Move(n int, buf *Buffer) Loc

Move moves a loc n characters

func (Loc) MoveLA

func (l Loc) MoveLA(n int, buf *LineArray) Loc

MoveLA moves the cursor n characters to the left or right It moves the cursor left if n is negative

type SharedBuffer

type SharedBuffer struct {
	*LineArray

	Path string

	Settings map[string]any

	Suggestions   []string
	Completions   []string
	CurSuggestion int

	// Whether or not suggestions can be autocompleted must be shared because
	// it changes based on how the buffer has changed
	HasSuggestions bool

	// The Highlighter struct actually performs the highlighting
	Highlighter *highlight.Highlighter
	// SyntaxDef represents the syntax highlighting definition being used
	// This stores the highlighting rules and filetype detection info
	SyntaxDef *highlight.Def

	ModifiedThisFrame bool
	// contains filtered or unexported fields
}

SharedBuffer is a struct containing info that is shared among buffers that have the same file open

func (*SharedBuffer) ClearModified

func (b *SharedBuffer) ClearModified()

func (*SharedBuffer) MarkModified

func (b *SharedBuffer) MarkModified(start, end int)

MarkModified marks the buffer as modified for this frame and performs rehighlighting if syntax highlighting is enabled

func (*SharedBuffer) RegisterRedrawCallback

func (b *SharedBuffer) RegisterRedrawCallback(callback func())

type TEStack

type TEStack struct {
	Top  *Element
	Size int
}

TEStack is a simple implementation of a LIFO stack for text events

func (*TEStack) Len

func (s *TEStack) Len() int

Len returns the stack's length

func (*TEStack) Peek

func (s *TEStack) Peek() *TextEvent

Peek returns the top element of the stack without removing it

func (*TEStack) Pop

func (s *TEStack) Pop() (value *TextEvent)

Pop removes the top element from the stack and returns its value If the stack is empty, return nil

func (*TEStack) Push

func (s *TEStack) Push(value *TextEvent)

Push a new element onto the stack

type TextEvent

type TextEvent struct {
	C Cursor

	EventType int
	Deltas    []Delta
	Time      time.Time
}

TextEvent holds data for a manipulation on some text that can be undone

Jump to

Keyboard shortcuts

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