pageindex

package
v0.2.5 Latest Latest
Warning

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

Go to latest
Published: Jun 17, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidChunkSize   = errors.New("invalid chunk size: must be positive")
	ErrInvalidOverlap     = errors.New("invalid overlap: cannot be negative")
	ErrOverlapExceedsSize = errors.New("overlap cannot exceed chunk size")
	ErrReaderClosed       = errors.New("reader is closed")
)

常见错误

Functions

func DetectDocType

func DetectDocType(source string) models.DocumentType

DetectDocType 根据文件路径检测文档类型

Types

type BatchConfig

type BatchConfig struct {
	Workers     int  // 并发worker数
	BatchSize   int  // 每批次处理数量
	StopOnError bool // 遇到错误是否停止
}

BatchConfig 批量索引配置

func DefaultBatchConfig

func DefaultBatchConfig() *BatchConfig

DefaultBatchConfig 默认批量配置

type BatchError

type BatchError struct {
	Index  int    // 文档索引位置
	DocID  string // 文档ID(如果有)
	Source string // 文档来源
	Error  error  // 错误信息
}

BatchError 批量索引错误

type BatchIndexer

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

BatchIndexer 批量索引器

func NewBatchIndexer

func NewBatchIndexer(engine PageIndex, workers, batchSize int) (*BatchIndexer, error)

NewBatchIndexer 创建批量索引器

func NewBatchIndexerWithConfig

func NewBatchIndexerWithConfig(engine PageIndex, config *BatchConfig) (*BatchIndexer, error)

NewBatchIndexerWithConfig 使用配置创建批量索引器

func (*BatchIndexer) GetConfig

func (b *BatchIndexer) GetConfig() *BatchConfig

GetConfig 获取当前配置

func (*BatchIndexer) GetStats

func (b *BatchIndexer) GetStats() Stats

GetStats 获取统计信息(当前实现返回配置信息)

func (*BatchIndexer) IndexDocuments

func (b *BatchIndexer) IndexDocuments(ctx context.Context, docs []*models.Document, progressCallback ProgressCallback) (*BatchResult, error)

IndexDocuments 并发批量索引文档

func (*BatchIndexer) IndexDocumentsBatch

func (b *BatchIndexer) IndexDocumentsBatch(ctx context.Context, docs []*models.Document, progressCallback ProgressCallback) (*BatchResult, error)

IndexDocumentsBatch 按批次索引文档(分批处理,每批等待完成后再处理下一批)

func (*BatchIndexer) SetStopOnError

func (b *BatchIndexer) SetStopOnError(stop bool)

SetStopOnError 设置遇错停止标志

type BatchResult

type BatchResult struct {
	Total      int           // 总文档数
	Success    int           // 成功数
	Failed     int           // 失败数
	Errors     []BatchError  // 错误列表
	Duration   time.Duration // 总耗时
	IndexedIDs []string      // 成功索引的文档ID
}

BatchResult 批量索引结果

type BufferedStreamChunker

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

BufferedStreamChunker 带缓冲的流式分块器 支持预读取和回退操作

func NewBufferedStreamChunker

func NewBufferedStreamChunker(r io.Reader, config *StreamChunkerConfig) *BufferedStreamChunker

NewBufferedStreamChunker 创建带缓冲的流式分块器

func (*BufferedStreamChunker) CollectAll

func (s *BufferedStreamChunker) CollectAll() ([]*models.Chunk, error)

CollectAll 收集所有分块

func (*BufferedStreamChunker) NextChunk

func (s *BufferedStreamChunker) NextChunk() (*models.Chunk, error)

NextChunk 返回下一个分块

func (*BufferedStreamChunker) Peek

func (s *BufferedStreamChunker) Peek(n int) (string, error)

Peek 预览接下来的数据(不移动读取位置)

type CSVParser

type CSVParser struct{}

CSVParser CSV解析器

func NewCSVParser

func NewCSVParser() *CSVParser

NewCSVParser 创建CSV解析器

func (*CSVParser) Parse

func (p *CSVParser) Parse(reader io.Reader, source string) (*models.Document, error)

Parse 解析CSV

func (*CSVParser) Supports

