Skip to content

Commit 3ba665a

Browse files
committed
Retry texlab with full text on state loss and clamp offsets
1 parent 71278bd commit 3ba665a

3 files changed

Lines changed: 55 additions & 12 deletions

File tree

‎services/web/app/src/Features/Texlab/TexlabController.mjs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import SessionManager from '../Authentication/SessionManager.mjs'
2-
import TexlabManager from './TexlabManager.mjs'
2+
import TexlabManager, { REQUIRE_FULL_TEXT } from './TexlabManager.mjs'
33
import texlabLogger from './TexlabLogger.mjs'
44

55
async function complete(req, res) {
@@ -29,6 +29,9 @@ async function complete(req, res) {
2929
})
3030
res.json({ items: result.items || [] })
3131
} catch (err) {
32+
if (err && err.code === REQUIRE_FULL_TEXT) {
33+
return res.status(409).json({ requiresFullText: true })
34+
}
3235
texlabLogger.error({ err, userId, projectId }, '[Texlab] completion failed')
3336
res.status(500).json({ error: 'texlab completion failed' })
3437
}

‎services/web/app/src/Features/Texlab/TexlabManager.mjs‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const requestBlobAsync = (projectId, fileHash) =>
3737
})
3838
})
3939

40+
export const REQUIRE_FULL_TEXT = 'REQUIRES_FULL_TEXT'
41+
4042
function buildHeaders(payload) {
4143
return `Content-Length: ${Buffer.byteLength(payload, 'utf8')}\r\n\r\n`
4244
}
@@ -394,6 +396,11 @@ class TexlabProcess {
394396
let textForPosition = fullText ?? currentText
395397

396398
if (!existing) {
399+
if (typeof fullText !== 'string') {
400+
const err = new Error(REQUIRE_FULL_TEXT)
401+
err.code = REQUIRE_FULL_TEXT
402+
throw err
403+
}
397404
const initialText = fullText ?? ''
398405
this.docs.set(uri, { version: desiredVersion, text: initialText })
399406
textForPosition = initialText
@@ -438,7 +445,19 @@ class TexlabProcess {
438445
}
439446
}
440447

441-
const position = positionFromOffset(textForPosition || '', offset)
448+
const baseText = textForPosition || ''
449+
let safeOffset = offset
450+
if (safeOffset > baseText.length) {
451+
safeOffset = baseText.length
452+
texlabLogger.warn(
453+
{ offset, safeOffset, length: baseText.length },
454+
'[Texlab] clamped completion offset'
455+
)
456+
} else if (safeOffset < 0) {
457+
safeOffset = 0
458+
}
459+
460+
const position = positionFromOffset(baseText, safeOffset)
442461
return await this.sendRequest(
443462
{
444463
jsonrpc: '2.0',

‎services/web/modules/texlab-completion/frontend/texlab-completion-source.ts‎

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CompletionContext, CompletionResult } from '@codemirror/autocomplete'
22
import getMeta from '@/utils/meta'
3-
import { postJSON } from '@/infrastructure/fetch-json'
3+
import { FetchError, postJSON } from '@/infrastructure/fetch-json'
44

55
type DocCache = {
66
text: string
@@ -78,16 +78,37 @@ async function fetchTexlabCompletion(
7878
}
7979
}
8080

81-
try {
82-
const res = await postJSON(`/project/${projectId}/texlab/complete`, {
83-
body: {
84-
docPath,
85-
offset,
86-
version,
87-
fullText,
88-
changes,
89-
},
81+
async function sendRequest(forceFullText = false) {
82+
const payloadVersion =
83+
forceFullText && cached ? cached.version + 1 : version
84+
const payloadText = forceFullText ? cached?.text ?? docText : fullText
85+
const payload = {
86+
docPath,
87+
offset,
88+
version: payloadVersion,
89+
fullText: payloadText,
90+
changes: forceFullText ? undefined : changes,
91+
}
92+
if (payloadText) {
93+
docCache.set(docPath, { text: payloadText, version: payloadVersion })
94+
}
95+
return await postJSON(`/project/${projectId}/texlab/complete`, {
96+
body: payload,
9097
})
98+
}
99+
100+
try {
101+
let res: any
102+
try {
103+
res = await sendRequest(false)
104+
} catch (err) {
105+
if (err instanceof FetchError && err.response?.status === 409) {
106+
// Server lost state, retry with full text
107+
res = await sendRequest(true)
108+
} else {
109+
throw err
110+
}
111+
}
91112
const items = (res.items || []).map((item: any) => {
92113
const label = item.label || item.insertText || ''
93114
const insertText = item.insertText || label

0 commit comments

Comments
 (0)