Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87d21ebf2b | ||
|
|
a524fc545c | ||
|
|
4316edaf43 | ||
|
|
d845924e8b | ||
|
|
a29b322bdd | ||
|
|
9723ffa7a6 | ||
|
|
f06cd88773 | ||
|
|
9af92b6914 | ||
|
|
8f64c4b312 | ||
|
|
a32877e908 | ||
|
|
6465c9c44a |
@@ -152,6 +152,7 @@ if (!snapshot) {
|
|||||||
for (const pkg of ["opencode", "opencode-bin"]) {
|
for (const pkg of ["opencode", "opencode-bin"]) {
|
||||||
await $`rm -rf ./dist/aur-${pkg}`
|
await $`rm -rf ./dist/aur-${pkg}`
|
||||||
await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
|
await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
|
||||||
|
await $`cd ./dist/aur-${pkg} && git checkout master`
|
||||||
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild.replace("${pkg}", pkg))
|
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild.replace("${pkg}", pkg))
|
||||||
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
|
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
|
||||||
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
|
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import path from "path"
|
|||||||
|
|
||||||
export const AuthCopilot = lazy(async () => {
|
export const AuthCopilot = lazy(async () => {
|
||||||
const file = Bun.file(path.join(Global.Path.state, "plugin", "copilot.ts"))
|
const file = Bun.file(path.join(Global.Path.state, "plugin", "copilot.ts"))
|
||||||
|
const exists = await file.exists()
|
||||||
const response = fetch("https://raw.githubusercontent.com/sst/opencode-github-copilot/refs/heads/main/auth.ts")
|
const response = fetch("https://raw.githubusercontent.com/sst/opencode-github-copilot/refs/heads/main/auth.ts")
|
||||||
.then((x) => Bun.write(file, x))
|
.then((x) => Bun.write(file, x))
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
|
|
||||||
if (!file.exists()) {
|
if (!exists) {
|
||||||
const worked = await response
|
const worked = await response
|
||||||
if (!worked) return
|
if (!worked) return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,13 @@ export namespace ConfigHooks {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Bus.subscribe(Session.Event.Idle, async () => {
|
Bus.subscribe(Session.Event.Idle, async (payload) => {
|
||||||
const cfg = await Config.get()
|
const cfg = await Config.get()
|
||||||
if (cfg.experimental?.hook?.session_completed) {
|
if (cfg.experimental?.hook?.session_completed) {
|
||||||
|
const session = await Session.get(payload.properties.sessionID)
|
||||||
|
// Only fire hook for top-level sessions (not subagent sessions)
|
||||||
|
if (session.parentID) return
|
||||||
|
|
||||||
for (const item of cfg.experimental.hook.session_completed) {
|
for (const item of cfg.experimental.hook.session_completed) {
|
||||||
log.info("session_completed", {
|
log.info("session_completed", {
|
||||||
command: item.command,
|
command: item.command,
|
||||||
|
|||||||
@@ -13,22 +13,15 @@ export namespace ProviderTransform {
|
|||||||
anthropic: {
|
anthropic: {
|
||||||
cacheControl: { type: "ephemeral" },
|
cacheControl: { type: "ephemeral" },
|
||||||
},
|
},
|
||||||
openaiCompatible: {
|
openrouter: {
|
||||||
cache_control: { type: "ephemeral" },
|
cache_control: { type: "ephemeral" },
|
||||||
},
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (providerID === "amazon-bedrock" || modelID.includes("anthropic")) {
|
|
||||||
const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
|
|
||||||
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
|
|
||||||
|
|
||||||
for (const msg of unique([...system, ...final])) {
|
|
||||||
msg.providerOptions = {
|
|
||||||
...msg.providerOptions,
|
|
||||||
bedrock: {
|
bedrock: {
|
||||||
cachePoint: { type: "ephemeral" },
|
cachePoint: { type: "ephemeral" },
|
||||||
},
|
},
|
||||||
|
openaiCompatible: {
|
||||||
|
cache_control: { type: "ephemeral" },
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,6 +325,7 @@ export namespace Session {
|
|||||||
providerID: z.string(),
|
providerID: z.string(),
|
||||||
modelID: z.string(),
|
modelID: z.string(),
|
||||||
mode: z.string().optional(),
|
mode: z.string().optional(),
|
||||||
|
tools: z.record(z.boolean()).optional(),
|
||||||
parts: z.array(
|
parts: z.array(
|
||||||
z.discriminatedUnion("type", [
|
z.discriminatedUnion("type", [
|
||||||
MessageV2.TextPart.omit({
|
MessageV2.TextPart.omit({
|
||||||
@@ -618,6 +619,7 @@ export namespace Session {
|
|||||||
|
|
||||||
for (const item of await Provider.tools(input.providerID)) {
|
for (const item of await Provider.tools(input.providerID)) {
|
||||||
if (mode.tools[item.id] === false) continue
|
if (mode.tools[item.id] === false) continue
|
||||||
|
if (input.tools?.[item.id] === false) continue
|
||||||
if (session.parentID && item.id === "task") continue
|
if (session.parentID && item.id === "task") continue
|
||||||
tools[item.id] = tool({
|
tools[item.id] = tool({
|
||||||
id: item.id as any,
|
id: item.id as any,
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ export const TaskTool = Tool.define({
|
|||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
modelID: msg.modelID,
|
modelID: msg.modelID,
|
||||||
providerID: msg.providerID,
|
providerID: msg.providerID,
|
||||||
|
tools: {
|
||||||
|
todoread: false,
|
||||||
|
todowrite: false,
|
||||||
|
},
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
id: Identifier.ascending("part"),
|
id: Identifier.ascending("part"),
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ func main() {
|
|||||||
stream := httpClient.Event.ListStreaming(ctx)
|
stream := httpClient.Event.ListStreaming(ctx)
|
||||||
for stream.Next() {
|
for stream.Next() {
|
||||||
evt := stream.Current().AsUnion()
|
evt := stream.Current().AsUnion()
|
||||||
|
if _, ok := evt.(opencode.EventListResponseEventStorageWrite); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
program.Send(evt)
|
program.Send(evt)
|
||||||
}
|
}
|
||||||
if err := stream.Err(); err != nil {
|
if err := stream.Err(); err != nil {
|
||||||
|
|||||||
@@ -196,8 +196,6 @@ func renderText(
|
|||||||
case opencode.UserMessage:
|
case opencode.UserMessage:
|
||||||
ts = time.UnixMilli(int64(casted.Time.Created))
|
ts = time.UnixMilli(int64(casted.Time.Created))
|
||||||
base := styles.NewStyle().Foreground(t.Text()).Background(backgroundColor)
|
base := styles.NewStyle().Foreground(t.Text()).Background(backgroundColor)
|
||||||
|
|
||||||
// Process @ mentions and styling with hyphen preservation
|
|
||||||
words := strings.Fields(text)
|
words := strings.Fields(text)
|
||||||
for i, word := range words {
|
for i, word := range words {
|
||||||
if strings.HasPrefix(word, "@") {
|
if strings.HasPrefix(word, "@") {
|
||||||
@@ -206,14 +204,9 @@ func renderText(
|
|||||||
words[i] = base.Render(word + " ")
|
words[i] = base.Render(word + " ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
styledText := strings.Join(words, "")
|
text = strings.Join(words, "")
|
||||||
|
text = ansi.WordwrapWc(text, width-6, " -")
|
||||||
// Apply word wrapping with hyphen preservation
|
content = base.Width(width - 6).Render(text)
|
||||||
frameSize := util.GetMessageContainerFrame()
|
|
||||||
wrappedText := util.ProcessTextWithHyphens(styledText, func(t string) string {
|
|
||||||
return ansi.WordwrapWc(t, width-frameSize, " ")
|
|
||||||
})
|
|
||||||
content = base.Width(width - frameSize).Render(wrappedText)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp := ts.
|
timestamp := ts.
|
||||||
@@ -715,4 +708,5 @@ func renderDiagnostics(
|
|||||||
// if !ok {
|
// if !ok {
|
||||||
// return ""
|
// return ""
|
||||||
// }
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ func (m *messagesComponent) Init() tea.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
measure := util.Measure("messages.Update")
|
||||||
|
defer measure("from", fmt.Sprintf("%T", msg))
|
||||||
var cmds []tea.Cmd
|
var cmds []tea.Cmd
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.WindowSizeMsg:
|
case tea.WindowSizeMsg:
|
||||||
@@ -102,9 +104,7 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.loading = false
|
m.loading = false
|
||||||
m.tail = m.viewport.AtBottom()
|
m.tail = m.viewport.AtBottom()
|
||||||
m.viewport = msg.viewport
|
m.viewport = msg.viewport
|
||||||
if m.tail {
|
m.header = msg.header
|
||||||
m.viewport.GotoBottom()
|
|
||||||
}
|
|
||||||
if m.dirty {
|
if m.dirty {
|
||||||
cmds = append(cmds, m.renderView())
|
cmds = append(cmds, m.renderView())
|
||||||
}
|
}
|
||||||
@@ -120,12 +120,12 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
type renderCompleteMsg struct {
|
type renderCompleteMsg struct {
|
||||||
viewport viewport.Model
|
viewport viewport.Model
|
||||||
|
header string
|
||||||
partCount int
|
partCount int
|
||||||
lineCount int
|
lineCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *messagesComponent) renderView() tea.Cmd {
|
func (m *messagesComponent) renderView() tea.Cmd {
|
||||||
m.header = m.renderHeader()
|
|
||||||
|
|
||||||
if m.rendering {
|
if m.rendering {
|
||||||
slog.Debug("pending render, skipping")
|
slog.Debug("pending render, skipping")
|
||||||
@@ -138,8 +138,10 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
m.rendering = true
|
m.rendering = true
|
||||||
|
|
||||||
viewport := m.viewport
|
viewport := m.viewport
|
||||||
|
tail := m.tail
|
||||||
|
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
|
header := m.renderHeader()
|
||||||
measure := util.Measure("messages.renderView")
|
measure := util.Measure("messages.renderView")
|
||||||
defer measure()
|
defer measure()
|
||||||
|
|
||||||
@@ -402,8 +404,12 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
content := "\n" + strings.Join(blocks, "\n\n")
|
content := "\n" + strings.Join(blocks, "\n\n")
|
||||||
viewport.SetHeight(m.height - lipgloss.Height(m.header))
|
viewport.SetHeight(m.height - lipgloss.Height(m.header))
|
||||||
viewport.SetContent(content)
|
viewport.SetContent(content)
|
||||||
|
if tail {
|
||||||
|
viewport.GotoBottom()
|
||||||
|
}
|
||||||
|
|
||||||
return renderCompleteMsg{
|
return renderCompleteMsg{
|
||||||
|
header: header,
|
||||||
viewport: viewport,
|
viewport: viewport,
|
||||||
partCount: partCount,
|
partCount: partCount,
|
||||||
lineCount: lineCount,
|
lineCount: lineCount,
|
||||||
|
|||||||
@@ -2051,10 +2051,6 @@ func wrapInterfaces(content []any, width int) [][]any {
|
|||||||
if unicode.IsSpace(r) {
|
if unicode.IsSpace(r) {
|
||||||
isSpace = true
|
isSpace = true
|
||||||
}
|
}
|
||||||
// Use hyphen-aware word boundary detection
|
|
||||||
if r == '-' {
|
|
||||||
isSpace = false
|
|
||||||
}
|
|
||||||
itemW = rw.RuneWidth(r)
|
itemW = rw.RuneWidth(r)
|
||||||
} else if att, ok := item.(*Attachment); ok {
|
} else if att, ok := item.(*Attachment); ok {
|
||||||
itemW = uniseg.StringWidth(att.Display)
|
itemW = uniseg.StringWidth(att.Display)
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ func (a appModel) Init() tea.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
measure := util.Measure("Update")
|
measure := util.Measure("app.Update")
|
||||||
defer measure("from", fmt.Sprintf("%T", msg))
|
defer measure("from", fmt.Sprintf("%T", msg))
|
||||||
|
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
@@ -528,17 +528,17 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a appModel) View() string {
|
func (a appModel) View() string {
|
||||||
|
measure := util.Measure("app.View")
|
||||||
|
defer measure()
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
|
|
||||||
var mainLayout string
|
var mainLayout string
|
||||||
|
|
||||||
measure := util.Measure("app.View")
|
|
||||||
if a.app.Session.ID == "" {
|
if a.app.Session.ID == "" {
|
||||||
mainLayout = a.home()
|
mainLayout = a.home()
|
||||||
} else {
|
} else {
|
||||||
mainLayout = a.chat()
|
mainLayout = a.chat()
|
||||||
}
|
}
|
||||||
measure()
|
|
||||||
mainLayout = styles.NewStyle().
|
mainLayout = styles.NewStyle().
|
||||||
Background(t.Background()).
|
Background(t.Background()).
|
||||||
Padding(0, 2).
|
Padding(0, 2).
|
||||||
|
|||||||
@@ -83,17 +83,11 @@ func Extension(path string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToMarkdown(content string, width int, backgroundColor compat.AdaptiveColor) string {
|
func ToMarkdown(content string, width int, backgroundColor compat.AdaptiveColor) string {
|
||||||
renderWidth := width - GetMarkdownContainerFrame()
|
r := styles.GetMarkdownRenderer(width-6, backgroundColor)
|
||||||
r := styles.GetMarkdownRenderer(renderWidth, backgroundColor)
|
|
||||||
content = strings.ReplaceAll(content, RootPath+"/", "")
|
content = strings.ReplaceAll(content, RootPath+"/", "")
|
||||||
|
rendered, _ := r.Render(content)
|
||||||
// Apply hyphen preservation during markdown rendering
|
|
||||||
rendered := ProcessTextWithHyphens(content, func(t string) string {
|
|
||||||
result, _ := r.Render(t)
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
lines := strings.Split(rendered, "\n")
|
lines := strings.Split(rendered, "\n")
|
||||||
// Clean up empty lines at start/end
|
|
||||||
if len(lines) > 0 {
|
if len(lines) > 0 {
|
||||||
firstLine := lines[0]
|
firstLine := lines[0]
|
||||||
cleaned := ansi.Strip(firstLine)
|
cleaned := ansi.Strip(firstLine)
|
||||||
|
|||||||
@@ -43,17 +43,37 @@ func New(opts ...Option) (m Model) {
|
|||||||
opt(&m)
|
opt(&m)
|
||||||
}
|
}
|
||||||
m.setInitialValues()
|
m.setInitialValues()
|
||||||
|
m.memo = &Memo{}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Memo struct {
|
||||||
|
dirty bool
|
||||||
|
cache string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Memo) View(render func() string) string {
|
||||||
|
if m.dirty {
|
||||||
|
// slog.Debug("memo dirty")
|
||||||
|
m.cache = render()
|
||||||
|
m.dirty = false
|
||||||
|
return m.cache
|
||||||
|
}
|
||||||
|
// slog.Debug("memo cache")
|
||||||
|
return m.cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Memo) Invalidate() {
|
||||||
|
m.dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
// Model is the Bubble Tea model for this viewport element.
|
// Model is the Bubble Tea model for this viewport element.
|
||||||
type Model struct {
|
type Model struct {
|
||||||
|
memo *Memo
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
KeyMap KeyMap
|
KeyMap KeyMap
|
||||||
|
|
||||||
cache string
|
|
||||||
|
|
||||||
// Whether or not to wrap text. If false, it'll allow horizontal scrolling
|
// Whether or not to wrap text. If false, it'll allow horizontal scrolling
|
||||||
// instead.
|
// instead.
|
||||||
SoftWrap bool
|
SoftWrap bool
|
||||||
@@ -160,6 +180,7 @@ func (m Model) Height() int {
|
|||||||
// SetHeight sets the height of the viewport.
|
// SetHeight sets the height of the viewport.
|
||||||
func (m *Model) SetHeight(h int) {
|
func (m *Model) SetHeight(h int) {
|
||||||
m.height = h
|
m.height = h
|
||||||
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Width returns the width of the viewport.
|
// Width returns the width of the viewport.
|
||||||
@@ -170,6 +191,7 @@ func (m Model) Width() int {
|
|||||||
// SetWidth sets the width of the viewport.
|
// SetWidth sets the width of the viewport.
|
||||||
func (m *Model) SetWidth(w int) {
|
func (m *Model) SetWidth(w int) {
|
||||||
m.width = w
|
m.width = w
|
||||||
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AtTop returns whether or not the viewport is at the very top position.
|
// AtTop returns whether or not the viewport is at the very top position.
|
||||||
@@ -220,7 +242,7 @@ func (m Model) HorizontalScrollPercent() float64 {
|
|||||||
func (m *Model) SetContent(s string) {
|
func (m *Model) SetContent(s string) {
|
||||||
s = strings.ReplaceAll(s, "\r\n", "\n") // normalize line endings
|
s = strings.ReplaceAll(s, "\r\n", "\n") // normalize line endings
|
||||||
m.SetContentLines(strings.Split(s, "\n"))
|
m.SetContentLines(strings.Split(s, "\n"))
|
||||||
m.render()
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetContentLines allows to set the lines to be shown instead of the content.
|
// SetContentLines allows to set the lines to be shown instead of the content.
|
||||||
@@ -239,7 +261,7 @@ func (m *Model) SetContentLines(lines []string) {
|
|||||||
if m.YOffset > m.maxYOffset() {
|
if m.YOffset > m.maxYOffset() {
|
||||||
m.GotoBottom()
|
m.GotoBottom()
|
||||||
}
|
}
|
||||||
m.render()
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContent returns the entire content as a single string.
|
// GetContent returns the entire content as a single string.
|
||||||
@@ -442,12 +464,14 @@ func (m Model) setupGutter(lines []string) []string {
|
|||||||
}
|
}
|
||||||
result[i] = strings.Join(line, "\n")
|
result[i] = strings.Join(line, "\n")
|
||||||
}
|
}
|
||||||
|
m.memo.Invalidate()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetYOffset sets the Y offset.
|
// SetYOffset sets the Y offset.
|
||||||
func (m *Model) SetYOffset(n int) {
|
func (m *Model) SetYOffset(n int) {
|
||||||
m.YOffset = clamp(n, 0, m.maxYOffset())
|
m.YOffset = clamp(n, 0, m.maxYOffset())
|
||||||
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetXOffset sets the X offset.
|
// SetXOffset sets the X offset.
|
||||||
@@ -457,6 +481,7 @@ func (m *Model) SetXOffset(n int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
m.xOffset = clamp(n, 0, m.maxXOffset())
|
m.xOffset = clamp(n, 0, m.maxXOffset())
|
||||||
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureVisible ensures that the given line and column are in the viewport.
|
// EnsureVisible ensures that the given line and column are in the viewport.
|
||||||
@@ -483,7 +508,7 @@ func (m *Model) ViewDown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.LineDown(m.Height())
|
m.LineDown(m.Height())
|
||||||
m.render()
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ViewUp moves the view up by one height of the viewport. Basically, "page up".
|
// ViewUp moves the view up by one height of the viewport. Basically, "page up".
|
||||||
@@ -493,7 +518,7 @@ func (m *Model) ViewUp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.LineUp(m.Height())
|
m.LineUp(m.Height())
|
||||||
m.render()
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// HalfViewDown moves the view down by half the height of the viewport.
|
// HalfViewDown moves the view down by half the height of the viewport.
|
||||||
@@ -503,7 +528,7 @@ func (m *Model) HalfViewDown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.LineDown(m.Height() / 2) //nolint:mnd
|
m.LineDown(m.Height() / 2) //nolint:mnd
|
||||||
m.render()
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// HalfViewUp moves the view up by half the height of the viewport.
|
// HalfViewUp moves the view up by half the height of the viewport.
|
||||||
@@ -513,7 +538,7 @@ func (m *Model) HalfViewUp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.LineUp(m.Height() / 2) //nolint:mnd
|
m.LineUp(m.Height() / 2) //nolint:mnd
|
||||||
m.render()
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LineDown moves the view down by the given number of lines.
|
// LineDown moves the view down by the given number of lines.
|
||||||
@@ -527,7 +552,7 @@ func (m *Model) LineDown(n int) {
|
|||||||
// the bottom.
|
// the bottom.
|
||||||
m.SetYOffset(m.YOffset + n)
|
m.SetYOffset(m.YOffset + n)
|
||||||
m.hiIdx = m.findNearedtMatch()
|
m.hiIdx = m.findNearedtMatch()
|
||||||
m.render()
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LineUp moves the view down by the given number of lines. Returns the new
|
// LineUp moves the view down by the given number of lines. Returns the new
|
||||||
@@ -541,7 +566,7 @@ func (m *Model) LineUp(n int) {
|
|||||||
// greater than the number of lines we are from the top.
|
// greater than the number of lines we are from the top.
|
||||||
m.SetYOffset(m.YOffset - n)
|
m.SetYOffset(m.YOffset - n)
|
||||||
m.hiIdx = m.findNearedtMatch()
|
m.hiIdx = m.findNearedtMatch()
|
||||||
m.render()
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TotalLineCount returns the total number of lines (both hidden and visible) within the viewport.
|
// TotalLineCount returns the total number of lines (both hidden and visible) within the viewport.
|
||||||
@@ -562,7 +587,7 @@ func (m *Model) GotoTop() (lines []string) {
|
|||||||
|
|
||||||
m.SetYOffset(0)
|
m.SetYOffset(0)
|
||||||
m.hiIdx = m.findNearedtMatch()
|
m.hiIdx = m.findNearedtMatch()
|
||||||
m.render()
|
m.memo.Invalidate()
|
||||||
return m.visibleLines()
|
return m.visibleLines()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,7 +595,7 @@ func (m *Model) GotoTop() (lines []string) {
|
|||||||
func (m *Model) GotoBottom() (lines []string) {
|
func (m *Model) GotoBottom() (lines []string) {
|
||||||
m.SetYOffset(m.maxYOffset())
|
m.SetYOffset(m.maxYOffset())
|
||||||
m.hiIdx = m.findNearedtMatch()
|
m.hiIdx = m.findNearedtMatch()
|
||||||
m.render()
|
m.memo.Invalidate()
|
||||||
return m.visibleLines()
|
return m.visibleLines()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,6 +608,7 @@ func (m *Model) SetHorizontalStep(n int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.horizontalStep = n
|
m.horizontalStep = n
|
||||||
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MoveLeft moves the viewport to the left by the given number of columns.
|
// MoveLeft moves the viewport to the left by the given number of columns.
|
||||||
@@ -590,6 +616,7 @@ func (m *Model) MoveLeft(cols int) {
|
|||||||
m.xOffset -= cols
|
m.xOffset -= cols
|
||||||
if m.xOffset < 0 {
|
if m.xOffset < 0 {
|
||||||
m.xOffset = 0
|
m.xOffset = 0
|
||||||
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,6 +633,7 @@ func (m *Model) MoveRight(cols int) {
|
|||||||
// Resets lines indent to zero.
|
// Resets lines indent to zero.
|
||||||
func (m *Model) ResetIndent() {
|
func (m *Model) ResetIndent() {
|
||||||
m.xOffset = 0
|
m.xOffset = 0
|
||||||
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHighlights sets ranges of characters to highlight.
|
// SetHighlights sets ranges of characters to highlight.
|
||||||
@@ -623,12 +651,14 @@ func (m *Model) SetHighlights(matches [][]int) {
|
|||||||
m.highlights = parseMatches(m.GetContent(), matches)
|
m.highlights = parseMatches(m.GetContent(), matches)
|
||||||
m.hiIdx = m.findNearedtMatch()
|
m.hiIdx = m.findNearedtMatch()
|
||||||
m.showHighlight()
|
m.showHighlight()
|
||||||
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearHighlights clears previously set highlights.
|
// ClearHighlights clears previously set highlights.
|
||||||
func (m *Model) ClearHighlights() {
|
func (m *Model) ClearHighlights() {
|
||||||
m.highlights = nil
|
m.highlights = nil
|
||||||
m.hiIdx = -1
|
m.hiIdx = -1
|
||||||
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) showHighlight() {
|
func (m *Model) showHighlight() {
|
||||||
@@ -637,6 +667,7 @@ func (m *Model) showHighlight() {
|
|||||||
}
|
}
|
||||||
line, colstart, colend := m.highlights[m.hiIdx].coords()
|
line, colstart, colend := m.highlights[m.hiIdx].coords()
|
||||||
m.EnsureVisible(line, colstart, colend)
|
m.EnsureVisible(line, colstart, colend)
|
||||||
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// HighlightNext highlights the next match.
|
// HighlightNext highlights the next match.
|
||||||
@@ -647,6 +678,7 @@ func (m *Model) HighlightNext() {
|
|||||||
|
|
||||||
m.hiIdx = (m.hiIdx + 1) % len(m.highlights)
|
m.hiIdx = (m.hiIdx + 1) % len(m.highlights)
|
||||||
m.showHighlight()
|
m.showHighlight()
|
||||||
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// HighlightPrevious highlights the previous match.
|
// HighlightPrevious highlights the previous match.
|
||||||
@@ -657,6 +689,7 @@ func (m *Model) HighlightPrevious() {
|
|||||||
|
|
||||||
m.hiIdx = (m.hiIdx - 1 + len(m.highlights)) % len(m.highlights)
|
m.hiIdx = (m.hiIdx - 1 + len(m.highlights)) % len(m.highlights)
|
||||||
m.showHighlight()
|
m.showHighlight()
|
||||||
|
m.memo.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) findNearedtMatch() int {
|
func (m Model) findNearedtMatch() int {
|
||||||
@@ -728,29 +761,30 @@ func (m Model) updateAsModel(msg tea.Msg) Model {
|
|||||||
|
|
||||||
// View renders the viewport into a string.
|
// View renders the viewport into a string.
|
||||||
func (m *Model) render() {
|
func (m *Model) render() {
|
||||||
w, h := m.Width(), m.Height()
|
|
||||||
if sw := m.Style.GetWidth(); sw != 0 {
|
|
||||||
w = min(w, sw)
|
|
||||||
}
|
|
||||||
if sh := m.Style.GetHeight(); sh != 0 {
|
|
||||||
h = min(h, sh)
|
|
||||||
}
|
|
||||||
contentWidth := w - m.Style.GetHorizontalFrameSize()
|
|
||||||
contentHeight := h - m.Style.GetVerticalFrameSize()
|
|
||||||
visible := m.visibleLines()
|
|
||||||
contents := lipgloss.NewStyle().
|
|
||||||
Width(contentWidth). // pad to width.
|
|
||||||
Height(contentHeight). // pad to height.
|
|
||||||
MaxHeight(contentHeight). // truncate height if taller.
|
|
||||||
MaxWidth(contentWidth). // truncate width if wider.
|
|
||||||
Render(strings.Join(visible, "\n"))
|
|
||||||
m.cache = m.Style.
|
|
||||||
UnsetWidth().UnsetHeight(). // Style size already applied in contents.
|
|
||||||
Render(contents)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) View() string {
|
func (m Model) View() string {
|
||||||
return m.cache
|
return m.memo.View(func() string {
|
||||||
|
w, h := m.Width(), m.Height()
|
||||||
|
if sw := m.Style.GetWidth(); sw != 0 {
|
||||||
|
w = min(w, sw)
|
||||||
|
}
|
||||||
|
if sh := m.Style.GetHeight(); sh != 0 {
|
||||||
|
h = min(h, sh)
|
||||||
|
}
|
||||||
|
contentWidth := w - m.Style.GetHorizontalFrameSize()
|
||||||
|
contentHeight := h - m.Style.GetVerticalFrameSize()
|
||||||
|
visible := m.visibleLines()
|
||||||
|
contents := lipgloss.NewStyle().
|
||||||
|
Width(contentWidth). // pad to width.
|
||||||
|
Height(contentHeight). // pad to height.
|
||||||
|
MaxHeight(contentHeight). // truncate height if taller.
|
||||||
|
MaxWidth(contentWidth). // truncate width if wider.
|
||||||
|
Render(strings.Join(visible, "\n"))
|
||||||
|
return m.Style.
|
||||||
|
UnsetWidth().UnsetHeight(). // Style size already applied in contents.
|
||||||
|
Render(contents)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func clamp(v, low, high int) int {
|
func clamp(v, low, high int) int {
|
||||||
|
|||||||
Reference in New Issue
Block a user