func (p *CSVParser) Supports(docType models.DocumentType) bool

Supports 是否支持该类型

type Chunker

type Chunker interface {
	Chunk(content string) ([]*models.Chunk, error)
}

Chunker 分块器接口

type Config

type Config struct {
	ChunkSize     int
	ChunkOverlap  int
	ChunkStrategy string // "fixed", "recursive", "semantic"
}

Config PageIndex配置

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig 默认配置

type DefaultStreamReader

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

DefaultStreamReader 默认流式读取器实现

func NewStreamReader

func NewStreamReader(r io.Reader, chunkSize int) *DefaultStreamReader

NewStreamReader 创建默认流式读取器

func (*DefaultStreamReader) Close

func (r *DefaultStreamReader) Close() error

Close 关闭读取器

func (*DefaultStreamReader) ReadChunk

func (r *DefaultStreamReader) ReadChunk() ([]byte, error)

ReadChunk 读取一块数据

func (*DefaultStreamReader) Reset

func (r *DefaultStreamReader) Reset(reader io.Reader) error

Reset 重置读取器

type DocumentTask

type DocumentTask struct {
	Index int              // 文档在原始切片中的索引
	Doc   *models.Document // 文档对象
}

DocumentTask 文档索引任务

type FixedSizeChunker

type FixedSizeChunker struct {
	ChunkSize    int // 每个分块的目标字符数
	ChunkOverlap int // 分块之间的重叠字符数
}

FixedSizeChunker 固定大小分块器

func NewFixedSizeChunker

func NewFixedSizeChunker(chunkSize, chunkOverlap int) *FixedSizeChunker

NewFixedSizeChunker 创建固定大小分块器

func (*FixedSizeChunker) Chunk

func (c *FixedSizeChunker) Chunk(content string) ([]*models.Chunk, error)

Chunk 执行分块

type HTMLParser

type HTMLParser struct{}

HTMLParser HTML解析器(简化版)

func NewHTMLParser

func NewHTMLParser() *HTMLParser

NewHTMLParser 创建HTML解析器

func (*HTMLParser) Parse

func (p *HTMLParser) Parse(reader io.Reader, source string) (*models.Document, error)

Parse 解析HTML

func (*HTMLParser) Supports

func (p *HTMLParser) Supports(docType models.DocumentType) bool

Supports 是否支持该类型

type JSONParser

type JSONParser struct{}

JSONParser JSON解析器

func NewJSONParser

func NewJSONParser() *JSONParser

NewJSONParser 创建JSON解析器

func (*JSONParser) Parse

func (p *JSONParser) Parse(reader io.Reader, source string) (*models.Document, error)

Parse 解析JSON

func (*JSONParser) Supports

func (p *JSONParser) Supports(docType models.DocumentType) bool

Supports 是否支持该类型

type LineStreamChunker

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

LineStreamChunker 按行流式分块器 适用于需要按行边界分割的场景

func NewLineStreamChunker

func NewLineStreamChunker(r io.Reader, config *StreamChunkerConfig) *LineStreamChunker

NewLineStreamChunker 创建按行流式分块器

func (*LineStreamChunker) CollectAll

func (s *LineStreamChunker) CollectAll() ([]*models.Chunk, error)

CollectAll 收集所有分块

func (*LineStreamChunker) NextChunk

func (s *LineStreamChunker) NextChunk() (*models.Chunk, error)

NextChunk 返回下一个分块(按行边界分割)

type MarkdownParser

type MarkdownParser struct{}

MarkdownParser Markdown解析器

func NewMarkdownParser

func NewMarkdownParser() *MarkdownParser

NewMarkdownParser 创建Markdown解析器

func (*MarkdownParser) Parse

func (p *MarkdownParser) Parse(reader io.Reader, source string) (*models.Document, error)

Parse 解析Markdown

func (*MarkdownParser) Supports

func (p *MarkdownParser) Supports(docType models.DocumentType) bool

Supports 是否支持该类型

type MemoryStore

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

MemoryStore 内存存储实现(用于测试)

func NewMemoryStore

func NewMemoryStore() *MemoryStore

NewMemoryStore 创建内存存储

