diff --git a/CHANGELOG.md b/CHANGELOG.md index f75bb9b16..635fdf3d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ ## CHANGELOG +### `@krassowski/jupyterlab-lsp 2.0.9` (???) + +- bug fixes + + - handles characters that need escaping (spaces, non-ASCII characters) more + robustly in files and folder names ([#403]) + +[#403]: https://github.com/krassowski/jupyterlab-lsp/issues/403 + +### `@krassowski/jupyterlab_go_to_definition 2.0.0` (???) + +- features + + - breaking change: renames `uri` to `contents_path` to help avoid programmer issues + with characters requiring URI encoding ([#406]) + +[#406]: https://github.com/krassowski/jupyterlab-lsp/pull/406 + ### `@krassowski/jupyterlab-lsp 2.0.8` (2020-10-25) - bug fixes diff --git a/atest/00_Smoke.robot b/atest/00_Smoke.robot index aba8c844b..c6ef1a240 100644 --- a/atest/00_Smoke.robot +++ b/atest/00_Smoke.robot @@ -1,5 +1,5 @@ *** Settings *** -Suite Setup Set Screenshot Directory ${OUTPUT DIR}${/}screenshots${/}smoke +Suite Setup Set Screenshot Directory ${SCREENSHOTS DIR}${/}smoke Resource Keywords.robot *** Test Cases *** diff --git a/atest/01_Editor.robot b/atest/01_Editor.robot index 791fde397..e970a7e7f 100644 --- a/atest/01_Editor.robot +++ b/atest/01_Editor.robot @@ -1,6 +1,6 @@ *** Settings *** Suite Setup Setup Suite For Screenshots editor -Force Tags ui:editor +Force Tags ui:editor aspect:ls:features Resource Keywords.robot Resource Variables.robot @@ -90,29 +90,6 @@ Editor Should Show Diagnostics Should Be True ${count} >= 1 Close Diagnostics Panel -Editor Should Jump To Definition - [Arguments] ${symbol} - Set Tags feature:jump-to-definition - ${sel} = Set Variable If "${symbol}".startswith(("xpath", "css")) ${symbol} xpath:(//span[@role="presentation"][contains(., "${symbol}")])[last()] - Open Context Menu Over ${sel} - ${cursor} = Measure Cursor Position - Capture Page Screenshot 02-jump-to-definition-0.png - Mouse Over ${MENU JUMP} - Capture Page Screenshot 02-jump-to-definition-1.png - Click Element ${MENU JUMP} - Wait Until Keyword Succeeds 10 x 1 s Cursor Should Jump ${cursor} - Capture Page Screenshot 02-jump-to-definition-2.png - -Cursor Should Jump - [Arguments] ${original} - ${current} = Measure Cursor Position - Should Not Be Equal ${original} ${current} - -Measure Cursor Position - Wait Until Page Contains Element ${CM CURSORS} - ${position} = Wait Until Keyword Succeeds 20 x 0.05s Get Vertical Position ${CM CURSOR} - [Return] ${position} - Editor Content Changed [Arguments] ${old_content} ${new_content} Get Editor Content diff --git a/atest/03_Notebook.robot b/atest/03_Notebook.robot index d31d5d432..17865a775 100644 --- a/atest/03_Notebook.robot +++ b/atest/03_Notebook.robot @@ -33,7 +33,7 @@ Foreign Extractors Code Overrides ${file} = Set Variable Code overrides.ipynb Setup Notebook Python ${file} - ${virtual_path} = Set Variable ${OUTPUT DIR}${/}home${/}.virtual_documents/Code\ overrides.ipynb + ${virtual_path} = Set Variable ${VIRTUALDOCS DIR}${/}Code overrides.ipynb Wait Until Created ${virtual_path} ${document} = Get File ${virtual_path} Should Be Equal ${document} get_ipython().run_line_magic("ls", "")\n\n\nget_ipython().run_line_magic("pip", " freeze")\n diff --git a/atest/04_Interface/DiagnosticsPanel.robot b/atest/04_Interface/DiagnosticsPanel.robot index a7c2d5486..5108a995d 100644 --- a/atest/04_Interface/DiagnosticsPanel.robot +++ b/atest/04_Interface/DiagnosticsPanel.robot @@ -1,6 +1,7 @@ *** Settings *** Suite Setup Setup Suite For Screenshots diagnostics_panel Resource ../Keywords.robot +Force Tags ui:notebook aspect:ls:features Test Setup Set Up Test Teardown Clean Up diff --git a/atest/05_Features/Jump.robot b/atest/05_Features/Jump.robot new file mode 100644 index 000000000..bda39433a --- /dev/null +++ b/atest/05_Features/Jump.robot @@ -0,0 +1,25 @@ +*** Settings *** +Suite Setup Setup Suite For Screenshots gh-403 +Force Tags feature:jump-to-definition gh:403 +Resource ../Keywords.robot + +*** Variables *** +${FOLDER WITH SPACE} a földer + +*** Test Cases *** +Python Jumps between Files + Copy Files to Folder With Spaces jump_a.py jump_b.py + ${def} = Set Variable a_function_definition + Open ${FOLDER WITH SPACE}/jump_b.py in ${MENU EDITOR} + Wait Until Fully Initialized + ${sel} = Set Variable xpath:(//span[contains(@class, 'cm-variable')][contains(text(), '${def}')])[last()] + Jump To Definition ${sel} + Wait Until Page Contains ANOTHER_CONSTANT + Capture Page Screenshot 10-jumped.png + +*** Keywords *** +Copy Files to Folder With Spaces + [Arguments] @{files} + FOR ${file} IN @{files} + Copy File examples${/}${file} ${NOTEBOOK DIR}${/}${FOLDER WITH SPACE}${/}${file} + END diff --git a/atest/06_Style.robot b/atest/06_Style.robot index 54a2bd1ac..09ec1d932 100644 --- a/atest/06_Style.robot +++ b/atest/06_Style.robot @@ -20,8 +20,8 @@ Screenshot Editor Themes with Lab Theme [Arguments] ${lab theme} ${file}=style.py ${notebook}=Diagnostic.ipynb ${norm lab theme} = Set Variable ${lab theme.lower().replace(" ", "-")} Set Tags theme:lab:${norm lab theme} - Set Screenshot Directory ${OUTPUT DIR}${/}style${/}${norm lab theme} - Copy File examples${/}${file} ${OUTPUT DIR}${/}home${/}${file} + Set Screenshot Directory ${SCREENSHOTS DIR}${/}style${/}${norm lab theme} + Copy File examples${/}${file} ${NOTEBOOK DIR}${/}${file} Run Keyword If "${THEME NAMES}" == "" Wait Until Keyword Succeeds 3x 1s Get Theme Names Lab Command Use ${lab theme} Theme Try to Close All Tabs diff --git a/atest/Keywords.robot b/atest/Keywords.robot index 3b8efebb5..11fbb7ad4 100644 --- a/atest/Keywords.robot +++ b/atest/Keywords.robot @@ -4,59 +4,67 @@ Library SeleniumLibrary Library OperatingSystem Library Process Library String +Library Collections Library ./logcheck.py Library ./ports.py +Library ./config.py *** Keywords *** Setup Server and Browser - ${port} = Get Unused Port - Set Global Variable ${PORT} ${port} - Set Global Variable ${URL} http://localhost:${PORT}${BASE} + Initialize Global Variables + Create Notebok Server Config + Initialize User Settings + ${server} = Start Process jupyter-lab + ... cwd=${NOTEBOOK DIR} + ... stdout=${LAB LOG} + ... stderr=STDOUT + ... env:HOME=${HOME} + Set Global Variable ${SERVER} ${server} + Open JupyterLab + Read Page Config + +Initialize Global Variables + ${root} = Normalize Path ${OUTPUT DIR}${/}..${/}..${/}.. + Set Global Variable ${ROOT} ${root} ${accel} = Evaluate "COMMAND" if "${OS}" == "Darwin" else "CTRL" Set Global Variable ${ACCEL} ${accel} ${token} = Generate Random String Set Global Variable ${TOKEN} ${token} - ${home} = Set Variable ${OUTPUT DIR}${/}home - ${root} = Normalize Path ${OUTPUT DIR}${/}..${/}..${/}.. - Create Directory ${home} - Create Notebok Server Config ${home} - Initialize User Settings - ${cmd} = Create Lab Launch Command ${root} - Set Screenshot Directory ${OUTPUT DIR}${/}screenshots - Set Global Variable ${LAB LOG} ${OUTPUT DIR}${/}lab.log Set Global Variable ${PREVIOUS LAB LOG LENGTH} 0 - ${server} = Start Process ${cmd} shell=yes env:HOME=${home} cwd=${home} stdout=${LAB LOG} - ... stderr=STDOUT - Set Global Variable ${SERVER} ${server} - Open JupyterLab + Set Screenshot Directory ${SCREENSHOTS DIR} + +Create Notebok Server Config + [Documentation] Copies in notebook server config file and updates accordingly + ${conf} = Set Variable ${NOTEBOOK DIR}${/}${NBSERVER CONF} + ${extra_node_roots} = Create List ${ROOT} + ${port} = Get Unused Port + Set Global Variable ${PORT} ${port} + Set Global Variable ${URL} http://localhost:${PORT}${BASE URL} + Copy File ${FIXTURES}${/}${NBSERVER CONF} ${conf} + Update Jupyter Config ${conf} LabApp + ... base_url=${BASE URL} + ... port=${PORT} + ... token=${TOKEN} + ... user_settings_dir=${SETTINGS DIR} + ... workspaces_dir=${WORKSPACES DIR} + Update Jupyter Config ${conf} LanguageServerManager + ... extra_node_roots=@{extra_node_roots} + +Read Page Config ${script} = Get Element Attribute id:jupyter-config-data innerHTML ${config} = Evaluate __import__("json").loads("""${script}""") Set Global Variable ${PAGE CONFIG} ${config} Set Global Variable ${LAB VERSION} ${config["appVersion"]} -Create Lab Launch Command - [Arguments] ${root} - [Documentation] Create a JupyterLab CLI shell string, escaping for traitlets - ${WORKSPACES DIR} = Set Variable ${OUTPUT DIR}${/}workspaces - ${app args} = Set Variable --no-browser --debug --NotebookApp.base_url\='${BASE}' --port\=${PORT} --NotebookApp.token\='${TOKEN}' - ${path args} = Set Variable --LabApp.user_settings_dir='${SETTINGS DIR.replace('\\', '\\\\')}' --LabApp.workspaces_dir\='${WORKSPACES DIR.replace('\\', '\\\\')}' - ${ext args} = Set Variable --LanguageServerManager.extra_node_roots\="['${root.replace('\\', '\\\\')}']" - ${cmd} = Set Variable jupyter-lab ${app args} ${path args} ${ext args} - [Return] ${cmd} - -Create Notebok Server Config - [Arguments] ${home} - [Documentation] Copies in notebook server config file to disables npm/build checks - Copy File ${FIXTURES}${/}${NBSERVER CONF} ${home}${/}${NBSERVER CONF} - Setup Suite For Screenshots [Arguments] ${folder} - Set Screenshot Directory ${OUTPUT DIR}${/}screenshots${/}${folder} + Set Screenshot Directory ${SCREENSHOTS DIR}${/}${folder} Set Tags lab:${LAB VERSION} Initialize User Settings - Set Suite Variable ${SETTINGS DIR} ${OUTPUT DIR}${/}user-settings children=${True} - Create File ${SETTINGS DIR}${/}@jupyterlab${/}codemirror-extension${/}commands.jupyterlab-settings {"styleActiveLine": true} + Create File + ... ${SETTINGS DIR}${/}@jupyterlab${/}codemirror-extension${/}commands.jupyterlab-settings + ... {"styleActiveLine": true} Reset Plugin Settings [Arguments] ${package}=jupyterlab-lsp ${plugin}=plugin @@ -91,7 +99,7 @@ Open JupyterLab Create WebDriver Firefox ... executable_path=${geckodriver} ... firefox_binary=${firefox} - ... service_log_path=${OUTPUT DIR}${/}geckodriver.log + ... service_log_path=${GECKODRIVER LOG} ... service_args=${service args} Wait Until Keyword Succeeds 3x 5s Wait For Splash @@ -191,7 +199,7 @@ Ensure Sidebar Is Closed Open Context Menu for File [Arguments] ${file} Ensure File Browser is Open - Click Element css:button[title="Refresh File List"] + Click Element ${JLAB CSS REFRESH FILES} ${selector} = Set Variable xpath://span[@class='jp-DirListing-itemText']\[text() = '${file}'] Wait Until Page Contains Element ${selector} Open Context Menu ${selector} @@ -212,7 +220,19 @@ Input Into Dialog Input Text ${DIALOG INPUT} ${text} Click Element ${DIALOG ACCEPT} +Open Folder + [Arguments] @{paths} + Click Element ${JLAB CSS REFRESH FILES} + FOR ${path} IN @{paths} + ${sel} = Set Variable css:li.jp-DirListing-item\[title^='Name: ${path}'] + Wait Until Page Contains Element ${sel} + Double Click Element ${sel} + END + Open ${file} in ${editor} + ${paths} = Set Variable ${file.split("/")} + Run Keyword If ${paths.__len__() > 1} Open Folder @{paths[:-1]} + ${file} = Set Variable ${paths[-1]} Open Context Menu for File ${file} Mouse Over ${MENU OPEN WITH} Wait Until Page Contains Element ${editor} @@ -221,15 +241,15 @@ Open ${file} in ${editor} Clean Up After Working With File [Arguments] ${file} - Remove File ${OUTPUT DIR}${/}home${/}${file} + Remove File ${NOTEBOOK DIR}${/}${file} Reset Application State Lab Log Should Not Contain Known Error Messages Setup Notebook [Arguments] ${Language} ${file} ${isolated}=${True} Set Tags language:${Language.lower()} - Run Keyword If ${isolated} Set Screenshot Directory ${OUTPUT DIR}${/}screenshots${/}notebook${/}${TEST NAME.replace(' ', '_')} - Copy File examples${/}${file} ${OUTPUT DIR}${/}home${/}${file} + Run Keyword If ${isolated} Set Screenshot Directory ${SCREENSHOTS DIR}${/}notebook${/}${TEST NAME.replace(' ', '_')} + Copy File examples${/}${file} ${NOTEBOOK DIR}${/}${file} Run Keyword If ${isolated} Try to Close All Tabs Open ${file} in ${MENU NOTEBOOK} Capture Page Screenshot 00-notebook-opened.png @@ -284,13 +304,13 @@ Open Context Menu Over Prepare File for Editing [Arguments] ${Language} ${Screenshots} ${file} Set Tags language:${Language.lower()} - Set Screenshot Directory ${OUTPUT DIR}${/}screenshots${/}${Screenshots}${/}${Language.lower()} + Set Screenshot Directory ${SCREENSHOTS DIR}${/}${Screenshots}${/}${Language.lower()} Try to Close All Tabs Open File ${file} Open File [Arguments] ${file} - Copy File examples${/}${file} ${OUTPUT DIR}${/}home${/}${file} + Copy File examples${/}${file} ${NOTEBOOK DIR}${/}${file} Open ${file} in ${MENU EDITOR} Capture Page Screenshot 00-opened.png @@ -323,3 +343,31 @@ Clean Up After Working with File and Settings [Arguments] ${file} Clean Up After Working With File ${file} Reset Plugin Settings + +Jump To Definition + [Arguments] ${symbol} + ${sel} = Set Variable If "${symbol}".startswith(("xpath", "css")) ${symbol} xpath:(//span[@role="presentation"][contains(., "${symbol}")])[last()] + Open Context Menu Over ${sel} + ${cursor} = Measure Cursor Position + Capture Page Screenshot 02-jump-to-definition-0.png + Mouse Over ${MENU JUMP} + Capture Page Screenshot 02-jump-to-definition-1.png + Click Element ${MENU JUMP} + [Return] ${cursor} + +Editor Should Jump To Definition + [Arguments] ${symbol} + Set Tags feature:jump-to-definition + ${cursor} = Jump To Definition ${symbol} + Wait Until Keyword Succeeds 10 x 1 s Cursor Should Jump ${cursor} + Capture Page Screenshot 02-jump-to-definition-2.png + +Cursor Should Jump + [Arguments] ${original} + ${current} = Measure Cursor Position + Should Not Be Equal ${original} ${current} + +Measure Cursor Position + Wait Until Page Contains Element ${CM CURSORS} + ${position} = Wait Until Keyword Succeeds 20 x 0.05s Get Vertical Position ${CM CURSOR} + [Return] ${position} diff --git a/atest/Variables.robot b/atest/Variables.robot index eb69d1c05..6497b91bb 100644 --- a/atest/Variables.robot +++ b/atest/Variables.robot @@ -2,8 +2,18 @@ ${FIXTURES} ${CURDIR}${/}fixtures ${NBSERVER CONF} jupyter_notebook_config.json ${SPLASH} id:jupyterlab-splash -# to help catch hard-coded paths -${BASE} /@est/ +# to help catch hard-coded paths and encoding issues +${BASE URL} /@est/ +${NOTEBOOK DIR NAME} nöte bòóks +# core paths +${HOME} ${OUTPUT DIR}${/}home +${LAB LOG} ${OUTPUT DIR}${/}lab.log +${GECKODRIVER LOG} ${OUTPUT DIR}${/}geckodriver.log +${SETTINGS DIR} ${OUTPUT DIR}${/}user-settings +${WORKSPACES DIR} ${OUTPUT DIR}${/}workspaces +${NOTEBOOK DIR} ${HOME}${/}${NOTEBOOK DIR NAME} +${VIRTUALDOCS DIR} ${NOTEBOOK DIR}${/}.virtual_documents +${SCREENSHOTS DIR} ${OUTPUT DIR}${/}screenshots # override with `python scripts/atest.py --variable HEADLESS:0` ${HEADLESS} 1 ${CMD PALETTE INPUT} css:#command-palette .lm-CommandPalette-input @@ -13,6 +23,7 @@ ${JLAB XP MENU ITEM LABEL} //div[@class='lm-Menu-itemLabel'] ${JLAB XP MENU LABEL} //div[@class='lm-MenuBar-itemLabel'] ${JLAB XP DOCK TAB} xpath://div[contains(@class, 'lm-DockPanel-tabBar')]//li[contains(@class, 'lm-TabBar-tab')] ${JLAB CSS VERSION} css:.jp-About-version +${JLAB CSS REFRESH FILES} css:button[title="Refresh File List"] ${CSS DIALOG OK} css:.jp-Dialog .jp-mod-accept ${MENU OPEN WITH} xpath://div[contains(@class, 'lm-Menu-itemLabel')][contains(text(), "Open With")] # R is missing on purpose (may need to use .) @@ -30,7 +41,7 @@ ${MENU JUMP} xpath://div[contains(@class, 'lm-Menu-itemLabel')][contains(te ${MENU SETTINGS} xpath://div[contains(@class, 'lm-MenuBar-itemLabel')][contains(text(), "Settings")] ${MENU EDITOR THEME} xpath://div[contains(@class, 'lm-Menu-itemLabel')][contains(text(), "Text Editor Theme")] ${CM CURSOR} css:.CodeMirror-cursor -${CM CURSORS} css:.CodeMirror-cursors:not([style='visibility: hidden']) +${CM CURSORS} css:.jp-MainAreaWidget:not(.lm-mod-hidden) .CodeMirror-cursors:not([style='visibility: hidden']) # settings ${LSP PLUGIN ID} @krassowski/jupyterlab-lsp:plugin ${COMPLETION PLUGIN ID} @krassowski/jupyterlab-lsp:completion diff --git a/atest/config.py b/atest/config.py new file mode 100644 index 000000000..39e3b788a --- /dev/null +++ b/atest/config.py @@ -0,0 +1,17 @@ +"""work with jupyter config""" + +import json +from pathlib import Path + +ENC = dict(encoding="utf-8") + + +def update_jupyter_config(path, has_traits, **key_values): + """update an existing jupyter_notebook_config.json""" + p = Path(path) + conf = json.loads(p.read_text(**ENC)) + + for key, value in key_values.items(): + conf.setdefault(has_traits, {})[key] = value + + p.write_text(json.dumps(conf, indent=2, sort_keys=True), **ENC) diff --git a/atest/examples/jump_a.py b/atest/examples/jump_a.py new file mode 100644 index 000000000..09bc0025a --- /dev/null +++ b/atest/examples/jump_a.py @@ -0,0 +1,5 @@ +A_CONSTANT = "constant" +ANOTHER_CONSTANT = "another constant" + +def a_function_definition(n: int=1) -> str: + return A_CONSTANT * n diff --git a/atest/examples/jump_b.py b/atest/examples/jump_b.py new file mode 100644 index 000000000..44c3e8c34 --- /dev/null +++ b/atest/examples/jump_b.py @@ -0,0 +1,4 @@ +from jump_a import a_function_definition, A_CONSTANT + + +assert a_function_definition() == A_CONSTANT diff --git a/atest/fixtures/jupyter_notebook_config.json b/atest/fixtures/jupyter_notebook_config.json index fef418cd8..b4c1663c7 100644 --- a/atest/fixtures/jupyter_notebook_config.json +++ b/atest/fixtures/jupyter_notebook_config.json @@ -5,6 +5,8 @@ "buildCheck": false, "buildAvailable": false } - } + }, + "log_level": "DEBUG", + "open_browser": false } } diff --git a/packages/jupyterlab-go-to-definition/src/jump.ts b/packages/jupyterlab-go-to-definition/src/jump.ts index 538cbc52e..602939245 100644 --- a/packages/jupyterlab-go-to-definition/src/jump.ts +++ b/packages/jupyterlab-go-to-definition/src/jump.ts @@ -37,5 +37,8 @@ export interface IGlobalJump { line: number; column: number; - uri: string; + /** + * The Jupyter ContentsManager path, _not_ passed through encode URI. + */ + contents_path: string; } diff --git a/packages/jupyterlab-go-to-definition/src/jumpers/jumper.ts b/packages/jupyterlab-go-to-definition/src/jumpers/jumper.ts index 4579b3081..23828d2fa 100644 --- a/packages/jupyterlab-go-to-definition/src/jumpers/jumper.ts +++ b/packages/jupyterlab-go-to-definition/src/jumpers/jumper.ts @@ -197,7 +197,9 @@ export abstract class CodeJumper { } public global_jump(position: IGlobalJump, is_symlink = false) { - let document_widget = this.document_manager.openOrReveal(position.uri); + let document_widget = this.document_manager.openOrReveal( + position.contents_path + ); document_widget.revealed .then(() => { @@ -285,7 +287,7 @@ export abstract class CodeJumper { editor_index: input_number, line: line_number, column: column, - uri: path + contents_path: path }, is_symlink ); diff --git a/packages/jupyterlab-lsp/src/editor_integration/codemirror.ts b/packages/jupyterlab-lsp/src/editor_integration/codemirror.ts index 9ef499071..9bc2417dc 100644 --- a/packages/jupyterlab-lsp/src/editor_integration/codemirror.ts +++ b/packages/jupyterlab-lsp/src/editor_integration/codemirror.ts @@ -281,11 +281,7 @@ export abstract class CodeMirrorIntegration if (!uris_equal(uri, current_uri)) { errors.push( - 'Workspace-wide edits not implemented (' + - decodeURI(uri) + - ' != ' + - decodeURI(current_uri) + - ')' + `Workspace-wide edits not implemented: ${uri} != ${current_uri}` ); } else { is_whole_document_edit = diff --git a/packages/jupyterlab-lsp/src/features/jump_to.ts b/packages/jupyterlab-lsp/src/features/jump_to.ts index 6ddddade5..08d9a2d59 100644 --- a/packages/jupyterlab-lsp/src/features/jump_to.ts +++ b/packages/jupyterlab-lsp/src/features/jump_to.ts @@ -22,6 +22,7 @@ import { IDocumentManager } from '@jupyterlab/docmanager'; import { ILSPAdapterManager, ILSPFeatureManager, PLUGIN_ID } from '../tokens'; import { LabIcon } from '@jupyterlab/ui-components'; import jumpToSvg from '../../style/icons/jump-to.svg'; +import { URLExt } from '@jupyterlab/coreutils'; export const jumpToIcon = new LabIcon({ name: 'lsp:jump-to', @@ -57,12 +58,12 @@ export class CMJumpToDefinition extends CodeMirrorIntegration { if ('targetUri' in location_or_link) { return { - uri: decodeURI(location_or_link.targetUri), + uri: location_or_link.targetUri, range: location_or_link.targetRange }; } else if ('uri' in location_or_link) { return { - uri: decodeURI(location_or_link.uri), + uri: location_or_link.uri, range: location_or_link.range }; } @@ -105,12 +106,10 @@ export class CMJumpToDefinition extends CodeMirrorIntegration { console.log('Jump target (source location):', source_position_ce); // can it be resolved vs our guessed server root? - const contents_path = uri_to_contents_path(uri); + let contents_path = uri_to_contents_path(uri); - if (contents_path) { - uri = contents_path; - } else if (uri.startsWith('file://')) { - uri = uri.slice(7); + if (contents_path == null && uri.startsWith('file://')) { + contents_path = decodeURI(uri.slice(7)); } let jump_data = { @@ -124,17 +123,23 @@ export class CMJumpToDefinition extends CodeMirrorIntegration { // with different OSes but also with JupyterHub and other platforms. try { - await this.jumper.document_manager.services.contents.get(uri, { - content: false - }); - this.jumper.global_jump({ uri, ...jump_data }, false); + await this.jumper.document_manager.services.contents.get( + contents_path, + { + content: false + } + ); + this.jumper.global_jump({ contents_path, ...jump_data }, false); return; } catch (err) { console.warn(err); } this.jumper.global_jump( - { uri: '.lsp_symlink/' + uri, ...jump_data }, + { + contents_path: URLExt.join('.lsp_symlink', contents_path), + ...jump_data + }, true ); } diff --git a/packages/jupyterlab-lsp/src/utils.ts b/packages/jupyterlab-lsp/src/utils.ts index 9ae4ffd73..fe4496d5d 100644 --- a/packages/jupyterlab-lsp/src/utils.ts +++ b/packages/jupyterlab-lsp/src/utils.ts @@ -136,7 +136,7 @@ export function uri_to_contents_path(child: string, parent?: string) { return null; } if (child.startsWith(parent)) { - return child.replace(parent, ''); + return decodeURI(child.replace(parent, '')); } return null; } diff --git a/packages/jupyterlab-lsp/src/virtual/document.ts b/packages/jupyterlab-lsp/src/virtual/document.ts index c831fc8d1..5627d0196 100644 --- a/packages/jupyterlab-lsp/src/virtual/document.ts +++ b/packages/jupyterlab-lsp/src/virtual/document.ts @@ -735,10 +735,11 @@ export class VirtualDocument { } get uri(): string { + const encodedPath = encodeURI(this.path); if (!this.parent) { - return this.path; + return encodedPath; } - return this.path + '.' + this.id_path + '.' + this.file_extension; + return encodedPath + '.' + this.id_path + '.' + this.file_extension; } transform_source_to_editor(pos: ISourcePosition): IEditorPosition { diff --git a/py_src/jupyter_lsp/schema/__init__.py b/py_src/jupyter_lsp/schema/__init__.py index 45bd976c7..321efb900 100644 --- a/py_src/jupyter_lsp/schema/__init__.py +++ b/py_src/jupyter_lsp/schema/__init__.py @@ -5,7 +5,7 @@ HERE = pathlib.Path(__file__).parent SCHEMA_FILE = HERE / "schema.json" -SCHEMA = json.loads(SCHEMA_FILE.read_text()) +SCHEMA = json.loads(SCHEMA_FILE.read_text(encoding="utf-8")) SPEC_VERSION = SCHEMA["definitions"]["current-version"]["enum"][0] diff --git a/py_src/jupyter_lsp/specs/config/__init__.py b/py_src/jupyter_lsp/specs/config/__init__.py index 6fe698272..5075590cc 100644 --- a/py_src/jupyter_lsp/specs/config/__init__.py +++ b/py_src/jupyter_lsp/specs/config/__init__.py @@ -6,4 +6,6 @@ def load_config_schema(key): """load a keyed filename""" - return json.loads((CONFIGS / "{}.schema.json".format(key)).read_text()) + return json.loads( + (CONFIGS / "{}.schema.json".format(key)).read_text(encoding="utf-8") + ) diff --git a/py_src/jupyter_lsp/virtual_documents_shadow.py b/py_src/jupyter_lsp/virtual_documents_shadow.py index e0bd8a752..d24c9c5de 100644 --- a/py_src/jupyter_lsp/virtual_documents_shadow.py +++ b/py_src/jupyter_lsp/virtual_documents_shadow.py @@ -40,6 +40,7 @@ def read_lines(self): # empty string required by the assumptions of the gluing algorithm lines = [""] try: + # TODO: what to do about bad encoding reads? lines = self.path.read_text().splitlines() except FileNotFoundError: pass diff --git a/scripts/atest.py b/scripts/atest.py index ac93b45e2..5c1e4f2b0 100644 --- a/scripts/atest.py +++ b/scripts/atest.py @@ -51,6 +51,7 @@ def get_stem(attempt, extra_args): def atest(attempt, extra_args): """perform a single attempt of the acceptance tests""" + # TODO: investigate whether this is still required vs geckodriver 0.28 if "FIREFOX_BINARY" not in os.environ: os.environ["FIREFOX_BINARY"] = shutil.which("firefox") diff --git a/scripts/bump_versions.py b/scripts/bump_versions.py index 95eae63bd..2620a479e 100755 --- a/scripts/bump_versions.py +++ b/scripts/bump_versions.py @@ -51,7 +51,7 @@ def maybe_change_version(self, dry: bool): def change_version(self, new_version: str, dry: bool): - changelog = CHANGELOG.read_text() + changelog = CHANGELOG.read_text(encoding="utf-8") if new_version not in changelog: raise Exception( ( @@ -71,7 +71,7 @@ def change_version(self, new_version: str, dry: bool): def replace_version(path: Path, template: str, old: str, new: str, dry: bool): - old_content = path.read_text() + old_content = path.read_text(encoding="utf-8") new_content = old_content.replace( template.format(version=old), template.format(version=new) ) diff --git a/scripts/integrity.py b/scripts/integrity.py index 9e97654ef..7b2883f3c 100644 --- a/scripts/integrity.py +++ b/scripts/integrity.py @@ -44,7 +44,7 @@ PACKAGES = { package["name"]: [path.parent, package] for path, package in [ - (path, json.loads(path.read_text())) + (path, json.loads(path.read_text(encoding="utf-8"))) for path in ROOT.glob("packages/*/package.json") ] } @@ -63,7 +63,7 @@ # CI stuff PIPE_FILE = ROOT / ".github/workflows/job.test.yml" -PIPELINES = yaml.safe_load(PIPE_FILE.read_text()) +PIPELINES = yaml.safe_load(PIPE_FILE.read_text(encoding="utf-8")) PIPE_VARS = PIPELINES["env"] DOCS = ROOT / "docs" @@ -82,8 +82,8 @@ def the_meta_package(): return ( meta_path, meta, - json.loads((meta_path / "tsconfig.json").read_text()), - (meta_path / "src" / "index.ts").read_text(), + json.loads((meta_path / "tsconfig.json").read_text(encoding="utf-8")), + (meta_path / "src" / "index.ts").read_text(encoding="utf-8"), ) @@ -139,7 +139,7 @@ def test_ts_package_integrity(name, info, the_meta_package): if schemas: for schema in schemas: - schema_instance = json.loads(schema.read_text()) + schema_instance = json.loads(schema.read_text(encoding="utf-8")) jsonschema.validators.Draft7Validator(schema_instance) @@ -158,7 +158,7 @@ def test_ts_package_integrity(name, info, the_meta_package): def test_jlab_versions(path): """is the version of jupyterlab consistent?""" assert ( - LAB_SPEC in pathlib.Path(path).read_text().lower() + LAB_SPEC in pathlib.Path(path).read_text(encoding="utf-8").lower() ), "{} lab version is out-of-sync vs {}".format(path, LAB_SPEC) @@ -172,7 +172,7 @@ def test_jlab_versions(path): ) def test_changelog_versions(pkg, version): """are the current versions represented in the changelog?""" - assert "## `{} {}`".format(pkg, version) in CHANGELOG.read_text() + assert "## `{} {}`".format(pkg, version) in CHANGELOG.read_text(encoding="utf-8") @pytest.mark.parametrize( diff --git a/scripts/nblint.py b/scripts/nblint.py index f4c345585..b9dc3c201 100644 --- a/scripts/nblint.py +++ b/scripts/nblint.py @@ -27,7 +27,7 @@ def blacken(source): def nblint(): for nb_path in DOCS_IPYNB: print(".", end="", flush=True) - nb_text = nb_path.read_text() + nb_text = nb_path.read_text(encoding="utf-8") nb_node = nbformat.reads(nb_text, 4) changes = 0 has_empty = 0 diff --git a/scripts/tectonic_cache.py b/scripts/tectonic_cache.py index cdde9cc88..4b200457f 100644 --- a/scripts/tectonic_cache.py +++ b/scripts/tectonic_cache.py @@ -19,7 +19,7 @@ def tectonic_cache(): "\n".join( [ line - for line in EXAMPLE.read_text().splitlines() + for line in EXAMPLE.read_text(encoding="utf-8").splitlines() if "\\foo" not in line ] ) diff --git a/setup.py b/setup.py index df624f09a..24125168e 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( version=re.findall( r"""__version__ = "([^"]+)"$""", - (Path(__file__).parent / "py_src" / "jupyter_lsp" / "_version.py").read_text(), + (Path(__file__).parent / "py_src" / "jupyter_lsp" / "_version.py").read_text(encoding="utf-8"), )[0], setup_requires=["pytest-runner"] if "test" in sys.argv else [], # py35 apparently doesn't support putting these in setup.cfg diff --git a/versions.py b/versions.py index 9db8b1d86..8c12ecce0 100644 --- a/versions.py +++ b/versions.py @@ -6,7 +6,7 @@ _VERSION_PY = ROOT / "py_src" / "jupyter_lsp" / "_version.py" -JUPYTER_LSP_VERSION = findall(r'= "(.*)"$', (_VERSION_PY).read_text())[0] +JUPYTER_LSP_VERSION = findall(r'= "(.*)"$', (_VERSION_PY).read_text(encoding="utf-8"))[0] with open(ROOT / "packages/jupyterlab-lsp/package.json") as f: jupyterlab_lsp_package = json.load(f)