Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preview Improvements #906

Merged
merged 3 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions code/changes/875.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ensure scrolling is still synchronised, even after scrolling the preview window
5 changes: 4 additions & 1 deletion code/src/node/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ export class PreviewManager {
}

private scrollView(editor: vscode.TextEditor) {
if (editor.document.uri !== this.currentUri) {
// For some reason, the object representation of the same URI is not stable
// leading this check to fail in cases where it should pass.
// Instead, compare the string representation of the uris.
if (editor.document.uri.toString() !== this.currentUri?.toString()) {
return
}

Expand Down
12 changes: 9 additions & 3 deletions docs/lsp/reference/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,25 +74,31 @@ The following options are useful when extending or working on the language serve

Developer flag which, when enabled, the server will publish any deprecation warnings as diagnostics.

.. esbonio:config:: esbonio.server.enableDevTools (boolean)
.. esbonio:config:: esbonio.server.enableDevTools
:scope: global
:type: boolean

Enable `lsp-devtools`_ integration for the language server itself.

.. esbonio:config:: esbonio.sphinx.enableDevTools (boolean)
.. esbonio:config:: esbonio.sphinx.enableDevTools
:scope: global
:type: boolean

Enable `lsp-devtools`_ integration for the Sphinx subprocess started by the language server.

.. esbonio:config:: esbonio.sphinx.pythonPath (string[])
.. esbonio:config:: esbonio.sphinx.pythonPath
:scope: global
:type: string[]

List of paths to use when constructing the value of ``PYTHONPATH``.
Used to inject the sphinx agent into the target environment."

.. esbonio:config:: esbonio.preview.showLineMarkers
:scope: global
:type: boolean

When enabled, reveal the source uri and line number (if possible) for the html element under the cursor.

.. _lsp-devtools: https://swyddfa.github.io/lsp-devtools/docs/latest/en/

.. _lsp-configuration-logging:
Expand Down
1 change: 1 addition & 0 deletions lib/esbonio/changes/704.enhancement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
When clicking on internal links of a previewed page, the corresponding source file will be automatically opened in the editor
3 changes: 3 additions & 0 deletions lib/esbonio/changes/906.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The `esbonio.preview.showLineMarkers` option should now work again.

When clicking on internal links of a previewed page, the websocket connection to the language server is now preserved.
171 changes: 127 additions & 44 deletions lib/esbonio/esbonio/sphinx_agent/static/webview.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,85 @@
// which allows the webpage to talk with the preview server and coordinate details such as refreshes
// and page scrolling.

/**
* Rewrite internal links so that the link between the webview and
* language server is maintained across pages.
*/
function rewriteInternalLinks(wsPort) {
if (!wsPort) {
return
}

const links = Array.from(document.querySelectorAll("a.internal"))

for (let link of links) {
let uri
try {
uri = new URL(link.href)
} catch (err) {
console.debug(`Skipping link ${link.href}, ${err}`)
continue
}

if (!uri.search) {
uri.search = `?ws=${wsPort}`
} else if (!uri.searchParams.get('ws')) {
uri.search += `&ws=${wsPort}`
}

link.href = uri.toString()
}
}

/**
* Sync the webview's scroll position with the editor
*/
function syncScrollPosition() {
const target = findEditorScrollTarget()
if (!target) {
console.debug('No target found')
return
}

const uri = target[0]
const line = target[1]

if (!uri || !line) {
console.debug('Missing uri or line')
return
}

// TODO: Rate limits.
sendMessage(
{ jsonrpc: "2.0", method: "editor/scroll", params: { uri: uri, line: line } }
)
}

/**
* Get the uri and line number of the given marker
*
* @param {HTMLElement} marker
* @returns {[string, number]} - The uri and line number
*/
function getMarkerLocation(marker) {
const match = marker.className.match(/.* esbonio-marker-(\d+).*/)
if (!match || !match[1]) {
console.debug(`Unable to find marker id in '${marker.className}'`)
return
}

const markerId = match[1]
const location = document.querySelector(`#esbonio-marker-index span[data-id="${markerId}"]`)
if (!location) {
console.debug(`Unable to locate source for marker id: '${markerId}'`)
return
}

const uri = location.dataset.uri
const line = parseInt(location.dataset.line)
return [uri, line]
}

/**
* Find the uri and line number the editor should scroll to
*
Expand All @@ -18,22 +97,7 @@ function findEditorScrollTarget() {
continue
}

const match = marker.className.match(/.* esbonio-marker-(\d+).*/)
if (!match || !match[1]) {
console.debug(`Unable to find marker id in '${marker.className}'`)
return
}

const markerId = match[1]
const location = document.querySelector(`#esbonio-marker-index span[data-id="${markerId}"]`)
if (!location) {
console.debug(`Unable to locate source for marker id: '${markerId}'`)
return
}

const uri = location.dataset.uri
const line = parseInt(location.dataset.line)
return [uri, line]
return getMarkerLocation(marker)
}