func (*MemoryStore) CreateChunks

func (s *MemoryStore) CreateChunks(ctx context.Context, chunks []*models.Chunk) error

CreateChunks 创建分块

func (*MemoryStore) CreateDocument

func (s *MemoryStore) CreateDocument(ctx context.Context, doc *models.Document) error

CreateDocument 创建文档

func (*MemoryStore) DeleteChunksByDocument

func (s *MemoryStore) DeleteChunksByDocument(ctx context.Context, docID string) error

DeleteChunksByDocument 删除文档的分块

func (*MemoryStore) DeleteDocument

func (s *MemoryStore) DeleteDocument(ctx context.Context, id string) error

DeleteDocument 删除文档

func (*MemoryStore) GetChunksByDocument

func (s *MemoryStore) GetChunksByDocument(ctx context.Context, docID string) ([]*models.Chunk, error)

GetChunksByDocument 获取文档的分块

func (*MemoryStore) GetDocument

func (s *MemoryStore) GetDocument(ctx context.Context, id string) (*models.Document, error)

GetDocument 获取文档

func (*MemoryStore) ListDocuments

func (s *MemoryStore) ListDocuments(ctx context.Context, offset, limit int) ([]*models.Document, error)

ListDocuments 列出文档

func (*MemoryStore) Search

func (s *MemoryStore) Search(ctx context.Context, query string, opts models.SearchOptions) (*models.SearchResult, error)

Search 搜索

func (*MemoryStore) UpdateDocument

func (s *MemoryStore) UpdateDocument(ctx context.Context, doc *models.Document) error

UpdateDocument 更新文档

type PDFParser

type PDFParser struct{}

PDFParser PDF文档解析器

func NewPDFParser

func NewPDFParser() *PDFParser

NewPDFParser 创建PDF解析器

func (*PDFParser) Parse

func (p *PDFParser) Parse(reader io.Reader, source string) (*models.Document, error)

Parse 解析PDF文档

func (*PDFParser) SupportedExtensions

func (p *PDFParser) SupportedExtensions() []string

SupportedExtensions 返回支持的文件扩展名

func (*PDFParser) Supports

func (p *PDFParser) Supports(docType models.DocumentType) bool

Supports 是否支持该类型

type PageIndex

type PageIndex interface {
	// 添加文档
	AddDocument(ctx context.Context, doc *models.Document, content io.Reader) error

	// 批量添加文档
	BatchAddDocuments(ctx context.Context, docs []*models.Document) error

	// 更新文档
	UpdateDocument(ctx context.Context, docID string, content io.Reader) error

	// 删除文档
	DeleteDocument(ctx context.Context, docID string) error

	// 获取文档
	GetDocument(ctx context.Context, docID string) (*models.Document, error)

	// 列出文档
	ListDocuments(ctx context.Context, offset, limit int) ([]*models.Document, error)

	// 搜索文档
	Search(ctx context.Context, query string, opts models.SearchOptions) (*models.SearchResult, error)

	// 获取文档分块
	GetChunks(ctx context.Context, docID string) ([]*models.Chunk, error)
}

PageIndex 页面索引接口

func NewPageIndex

func NewPageIndex(store Store, config *Config) PageIndex

NewPageIndex 创建PageIndex实例

type ParseResult

type ParseResult struct {
	Title    string
	Content  string
	Metadata map[string]interface{}
}

ParseResult 解析结果

type Parser

type Parser interface {
	Parse(reader io.Reader, source string) (*models.Document, error)
	Supports(docType models.DocumentType) bool
}

Parser 文档解析器接口

type ParserManager

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

ParserManager 解析器管理器

func NewParserManager

func NewParserManager() *ParserManager

NewParserManager 创建解析器管理器

func (*ParserManager) GetParser

func (pm *ParserManager) GetParser(docType models.DocumentType) (Parser, error)

GetParser 获取解析器

func (*ParserManager) GetParserByExtension

func (pm *ParserManager) GetParserByExtension(ext string) (Parser, models.DocumentType, error)

GetParserByExtension 根据文件扩展名获取解析器

func (*ParserManager) Parse

func (pm *ParserManager) Parse(reader io.Reader, source string, docType models.DocumentType) (*models.Document, error)

