Skip to content

Commit

Permalink
Add support for input
Browse files Browse the repository at this point in the history
  • Loading branch information
fcollonval committed May 14, 2024
1 parent dac0e82 commit 7c256ae
Showing 1 changed file with 98 additions and 93 deletions.
191 changes: 98 additions & 93 deletions packages/docprovider/src/notebookCellExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,104 @@ async function requestServer(
ServerConnection.makeRequest(url, init, settings)
.then(async response => {
if (!response.ok) {
promise.reject(await ServerConnection.ResponseError.create(response));
if (response.status === 300) {
let replyUrl = response.headers.get('Location') || '';

if (!replyUrl.startsWith(settings.baseUrl)) {
replyUrl = URLExt.join(settings.baseUrl, replyUrl);
}
const { parent_header, input_request } = await response.json();
// TODO only the client sending the snippet will be prompted for the input
// we can have a deadlock if its connection is lost.
const panel = new Panel();
panel.addClass('jp-OutputArea-child');
panel.addClass('jp-OutputArea-stdin-item');

const prompt = new OutputPrompt();
prompt.addClass('jp-OutputArea-prompt');
panel.addWidget(prompt);

const input = new Stdin({
future: Object.freeze({
sendInputReply: (
content: KernelMessage.IInputReply,
parent_header: KernelMessage.IHeader<'input_request'>
) => {
ServerConnection.makeRequest(
replyUrl,
{
method: 'POST',
body: JSON.stringify({ input: content.value })
},
settings
).catch(error => {
console.error(
`Failed to set input to ${JSON.stringify(content)}.`,
error
);
});
}
}) as any,
parent_header,
password: input_request.password,
prompt: input_request.prompt,
translator
});
input.addClass('jp-OutputArea-output');
panel.addWidget(input);

// Get the input node to ensure focus after updating the model upon user reply.
const inputNode = input.node.getElementsByTagName('input')[0];

void input.value.then(value => {
panel.addClass('jp-OutputArea-stdin-hiding');

// FIXME this is not great as the model should not be modified on the client.
// Use stdin as the stream so it does not get combined with stdout.
// Note: because it modifies DOM it may (will) shift focus away from the input node.
cell.outputArea.model.add({
output_type: 'stream',
name: 'stdin',
text: value + '\n'
});
// Refocus the input node after it lost focus due to update of the model.
inputNode.focus();

// Keep the input in view for a little while; this (along refocusing)
// ensures that we can avoid the cell editor stealing the focus, and
// leading to user inadvertently modifying editor content when executing
// consecutive commands in short succession.
window.setTimeout(async () => {
// Tack currently focused element to ensure that it remains on it
// after disposal of the panel with the old input
// (which modifies DOM and can lead to focus jump).
const focusedElement = document.activeElement;
// Dispose the old panel with no longer needed input box.
panel.dispose();
// Refocus the element that was focused before.
if (focusedElement && focusedElement instanceof HTMLElement) {
focusedElement.focus();
}

try {
const response = await requestServer(
cell,
url,
init,
settings,
translator
);
promise.resolve(response);
} catch (error) {
promise.reject(error);
}
}, 500);
});

cell.outputArea.layout.addWidget(panel);
} else {
promise.reject(await ServerConnection.ResponseError.create(response));
}
} else if (response.status === 202) {
let redirectUrl = response.headers.get('Location') || url;

Expand Down Expand Up @@ -234,98 +331,6 @@ async function requestServer(
// Evanescent interval
Math.min(MAX_POLLING_INTERVAL, interval * 2)
);
} else if (response.status === 300) {
let replyUrl = response.headers.get('Location') || '';

if (!replyUrl.startsWith(settings.baseUrl)) {
replyUrl = URLExt.join(settings.baseUrl, replyUrl);
}
const { parent_header, input_request } = await response.json();
// TODO only the client sending the snippet will be prompted for the input
// we can have a deadlock if its connection is lost.
const panel = new Panel();
panel.addClass('jp-OutputArea-child');
panel.addClass('jp-OutputArea-stdin-item');

const prompt = new OutputPrompt();
prompt.addClass('jp-OutputArea-prompt');
panel.addWidget(prompt);

const input = new Stdin({
future: Object.freeze({
sendInputReply: (
content: KernelMessage.IInputReply,
parent_header: KernelMessage.IHeader<'input_request'>
) => {
ServerConnection.makeRequest(
replyUrl,
{ method: 'POST' },
settings
).catch(error => {
console.error(
`Failed to set input to ${JSON.stringify(content)}.`,
error
);
});
}
}) as any,
parent_header,
password: input_request.password,
prompt: input_request.prompt,
translator
});
input.addClass('jp-OutputArea-output');
panel.addWidget(input);

// Get the input node to ensure focus after updating the model upon user reply.
const inputNode = input.node.getElementsByTagName('input')[0];

void input.value.then(value => {
panel.addClass('jp-OutputArea-stdin-hiding');

// FIXME this is not great as the model should not be modified on the client.
// Use stdin as the stream so it does not get combined with stdout.
// Note: because it modifies DOM it may (will) shift focus away from the input node.
cell.outputArea.model.add({
output_type: 'stream',
name: 'stdin',
text: value + '\n'
});
// Refocus the input node after it lost focus due to update of the model.
inputNode.focus();

// Keep the input in view for a little while; this (along refocusing)
// ensures that we can avoid the cell editor stealing the focus, and
// leading to user inadvertently modifying editor content when executing
// consecutive commands in short succession.
window.setTimeout(async () => {
// Tack currently focused element to ensure that it remains on it
// after disposal of the panel with the old input
// (which modifies DOM and can lead to focus jump).
const focusedElement = document.activeElement;
// Dispose the old panel with no longer needed input box.
panel.dispose();
// Refocus the element that was focused before.
if (focusedElement && focusedElement instanceof HTMLElement) {
focusedElement.focus();
}

try {
const response = await requestServer(
cell,
url,
init,
settings,
translator
);
promise.resolve(response);
} catch (error) {
promise.reject(error);
}
}, 500);
});

cell.outputArea.layout.addWidget(panel);
} else {
promise.resolve(response);
}
Expand Down

0 comments on commit 7c256ae

Please sign in to comment.