return
Expand Down Expand Up @@ -107,11 +171,51 @@ function scrollViewTo(uri, linum) {
const t = (linum - previousLine) / Math.max(currentLine - previousLine, 1)
const y = (1 - t) * previousPos + t * currentPos

console.table({line: linum, previous: previousLine, current: currentLine, t: t, y: y})
// console.table({line: linum, previous: previousLine, current: currentLine, t: t, y: y})

window.scrollTo(0, y - 60)
}

/**
* Render the markers used to synchronise scroll state
*/
function renderLineMarkers() {

const markers = Array.from(document.querySelectorAll(`.esbonio-marker`))
let lines = [".esbonio-marker { position: relative; }"]

for (let marker of markers) {
let location = getMarkerLocation(marker)
if (!location) {
continue
}

let uri = location[0]
let line = location[1]

const match = marker.className.match(/.* esbonio-marker-(\d+).*/)
let markerId = match[1]

lines.push(`
.esbonio-marker-${markerId}::before {
display: none;
content: '${uri}:${line}';
font-family: monospace;
position: absolute;
top: -1.2em;
}

.esbonio-marker-${markerId}:hover::before {
display: block;
}
`)
}

let markerStyle = document.createElement('style')
markerStyle.innerText = lines.join('\n')
document.body.append(markerStyle)
}

const host = window.location.hostname;
const queryString = window.location.search;
const queryParams = new URLSearchParams(queryString);
Expand Down Expand Up @@ -166,29 +270,15 @@ function handle(message) {
}

window.addEventListener("scroll", (event) => {
const target = findEditorScrollTarget()
if (!target) {
return
}

const uri = target[0]
const line = target[1]

if (!uri || !line) {
return
}

// TODO: Rate limits.
sendMessage(
{ jsonrpc: "2.0", method: "editor/scroll", params: { uri: uri, line: line } }
)

syncScrollPosition()
})

// Connection opened
socket.addEventListener("open", (event) => {
console.debug("Connected.")
connected = true

setTimeout(syncScrollPosition, 50)
});

// Listen for messages
Expand All @@ -198,18 +288,11 @@ socket.addEventListener("message", (event) => {

function main() {
if (showMarkers) {
let markerStyle = document.createElement('style')
let lines = [".linemarker { background: rgb(255, 0, 0, 0.25); position: relative; }"]
for (let line of scrollTargets.keys()) {
lines.push(`.linemarker-${line}::before {
content: 'line ${line}'; position: absolute; right: 0; top: -1.2em;
}`)
}

markerStyle.innerText = lines.join('\n')
document.body.append(markerStyle)
renderLineMarkers()
}

rewriteInternalLinks(ws)

// Are we in an <iframe>?
if (window.parent !== window.top) {
window.parent.postMessage({ 'ready': true }, "*")
Expand Down