Parse 解析文档

func (*ParserManager) ParseByExtension

func (pm *ParserManager) ParseByExtension(reader io.Reader, source string) (*models.Document, error)

ParseByExtension 根据扩展名解析文档

func (*ParserManager) Register

func (pm *ParserManager) Register(docType models.DocumentType, parser Parser)

Register 注册解析器

func (*ParserManager) RegisterWithExtensions

func (pm *ParserManager) RegisterWithExtensions(docType models.DocumentType, parser Parser, extensions []string)

RegisterWithExtensions 注册解析器并指定支持的扩展名

func (*ParserManager) SupportedDocTypes

func (pm *ParserManager) SupportedDocTypes() []models.DocumentType

SupportedDocTypes 返回所有支持的文档类型

func (*ParserManager) SupportedExtensions

func (pm *ParserManager) SupportedExtensions() []string

SupportedExtensions 返回所有支持的文件扩展名

type PostgreSQLStore

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

PostgreSQLStore PostgreSQL存储实现

func NewPostgreSQLStore

func NewPostgreSQLStore(db *sql.DB) *PostgreSQLStore

NewPostgreSQLStore 创建PostgreSQL存储

func (*PostgreSQLStore) CreateChunks

func (s *PostgreSQLStore) CreateChunks(ctx context.Context, chunks []*models.Chunk) error

CreateChunks 创建分块

func (*PostgreSQLStore) CreateDocument

func (s *PostgreSQLStore) CreateDocument(ctx context.Context, doc *models.Document) error

CreateDocument 创建文档

func (*PostgreSQLStore) DeleteChunksByDocument

func (s *PostgreSQLStore) DeleteChunksByDocument(ctx context.Context, docID string) error

DeleteChunksByDocument 删除文档的分块

func (*PostgreSQLStore) DeleteDocument

func (s *PostgreSQLStore) DeleteDocument(ctx context.Context, id string) error

DeleteDocument 删除文档

func (*PostgreSQLStore) GetChunksByDocument

func (s *PostgreSQLStore) GetChunksByDocument(ctx context.Context, docID string) ([]*models.Chunk, error)

GetChunksByDocument 获取文档的分块

func (*PostgreSQLStore) GetDocument

func (s *PostgreSQLStore) GetDocument(ctx context.Context, id string) (*models.Document, error)

GetDocument 获取文档

func (*PostgreSQLStore) InitSchema

func (s *PostgreSQLStore) InitSchema(ctx context.Context) error

InitSchema 初始化数据库表结构

func (*PostgreSQLStore) ListDocuments

func (s *PostgreSQLStore) ListDocuments(ctx context.Context, offset, limit int) ([]*models.Document, error)

ListDocuments 列出文档

func (*PostgreSQLStore) Search

Search 简单搜索(基于LIKE)

func (*PostgreSQLStore) UpdateDocument

func (s *PostgreSQLStore) UpdateDocument(ctx context.Context, doc *models.Document) error

UpdateDocument 更新文档

type ProgressCallback

type ProgressCallback func(info ProgressInfo)

ProgressCallback 进度回调函数类型

type ProgressInfo

type ProgressInfo struct {
	Total     int           // 总数
	Completed int           // 已完成数
	Success   int           // 成功数
	Failed    int           // 失败数
	Current   string        // 当前处理的文档来源
	Duration  time.Duration // 已耗时
}

ProgressInfo 进度信息

type RecursiveChunker

type RecursiveChunker struct {
	Separators   []string // 分隔符优先级列表
	ChunkSize    int
	ChunkOverlap int
}

RecursiveChunker 递归分块器

func NewRecursiveChunker

func NewRecursiveChunker(chunkSize, chunkOverlap int) *RecursiveChunker

NewRecursiveChunker 创建递归分块器

func (*RecursiveChunker) Chunk

func (c *RecursiveChunker) Chunk(content string) ([]*models.Chunk, error)

Chunk 执行递归分块

type SQLiteStore

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

SQLiteStore SQLite存储实现 适用于本地单机部署,无需外部数据库服务

func NewSQLiteStore

func NewSQLiteStore(config SQLiteStoreConfig) (*SQLiteStore, error)

NewSQLiteStore 创建SQLite存储

func (*SQLiteStore) Close

func (s *SQLiteStore) Close() error

Close 关闭数据库连接

func (*SQLiteStore) CreateChunks

func (s *SQLiteStore) CreateChunks(ctx context.Context, chunks []*models.Chunk) error

CreateChunks 创建分块

func (*SQLiteStore) CreateDocument

func (s *SQLiteStore) CreateDocument(ctx context.Context, doc *models.Document) error

CreateDocument 创建文档

func (*SQLiteStore) DB

func (s *SQLiteStore) DB() *sql.DB

DB 返回底层*sql.DB(供其他模块复用同一连接)

func (*SQLiteStore) DeleteChunksByDocument

func (s *SQLiteStore) DeleteChunksByDocument(ctx context.Context, docID string) error

DeleteChunksByDocument 删除文档的分块

func (*SQLiteStore) DeleteDocument

func (s *SQLiteStore) DeleteDocument(ctx context.Context, id string) error

DeleteDocument 删除文档

func (*SQLiteStore) GetChunksByDocument

func (s *SQLiteStore) GetChunksByDocument(ctx context.Context, docID string) ([]*models.Chunk, error)

GetChunksByDocument 获取文档的分块

func (*SQLiteStore) GetDocument

func (s *SQLiteStore) GetDocument(ctx context.Context, id string) (*models.Document, error)

GetDocument 获取文档

func (*SQLiteStore) InitSchema

func (s *SQLiteStore) InitSchema(ctx context.Context) error

InitSchema 初始化数据库表结构

func (*SQLiteStore) ListDocuments

func (s *SQLiteStore) ListDocuments(ctx context.Context, offset, limit int) ([]*models.Document, error)

ListDocuments 列出文档

func (*SQLiteStore) Search

func (s *SQLiteStore) Search(ctx context.Context, query string, opts models.SearchOptions) (*models.SearchResult, error)

Search 搜索(SQLite的LIKE默认对ASCII不区分大小写)

func (*SQLiteStore) UpdateDocument

func (s *SQLiteStore) UpdateDocument(ctx context.Context, doc *models.Document) error

UpdateDocument 更新文档

type SQLiteStoreConfig

type SQLiteStoreConfig struct {
	DBPath string // 数据库文件路径,如 "rag.db"、":memory:"(内存模式)
}

SQLiteStoreConfig SQLite存储配置

type SemanticChunker

type SemanticChunker struct {
	MaxChunkSize int
}

SemanticChunker 语义分块器(简化版)

func NewSemanticChunker

func NewSemanticChunker(maxChunkSize int) *SemanticChunker

NewSemanticChunker 创建语义分块器

func (*SemanticChunker) Chunk

func (c *SemanticChunker) Chunk(content string) ([]*models.Chunk, error)

Chunk 执行语义分块(基于段落和句子边界)

type Stats

type Stats struct {
	Workers       int
	BatchSize     int
	ActiveJobs    int
	CompletedJobs int
}

Stats 统计信息

type Store

type Store interface {
	// 文档操作
	CreateDocument(ctx context.Context, doc *models.Document) error
	GetDocument(ctx context.Context, id string) (*models.Document, error)
	UpdateDocument(ctx context.Context, doc *models.Document) error
	DeleteDocument(ctx context.Context, id string) error
	ListDocuments(ctx context.Context, offset, limit int) ([]*models.Document, error)

	// 分块操作
	CreateChunks(ctx context.Context, chunks []*models.Chunk) error
	GetChunksByDocument(ctx context.Context, docID string) ([]*models.Chunk, error)
	DeleteChunksByDocument(ctx context.Context, docID string) error

	// 搜索
	Search(ctx context.Context, query string, opts models.SearchOptions) (*models.SearchResult, error)
}

Store 文档存储接口

type StreamChunker

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

StreamChunker 流式分块器

func NewStreamChunker

func NewStreamChunker(r io.Reader, config *StreamChunkerConfig) *StreamChunker

NewStreamChunker 从 io.Reader 创建流式分块器

func (*StreamChunker) ChunkIndex

func (s *StreamChunker) ChunkIndex() int

ChunkIndex 返回当前分块索引

func (*StreamChunker) CollectAll

func (s *StreamChunker) CollectAll() ([]*models.Chunk, error)

CollectAll 收集所有分块

func (*StreamChunker) NextChunk

func (s *StreamChunker) NextChunk() (*models.Chunk, error)

NextChunk 返回下一个分块 返回 nil, io.EOF 表示所有分块已读取完毕

func (*StreamChunker) NextChunkWithCallback

func (s *StreamChunker) NextChunkWithCallback(callback func(*models.Chunk) error) error

NextChunkWithCallback 读取下一个分块并执行回调

func (*StreamChunker) ProcessAll

func (s *StreamChunker) ProcessAll(callback func(*models.Chunk) error) error

ProcessAll 处理所有分块

func (*StreamChunker) Reset

func (s *StreamChunker) Reset(r io.Reader)

Reset 重置分块器,可以重新从头开始

func (*StreamChunker) TotalRead

func (s *StreamChunker) TotalRead() int

TotalRead 返回已读取的总字符数

type StreamChunkerBuilder

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

StreamChunkerBuilder 流式分块器构建器

func NewStreamChunkerBuilder

func NewStreamChunkerBuilder(r io.Reader) *StreamChunkerBuilder

NewStreamChunkerBuilder 创建流式分块器构建器

func (*StreamChunkerBuilder) Build

func (b *StreamChunkerBuilder) Build() interface {
	NextChunk() (*models.Chunk, error)
}

Build 构建流式分块器

func (*StreamChunkerBuilder) WithBufferSize

func (b *StreamChunkerBuilder) WithBufferSize(size int) *StreamChunkerBuilder

WithBufferSize 设置缓冲区大小

func (*StreamChunkerBuilder) WithChunkSize

func (b *StreamChunkerBuilder) WithChunkSize(size int) *StreamChunkerBuilder

WithChunkSize 设置分块大小

func (*StreamChunkerBuilder) WithMode

WithMode 设置分块模式

func (*StreamChunkerBuilder) WithOverlap

func (b *StreamChunkerBuilder) WithOverlap(overlap int) *StreamChunkerBuilder

WithOverlap 设置重叠大小

type StreamChunkerConfig

type StreamChunkerConfig struct {
	ChunkSize    int // 每个分块的目标字符数
	ChunkOverlap int // 分块之间的重叠字符数
	BufferSize   int // 读取缓冲区大小(字节)
}

StreamChunker 流式分块器配置

func DefaultStreamChunkerConfig

func DefaultStreamChunkerConfig() *StreamChunkerConfig

DefaultStreamChunkerConfig 默认流式分块器配置

type StreamReader

type StreamReader interface {
	// ReadChunk 读取一块数据
	// 返回 io.EOF 表示读取完毕
	ReadChunk() ([]byte, error)

	// Reset 重置读取器,可以重新从头读取
	Reset(r io.Reader) error

	// Close 关闭读取器
	Close() error
}

StreamReader 流式读取器接口,用于逐块读取大文件

type TextParser

type TextParser struct{}

TextParser 文本解析器

func NewTextParser

func NewTextParser() *TextParser

NewTextParser 创建文本解析器

func (*TextParser) Parse

func (p *TextParser) Parse(reader io.Reader, source string) (*models.Document, error)

Parse 解析文本

func (*TextParser) Supports

func (p *TextParser) Supports(docType models.DocumentType) bool

Supports 是否支持该类型

type WordParser

type WordParser struct{}

WordParser Word文档解析器

func NewWordParser

func NewWordParser() *WordParser

NewWordParser 创建Word解析器

func (*WordParser) Parse

func (p *WordParser) Parse(reader io.Reader, source string) (*models.Document, error)

Parse 解析Word文档

func (*WordParser) SupportedExtensions

func (p *WordParser) SupportedExtensions() []string

SupportedExtensions 返回支持的文件扩展名

func (*WordParser) Supports

func (p *WordParser) Supports(docType models.DocumentType) bool

Supports 是否支持该类型

Jump to

Keyboard shortcuts

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