Merge branch 'master' into resolve-107208

This commit is contained in:
Rob Lourens 2020-10-05 17:14:03 -07:00 committed by GitHub
commit b24e3ecfed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
211 changed files with 2637 additions and 2400 deletions

View file

@ -1,122 +0,0 @@
#-------------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#-------------------------------------------------------------------------------------------------------------
FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-12
ARG TARGET_DISPLAY=":1"
# VNC options
ARG MAX_VNC_RESOLUTION=1920x1080x16
ARG TARGET_VNC_RESOLUTION=1920x1080
ARG TARGET_VNC_DPI=72
ARG TARGET_VNC_PORT=5901
ARG VNC_PASSWORD="vscode"
# noVNC (VNC web client) options
ARG INSTALL_NOVNC="true"
ARG NOVNC_VERSION=1.1.0
ARG TARGET_NOVNC_PORT=6080
ARG WEBSOCKETIFY_VERSION=0.9.0
# Firefox is useful for testing things like browser launch events, but optional
ARG INSTALL_FIREFOX="false"
# Expected non-root username from base image
ARG USERNAME=node
# Core environment variables for X11, VNC, and fluxbox
ENV DBUS_SESSION_BUS_ADDRESS="autolaunch:" \
MAX_VNC_RESOLUTION="${MAX_VNC_RESOLUTION}" \
VNC_RESOLUTION="${TARGET_VNC_RESOLUTION}" \
VNC_DPI="${TARGET_VNC_DPI}" \
VNC_PORT="${TARGET_VNC_PORT}" \
NOVNC_PORT="${TARGET_NOVNC_PORT}" \
DISPLAY="${TARGET_DISPLAY}" \
LANG="en_US.UTF-8" \
LANGUAGE="en_US.UTF-8" \
VISUAL="nano" \
EDITOR="nano"
# Configure apt and install packages
RUN apt-get update \
&& export DEBIAN_FRONTEND=noninteractive \
#
# Install the Cascadia Code fonts - https://github.com/microsoft/cascadia-code
&& curl -sSL https://github.com/microsoft/cascadia-code/releases/download/v2004.30/CascadiaCode_2004.30.zip -o /tmp/cascadia-fonts.zip \
&& unzip /tmp/cascadia-fonts.zip -d /tmp/cascadia-fonts \
&& mkdir -p /usr/share/fonts/truetype/cascadia \
&& mv /tmp/cascadia-fonts/ttf/* /usr/share/fonts/truetype/cascadia/ \
&& rm -rf /tmp/cascadia-fonts.zip /tmp/cascadia-fonts \
#
# Install X11, fluxbox and VS Code dependencies
&& apt-get -y install --no-install-recommends \
xvfb \
x11vnc \
fluxbox \
dbus-x11 \
x11-utils \
x11-xserver-utils \
xdg-utils \
fbautostart \
xterm \
eterm \
gnome-terminal \
gnome-keyring \
seahorse \
nautilus \
libx11-dev \
libxkbfile-dev \
libsecret-1-dev \
libnotify4 \
libnss3 \
libxss1 \
libasound2 \
libgbm1 \
xfonts-base \
xfonts-terminus \
fonts-noto \
fonts-wqy-microhei \
fonts-droid-fallback \
vim-tiny \
nano \
#
# [Optional] Install noVNC
&& if [ "${INSTALL_NOVNC}" = "true" ]; then \
mkdir -p /usr/local/novnc \
&& curl -sSL https://github.com/novnc/noVNC/archive/v${NOVNC_VERSION}.zip -o /tmp/novnc-install.zip \
&& unzip /tmp/novnc-install.zip -d /usr/local/novnc \
&& cp /usr/local/novnc/noVNC-${NOVNC_VERSION}/vnc_lite.html /usr/local/novnc/noVNC-${NOVNC_VERSION}/index.html \
&& rm /tmp/novnc-install.zip \
&& curl -sSL https://github.com/novnc/websockify/archive/v${WEBSOCKETIFY_VERSION}.zip -o /tmp/websockify-install.zip \
&& unzip /tmp/websockify-install.zip -d /usr/local/novnc \
&& apt-get -y install --no-install-recommends python-numpy \
&& ln -s /usr/local/novnc/websockify-${WEBSOCKETIFY_VERSION} /usr/local/novnc/noVNC-${NOVNC_VERSION}/utils/websockify \
&& rm /tmp/websockify-install.zip; \
fi \
#
# [Optional] Install Firefox
&& if [ "${INSTALL_FIREFOX}" = "true" ]; then \
apt-get -y install --no-install-recommends firefox-esr; \
fi \
#
# Clean up
&& apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
COPY bin/init-dev-container.sh /usr/local/share/
COPY bin/set-resolution /usr/local/bin/
COPY fluxbox/* /root/.fluxbox/
COPY fluxbox/* /home/${USERNAME}/.fluxbox/
# Update privs, owners of config files
RUN mkdir -p /var/run/dbus /root/.vnc /home/${USERNAME}/.vnc \
&& touch /root/.Xmodmap /home/${USERNAME}/.Xmodmap \
&& echo "${VNC_PASSWORD}" | tee /root/.vnc/passwd > /home/${USERNAME}/.vnc/passwd \
&& chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.Xmodmap /home/${USERNAME}/.fluxbox /home/${USERNAME}/.vnc \
&& chmod +x /usr/local/share/init-dev-container.sh /usr/local/bin/set-resolution
ENTRYPOINT ["/usr/local/share/init-dev-container.sh"]
CMD ["sleep", "infinity"]

View file

@ -1,8 +1,8 @@
# Code - OSS Development Container
This repository includes configuration for a development container for working with Code - OSS in an isolated local container or using [Visual Studio Codespaces](https://aka.ms/vso).
This repository includes configuration for a development container for working with Code - OSS in an isolated local container or using [GitHub Codespaces](https://github.com/features/codespaces).
> **Tip:** The default VNC password is `vscode`. The VNC server runs on port `5901` with a web client at `6080`. For better performance, we recommend using a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/). Applications like the macOS Screen Sharing app will not perform as well. [Chicken](https://sourceforge.net/projects/chicken/) is a good macOS alternative.
> **Tip:** The default VNC password is `vscode`. The VNC server runs on port `5901` with a web client at `6080`. For better performance, we recommend using a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/). Applications like the macOS Screen Sharing app will not perform as well.
## Quick start - local
@ -30,25 +30,41 @@ Anything you start in VS Code or the integrated terminal will appear here.
Next: **[Try it out!](#try-it)**
## Quick start - Codespaces
## Quick start - GitHub Codespaces
>Note that the Codespaces browser-based editor cannot currently access the desktop environment in this container (due to a [missing feature](https://github.com/MicrosoftDocs/vsonline/issues/117)). We recommend using Visual Studio Code from the desktop to connect instead in the near term.
> **IMPORTANT:** The current user beta for GitHub Codespaces uses a "Basic" sized codespace which is too small to run a full build of VS Code. You'll soon be able to use a "Standard" sized codespace (4-core, 8GB) that will be better suited for this purpose.
1. Install [Visual Studio Code Stable](https://code.visualstudio.com/) or [Insiders](https://code.visualstudio.com/insiders/) and the [Visual Studio Codespaces](https://aka.ms/vscs-ext-vscode) extension.
1. From the [microsoft/vscode GitHub repository](https://github.com/microsoft/vscode), click on the **Code** dropdown, select **Open with Codespaces**, and the **New codespace**
![Image of VS Codespaces extension](https://microsoft.github.io/vscode-remote-release/images/codespaces-extn.png)
> Note that you will not see these options if you are not in the beta yet.
> Note that the Visual Studio Codespaces extension requires the Visual Studio Code distribution of Code - OSS.
2. After the codespace is up and running in your browser, press <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> and select **View: Show Remote Explorer**.
2. Sign in by pressing <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> and selecting **Codespaces: Sign In**. You may also need to use the **Codespaces: Create Plan** if you do not have a plan. See the [Codespaces docs](https://aka.ms/vso-docs/vscode) for details.
3. You should see port `6080` under **Forwarded Ports**. Select the line and click on the globe icon to open it in a browser tab.
3. Press <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> and select **Codespaces: Create New Codespace**.
> If you do not see port `6080`, press <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd>, select **Forward a Port** and enter port `6080`.
4. Use default settings (which should include **Standard** 4 core, 8 GB RAM Codespace), select a plan, and then enter the repository URL `https://github.com/microsoft/vscode` (or a branch or PR URL) in the input box when prompted.
4. In the new tab, you should see noVNC. Click **Connect** and enter `vscode` as the password.
5. After the container is running, open a web browser and go to [http://localhost:6080](http://localhost:6080) or use a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/) to connect to `localhost:5901` and enter `vscode` as the password.
Anything you start in VS Code or the integrated terminal will appear here.
6. Anything you start in VS Code or the integrated terminal will appear here.
Next: **[Try it out!](#try-it)**
### Using VS Code with GitHub Codespaces
You will likely see better performance when accessing the codespace you created from VS Code since you can use a[VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/). Here's how to do it.
1. [Create a codespace](#quick-start---github-codespaces) if you have not already.
2. Set up [VS Code for use with GitHub Codespaces](https://docs.github.com/github/developing-online-with-codespaces/using-codespaces-in-visual-studio-code)
3. After the VS Code is up and running, press <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd>, choose **Codespaces: Connect to Codespace**, and select the codespace you created.
4. After you've connected to the codespace, use a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/) to connect to `localhost:5901` and enter `vscode` as the password.
5. Anything you start in VS Code or the integrated terminal will appear here.
Next: **[Try it out!](#try-it)**
## Try it!

View file

@ -1,91 +0,0 @@
#!/bin/bash
NONROOT_USER=node
LOG=/tmp/container-init.log
# Execute the command it not already running
startInBackgroundIfNotRunning()
{
log "Starting $1."
echo -e "\n** $(date) **" | sudoIf tee -a /tmp/$1.log > /dev/null
if ! pidof $1 > /dev/null; then
keepRunningInBackground "$@"
while ! pidof $1 > /dev/null; do
sleep 1
done
log "$1 started."
else
echo "$1 is already running." | sudoIf tee -a /tmp/$1.log > /dev/null
log "$1 is already running."
fi
}
# Keep command running in background
keepRunningInBackground()
{
($2 sh -c "while :; do echo [\$(date)] Process started.; $3; echo [\$(date)] Process exited!; sleep 5; done 2>&1" | sudoIf tee -a /tmp/$1.log > /dev/null & echo "$!" | sudoIf tee /tmp/$1.pid > /dev/null)
}
# Use sudo to run as root when required
sudoIf()
{
if [ "$(id -u)" -ne 0 ]; then
sudo "$@"
else
"$@"
fi
}
# Use sudo to run as non-root user if not already running
sudoUserIf()
{
if [ "$(id -u)" -eq 0 ]; then
sudo -u ${NONROOT_USER} "$@"
else
"$@"
fi
}
# Log messages
log()
{
echo -e "[$(date)] $@" | sudoIf tee -a $LOG > /dev/null
}
log "** SCRIPT START **"
# Start dbus.
log 'Running "/etc/init.d/dbus start".'
if [ -f "/var/run/dbus/pid" ] && ! pidof dbus-daemon > /dev/null; then
sudoIf rm -f /var/run/dbus/pid
fi
sudoIf /etc/init.d/dbus start 2>&1 | sudoIf tee -a /tmp/dbus-daemon-system.log > /dev/null
while ! pidof dbus-daemon > /dev/null; do
sleep 1
done
# Set up Xvfb.
startInBackgroundIfNotRunning "Xvfb" sudoIf "Xvfb ${DISPLAY:-:1} +extension RANDR -screen 0 ${MAX_VNC_RESOLUTION:-1920x1080x16}"
# Start fluxbox as a light weight window manager.
startInBackgroundIfNotRunning "fluxbox" sudoUserIf "dbus-launch startfluxbox"
# Start x11vnc
startInBackgroundIfNotRunning "x11vnc" sudoIf "x11vnc -display ${DISPLAY:-:1} -rfbport ${VNC_PORT:-5901} -localhost -no6 -xkb -shared -forever -passwdfile $HOME/.vnc/passwd"
# Set resolution
/usr/local/bin/set-resolution ${VNC_RESOLUTION:-1280x720} ${VNC_DPI:-72}
# Spin up noVNC if installed and not runnning.
if [ -d "/usr/local/novnc" ] && [ "$(ps -ef | grep /usr/local/novnc/noVNC*/utils/launch.sh | grep -v grep)" = "" ]; then
keepRunningInBackground "noVNC" sudoIf "/usr/local/novnc/noVNC*/utils/launch.sh --listen ${NOVNC_PORT:-6080} --vnc localhost:${VNC_PORT:-5901}"
log "noVNC started."
else
log "noVNC is already running or not installed."
fi
# Run whatever was passed in
log "Executing \"$@\"."
"$@"
log "** SCRIPT EXIT **"

View file

@ -1,25 +0,0 @@
#!/bin/bash
RESOLUTION=${1:-${VNC_RESOLUTION:-1920x1080}}
DPI=${2:-${VNC_DPI:-72}}
if [ -z "$1" ]; then
echo -e "**Current Settings **\n"
xrandr
echo -n -e "\nEnter new resolution (WIDTHxHEIGHT, blank for ${RESOLUTION}, Ctrl+C to abort).\n> "
read NEW_RES
if [ "${NEW_RES}" != "" ]; then
RESOLUTION=${NEW_RES}
fi
if [ -z "$2" ]; then
echo -n -e "\nEnter new DPI (blank for ${DPI}, Ctrl+C to abort).\n> "
read NEW_DPI
if [ "${NEW_DPI}" != "" ]; then
DPI=${NEW_DPI}
fi
fi
fi
xrandr --fb ${RESOLUTION} --dpi ${DPI} > /dev/null 2>&1
echo -e "\n**New Settings **\n"
xrandr
echo

View file

@ -1,42 +1,26 @@
{
"name": "Code - OSS",
"build": {
"dockerfile": "Dockerfile",
"args": {
"MAX_VNC_RESOLUTION": "1920x1080x16",
"TARGET_VNC_RESOLUTION": "1280x768",
"TARGET_VNC_PORT": "5901",
"TARGET_NOVNC_PORT": "6080",
"VNC_PASSWORD": "vscode",
"INSTALL_FIREFOX": "true"
}
},
// Image contents: https://github.com/microsoft/vscode-dev-containers/blob/master/repository-containers/images/github.com/microsoft/vscode/.devcontainer/base.Dockerfile
"image": "mcr.microsoft.com/vscode/devcontainers/repos/microsoft/vscode:dev",
"workspaceMount": "source=${localWorkspaceFolder},target=/home/node/workspace/vscode,type=bind,consistency=cached",
"workspaceFolder": "/home/node/workspace/vscode",
"overrideCommand": false,
"runArgs": [
"--init",
// seccomp=unconfined is required for Chrome sandboxing
"--security-opt", "seccomp=unconfined"
],
"runArgs": [ "--init", "--security-opt", "seccomp=unconfined"],
"settings": {
// zsh is also available
"terminal.integrated.shell.linux": "/bin/bash",
"resmon.show.battery": false,
"resmon.show.cpufreq": false,
"remote.extensionKind": {
"ms-vscode.js-debug-nightly": "workspace",
"msjsdiag.debugger-for-chrome": "workspace"
},
"debug.chrome.useV3": true
"resmon.show.cpufreq": false
},
// noVNC, VNC ports
"forwardPorts": [6080, 5901],
// noVNC, VNC ports, debug
"forwardPorts": [6080, 5901, 9222],
"extensions": [
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"msjsdiag.debugger-for-chrome",
"mutantdino.resourcemonitor",
"GitHub.vscode-pull-request-github"
],

View file

@ -1,9 +0,0 @@
[app] (name=code-oss-dev)
[Position] (CENTER) {0 0}
[Maximized] {yes}
[Dimensions] {100% 100%}
[end]
[transient] (role=GtkFileChooserDialog)
[Position] (CENTER) {0 0}
[Dimensions] {70% 70%}
[end]

View file

@ -1,9 +0,0 @@
session.menuFile: ~/.fluxbox/menu
session.keyFile: ~/.fluxbox/keys
session.styleFile: /usr/share/fluxbox/styles//Squared_for_Debian
session.configVersion: 13
session.screen0.workspaces: 1
session.screen0.workspacewarping: false
session.screen0.toolbar.widthPercent: 100
session.screen0.strftimeFormat: %d %b, %a %02k:%M:%S
session.screen0.toolbar.tools: prevworkspace, workspacename, nextworkspace, clock, prevwindow, nextwindow, iconbar, systemtray

View file

@ -1,16 +0,0 @@
[begin] ( Code - OSS Development Container )
[exec] (File Manager) { nautilus ~ } <>
[exec] (Terminal) {/usr/bin/gnome-terminal --working-directory=~ } <>
[exec] (Start Code - OSS) { x-terminal-emulator -T "Code - OSS Build" -e bash /workspaces/vscode*/scripts/code.sh } <>
[submenu] (System >) {}
[exec] (Set Resolution) { x-terminal-emulator -T "Set Resolution" -e bash /usr/local/bin/set-resolution } <>
[exec] (Passwords and Keys) { seahorse } <>
[exec] (Top) { x-terminal-emulator -T "Top" -e /usr/bin/top } <>
[exec] (Editres) {editres} <>
[exec] (Xfontsel) {xfontsel} <>
[exec] (Xkill) {xkill} <>
[exec] (Xrefresh) {xrefresh} <>
[end]
[config] (Configuration >)
[workspaces] (Workspaces >)
[end]

View file

@ -20,8 +20,8 @@
"context-keys": {"assign": []},
"css-less-scss": {"assign": ["aeschli"]},
"custom-editors": {"assign": ["mjbvz"]},
"debug": {"assign": ["isidorn"]},
"debug-console": {"assign": ["isidorn"]},
"debug": {"assign": ["connor4312 "]},
"debug-console": {"assign": ["connor4312 "]},
"dialogs": {"assign": ["sbatten"]},
"diff-editor": {"assign": []},
"dropdown": {"assign": []},

View file

@ -133,6 +133,13 @@
"action": "updateLabels",
"addLabel": "~needs more info"
},
{
"type": "comment",
"name": "jsDebugLogs",
"action": "updateLabels",
"addLabel": "needs more info",
"comment": "Please collect trace logs using the following instructions:\n\n> If you're able to, add `\"trace\": true` to your `launch.json` and reproduce the issue. The location of the log file on your disk will be written to the Debug Console. Share that with us.\n>\n> ⚠️ This log file will not contain source code, but will contain file paths. You can drop it into https://microsoft.github.io/vscode-pwa-analyzer/index.html to see what it contains. If you'd rather not share the log publicly, you can email it to connor@xbox.com"
},
{
"type": "comment",
"name": "closedWith",

View file

@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v35
ref: v36
path: ./actions
- name: Install Actions
if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'author-verification-requested')

View file

@ -13,7 +13,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v35
ref: v36
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run Commands

View file

@ -11,7 +11,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v35
ref: v36
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions

View file

@ -13,7 +13,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v35
ref: v36
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions

View file

@ -11,7 +11,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v35
ref: v36
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions

View file

@ -13,7 +13,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v35
ref: v36
path: ./actions
- name: Install Actions
if: contains(github.event.issue.labels.*.name, '*english-please')

View file

@ -18,7 +18,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v35
ref: v36
- name: Install Actions
if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request')
run: npm install --production --prefix ./actions

View file

@ -14,7 +14,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v35
ref: v36
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Install Storage Module

View file

@ -14,7 +14,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v35
ref: v36
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run Locker

View file

@ -14,7 +14,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v35
ref: v36
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run Needs More Info Closer

View file

@ -11,7 +11,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v35
ref: v36
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions

View file

@ -11,7 +11,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v35
ref: v36
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions

View file

@ -13,7 +13,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v35
ref: v36
path: ./actions
- name: Checkout Repo
if: github.event_name != 'issues'

View file

@ -14,7 +14,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v35
ref: v36
- name: Install Actions
if: contains(github.event.issue.labels.*.name, 'testplan-item') || contains(github.event.issue.labels.*.name, 'invalid-testplan-item')
run: npm install --production --prefix ./actions

1
.nvmrc
View file

@ -1 +0,0 @@
10

182
.vscode/notebooks/endgame.github-issues vendored Normal file
View file

@ -0,0 +1,182 @@
[
{
"kind": 1,
"language": "markdown",
"value": "# Endgame",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "## Macros",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github\n\n$MILESTONE=milestone:\"September 2020\"\n\n$MINE=assignee:@me",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "## Endgame Champion",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "### Test Plan Items",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$REPOS $MILESTONE label:testplan-item is:open",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "### Feature Requests Missing Labels",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$REPOS $MILESTONE is:issue is:closed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "### Open Issues on the Milestone",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$REPOS $MILESTONE -label:testplan-item -label:iteration-plan -label:endgame-plan is:open",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "## Testing",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "### My Assigned Test Plan Items",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$REPOS $MILESTONE $MINE label:testplan-item is:open",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "### My Created Test Plan Items",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$REPOS $MILESTONE author:@me label:testplan-item",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "## Verification",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "### Issues to Verify",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "#### Issues filed by me",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$REPOS $MILESTONE -$MINE author:@me sort:updated-asc is:closed is:issue label:bug -label:verified -label:on-testplan -label:duplicate -label:*duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "#### Issues filed by others",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$REPOS $MILESTONE -$MINE -author:@me sort:updated-asc is:closed is:issue label:bug -label:verified -label:on-testplan -label:duplicate -label:*duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "### Verification Needed",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$REPOS $MILESTONE is:issue is:closed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate ",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "### My Issues Needing Verification",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$REPOS $MILESTONE $MINE sort:updated-desc is:closed is:issue -label:verified -label:on-testplan -label:duplicate -label:*duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found\n",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "### My Open Issues",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$REPOS $MILESTONE $MINE is:open is:issue",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "## Documentation",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "### Needing Release Notes",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "repo:microsoft/vscode $MILESTONE $MINE is:closed label:feature-request -label:on-release-notes",
"editable": true
}
]

View file

@ -8,7 +8,7 @@
{
"kind": 2,
"language": "github-issues",
"value": "$inbox -label:\"needs more info\" -label:emmet",
"value": "$inbox -label:\"needs more info\"",
"editable": true
},
{
@ -44,7 +44,7 @@
{
"kind": 2,
"language": "github-issues",
"value": "$inbox -label:emmet",
"value": "$inbox",
"editable": true
}
]

View file

@ -33,22 +33,22 @@ src/vs/base/browser/ui/contextview/contextview.ts:
163 </style>
src/vs/code/electron-sandbox/issue/issueReporterMain.ts:
57 const platformClass = platform.isWindows ? 'windows' : platform.isLinux ? 'linux' : 'mac';
58 addClass(document.body, platformClass); // used by our fonts
59
60: document.body.innerHTML = BaseHtml();
61 const issueReporter = new IssueReporter(configuration);
62 issueReporter.render();
63 document.body.style.display = 'block';
58 const platformClass = platform.isWindows ? 'windows' : platform.isLinux ? 'linux' : 'mac';
59 document.body.classList.add(platformClass); // used by our fonts
60
61: document.body.innerHTML = BaseHtml();
62 const issueReporter = new IssueReporter(configuration);
63 issueReporter.render();
64 document.body.style.display = 'block';
src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts:
320 content.push(`.highest { color: ${styles.highlightForeground}; }`);
321 }
322
323: styleTag.innerHTML = content.join('\n');
324 if (document.head) {
325 document.head.appendChild(styleTag);
326 }
321 content.push(`.highest { color: ${styles.highlightForeground}; }`);
322 }
323
324: styleTag.innerHTML = content.join('\n');
325 if (document.head) {
326 document.head.appendChild(styleTag);
327 }
src/vs/editor/browser/view/domLineBreaksComputer.ts:
107 allCharOffsets[i] = tmp[0];
@ -100,29 +100,29 @@ src/vs/editor/standalone/browser/colorizer.ts:
46 }
src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts:
212 if (!this._globalStyleElement) {
213 this._globalStyleElement = dom.createStyleSheet();
214 this._globalStyleElement.className = 'monaco-colors';
215: this._globalStyleElement.innerHTML = this._css;
216 this._styleElements.push(this._globalStyleElement);
217 }
218 return Disposable.None;
232 if (!this._globalStyleElement) {
233 this._globalStyleElement = dom.createStyleSheet();
234 this._globalStyleElement.className = 'monaco-colors';
235: this._globalStyleElement.innerHTML = this._allCSS;
236 this._styleElements.push(this._globalStyleElement);
237 }
238 return Disposable.None;
221 private _registerShadowDomContainer(domNode: HTMLElement): IDisposable {
222 const styleElement = dom.createStyleSheet(domNode);
223 styleElement.className = 'monaco-colors';
224: styleElement.innerHTML = this._css;
225 this._styleElements.push(styleElement);
226 return {
227 dispose: () => {
241 private _registerShadowDomContainer(domNode: HTMLElement): IDisposable {
242 const styleElement = dom.createStyleSheet(domNode);
243 styleElement.className = 'monaco-colors';
244: styleElement.innerHTML = this._allCSS;
245 this._styleElements.push(styleElement);
246 return {
247 dispose: () => {
291 ruleCollector.addRule(generateTokensCSSForColorMap(colorMap));
292
293 this._css = cssRules.join('\n');
294: this._styleElements.forEach(styleElement => styleElement.innerHTML = this._css);
295
296 TokenizationRegistry.setColorMap(colorMap);
297 this._onColorThemeChange.fire(theme);
321
322 private _updateCSS(): void {
323 this._allCSS = `${this._codiconCSS}\n${this._themeCSS}`;
324: this._styleElements.forEach(styleElement => styleElement.innerHTML = this._allCSS);
325 }
326
327 public getFileIconTheme(): IFileIconTheme {
src/vs/editor/test/browser/controller/imeTester.ts:
55 let content = this._model.getModelLineContent(i);
@ -142,21 +142,21 @@ src/vs/editor/test/browser/controller/imeTester.ts:
75 let startBtn = document.createElement('button');
src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts:
454
455 private getMarkdownDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement {
456 const dragImageContainer = DOM.$('.cell-drag-image.monaco-list-row.focused.markdown-cell-row');
457: dragImageContainer.innerHTML = templateData.container.outerHTML;
458
459 // Remove all rendered content nodes after the
460 const markdownContent = dragImageContainer.querySelector('.cell.markdown')!;
455
456 private getMarkdownDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement {
457 const dragImageContainer = DOM.$('.cell-drag-image.monaco-list-row.focused.markdown-cell-row');
458: dragImageContainer.innerHTML = templateData.container.outerHTML;
459
460 // Remove all rendered content nodes after the
461 const markdownContent = dragImageContainer.querySelector('.cell.markdown')!;
611 return null;
612 }
613
614: editorContainer.innerHTML = richEditorText;
615
616 return dragImageContainer;
617 }
641 return null;
642 }
643
644: editorContainer.innerHTML = richEditorText;
645
646 return dragImageContainer;
647 }
src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts:
375 addMouseoverListeners(outputNode, outputId);
@ -165,21 +165,21 @@ src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts:
378: outputNode.innerHTML = content.htmlContent;
379 cellOutputContainer.appendChild(outputNode);
380 domEval(outputNode);
381 } else {
381 } else if (preloadErrs.some(e => !!e)) {
src/vs/workbench/contrib/webview/browser/pre/main.js:
386 // apply default styles
387 const defaultStyles = newDocument.createElement('style');
388 defaultStyles.id = '_defaultStyles';
389: defaultStyles.innerHTML = defaultCssRules;
390 newDocument.head.prepend(defaultStyles);
391
392 applyStyles(newDocument, newDocument.body);
393 // apply default styles
394 const defaultStyles = newDocument.createElement('style');
395 defaultStyles.id = '_defaultStyles';
396: defaultStyles.innerHTML = defaultCssRules;
397 newDocument.head.prepend(defaultStyles);
398
399 applyStyles(newDocument, newDocument.body);
src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts:
281
282 const content = model.main.textEditorModel.getValue(EndOfLinePreference.LF);
283 if (!strings.endsWith(input.resource.path, '.md')) {
283 if (!input.resource.path.endsWith('.md')) {
284: this.content.innerHTML = content;
285 this.updateSizeClasses();
286 this.decorateContent();

View file

@ -2,7 +2,7 @@
# Flags: CaseSensitive WordMatch
# ContextLines: 2
10 results - 2 files
7 results - 2 files
src/vs/base/browser/dom.ts:
74 };
@ -22,26 +22,8 @@ src/vs/base/browser/dom.ts:
88
src/vs/base/common/strings.ts:
15
16 /**
17: * @deprecated ES6: use `String.padStart`
18 */
19 export function pad(n: number, l: number, char: string = '0'): string {
146
147 /**
148: * @deprecated ES6: use `String.startsWith`
149 */
150 export function startsWith(haystack: string, needle: string): boolean {
167
168 /**
169: * @deprecated ES6: use `String.endsWith`
170 */
171 export function endsWith(haystack: string, needle: string): boolean {
857
858 /**
859: * @deprecated ES6
860 */
861 export function repeat(s: string, count: number): string {
15
16 /**
17: * @deprecated ES6: use `String.padStart`
18 */
19 export function pad(n: number, l: number, char: string = '0'): string {

View file

@ -1 +1,3 @@
* text eol=lf
*.exe binary
*.dll binary

View file

@ -67,7 +67,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
}
}
requestService = getRequestService(params.initializationOptions.handledSchemas || ['file'], connection, runtime);
requestService = getRequestService(params.initializationOptions?.handledSchemas || ['file'], connection, runtime);
function getClientCapability<T>(name: string, def: T) {
const keys = name.split('.');

View file

@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { promises as fs } from 'fs';
import { createServer, Server } from 'net';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
@ -203,7 +204,27 @@ async function createAttachServer(context: vscode.ExtensionContext) {
return undefined;
}
server = new Promise<Server>((resolve, reject) => {
server = createServerInner(ipcAddress).catch(err => {
console.error(err);
return undefined;
});
return await server;
}
const createServerInner = async (ipcAddress: string) => {
try {
return await createServerInstance(ipcAddress);
} catch (e) {
// On unix/linux, the file can 'leak' if the process exits unexpectedly.
// If we see this, try to delete the file and then listen again.
await fs.unlink(ipcAddress).catch(() => undefined);
return await createServerInstance(ipcAddress);
}
};
const createServerInstance = (ipcAddress: string) =>
new Promise<Server>((resolve, reject) => {
const s = createServer(socket => {
let data: Buffer[] = [];
socket.on('data', async chunk => {
@ -229,14 +250,8 @@ async function createAttachServer(context: vscode.ExtensionContext) {
})
.on('error', reject)
.listen(ipcAddress, () => resolve(s));
}).catch(err => {
console.error(err);
return undefined;
});
return await server;
}
/**
* Destroys the auto-attach server, if it's running.
*/

View file

@ -112,16 +112,16 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
}
}
requestService = getRequestService(params.initializationOptions.handledSchemas || ['file'], connection, runtime);
requestService = getRequestService(initializationOptions?.handledSchemas || ['file'], connection, runtime);
const workspace = {
get settings() { return globalSettings; },
get folders() { return workspaceFolders; }
};
languageModes = getLanguageModes(initializationOptions ? initializationOptions.embeddedLanguages : { css: true, javascript: true }, workspace, params.capabilities, requestService);
languageModes = getLanguageModes(initializationOptions?.embeddedLanguages || { css: true, javascript: true }, workspace, params.capabilities, requestService);
const dataPaths: string[] = params.initializationOptions.dataPaths || [];
const dataPaths: string[] = initializationOptions?.dataPaths || [];
fetchHTMLDataProviders(dataPaths, requestService).then(dataProviders => {
languageModes.updateDataProviders(dataProviders);
});
@ -146,7 +146,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
}
clientSnippetSupport = getClientCapability('textDocument.completion.completionItem.snippetSupport', false);
dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof params.initializationOptions.provideFormatter !== 'boolean');
dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof initializationOptions?.provideFormatter !== 'boolean');
scopedSettingsSupport = getClientCapability('workspace.configuration', false);
workspaceFoldersSupport = getClientCapability('workspace.workspaceFolders', false);
foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE);
@ -155,7 +155,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['.', ':', '<', '"', '=', '/'] } : undefined,
hoverProvider: true,
documentHighlightProvider: true,
documentRangeFormattingProvider: params.initializationOptions.provideFormatter === true,
documentRangeFormattingProvider: initializationOptions?.provideFormatter === true,
documentLinkProvider: { resolveProvider: false },
documentSymbolProvider: true,
definitionProvider: true,

View file

@ -167,7 +167,12 @@ interface ISchemaAssociation {
* A match succeeds when there is at least one pattern matching and last matching pattern does not start with '!'.
*/
fileMatch: string[];
/*
* The schema for the given URI.
* If no schema is provided, the schema will be fetched with the schema request service (if available).
*/
schema?: JSONSchema;
}
```

View file

@ -1,7 +1,7 @@
{
"name": "vscode-json-languageserver",
"description": "JSON language server",
"version": "1.2.3",
"version": "1.3.0",
"author": "Microsoft Corporation",
"license": "MIT",
"engines": {

View file

@ -137,7 +137,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
} : undefined,
hoverProvider: true,
documentSymbolProvider: true,
documentRangeFormattingProvider: params.initializationOptions.provideFormatter === true,
documentRangeFormattingProvider: params.initializationOptions?.provideFormatter === true,
colorProvider: {},
foldingRangeProvider: true,
selectionRangeProvider: true,

View file

@ -68,6 +68,7 @@ export interface ITokenResponse {
refresh_token: string;
scope: string;
token_type: string;
id_token?: string;
}
function parseQuery(uri: vscode.Uri) {
@ -449,7 +450,19 @@ export class AzureActiveDirectoryService {
}
private getTokenFromResponse(json: ITokenResponse, scope: string, existingId?: string): IToken {
const claims = this.getTokenClaims(json.access_token);
let claims = undefined;
try {
claims = this.getTokenClaims(json.access_token);
} catch (e) {
if (json.id_token) {
Logger.info('Failed to fetch token claims from access_token. Attempting to parse id_token instead');
claims = this.getTokenClaims(json.id_token);
} else {
throw e;
}
}
return {
expiresIn: json.expires_in,
expiresAt: json.expires_in ? Date.now() + json.expires_in * 1000 : undefined,

View file

@ -282,8 +282,8 @@ export class PackageJSONContribution implements IJSONContribution {
private npmView(pack: string): Promise<ViewPackageInfo | undefined> {
return new Promise((resolve, _reject) => {
const command = 'npm view --json ' + pack + ' description dist-tags.latest homepage version';
cp.exec(command, (error, stdout) => {
const args = ['view', '--json', pack, 'description', 'dist-tags.latest', 'homepage', 'version'];
cp.execFile('npm', args, (error, stdout) => {
if (!error) {
try {
const content = JSON.parse(stdout);

View file

@ -137,7 +137,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider<TreeItem> {
}
private async debugScript(script: NpmScript) {
startDebugging(script.task.name, script.getFolder());
startDebugging(script.task.definition.script, path.dirname(script.package.resourceUri!.fsPath), script.getFolder());
}
private findScript(document: TextDocument, script?: NpmScript): number {

View file

@ -11,6 +11,7 @@ import {
createTask, startDebugging, findAllScriptRanges
} from './tasks';
import * as nls from 'vscode-nls';
import { dirname } from 'path';
const localize = nls.loadMessageBundle();
@ -107,12 +108,12 @@ export class NpmScriptHoverProvider implements HoverProvider {
}
}
public debugScriptFromHover(args: any) {
public debugScriptFromHover(args: { script: string; documentUri: Uri }) {
let script = args.script;
let documentUri = args.documentUri;
let folder = workspace.getWorkspaceFolder(documentUri);
if (folder) {
startDebugging(script, folder);
startDebugging(script, dirname(documentUri.fsPath), folder);
}
}
}

View file

@ -357,11 +357,12 @@ export function runScript(script: string, document: TextDocument) {
}
}
export function startDebugging(scriptName: string, folder: WorkspaceFolder) {
export function startDebugging(scriptName: string, cwd: string, folder: WorkspaceFolder) {
const config: DebugConfiguration = {
type: 'pwa-node',
request: 'launch',
name: `Debug ${scriptName}`,
cwd,
runtimeExecutable: getPackageManager(folder),
runtimeArgs: [
'run',

View file

@ -2,4 +2,4 @@
**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.
This extension provides Syntax Highlighting, Symbol Infomation, Result Highlighting, and Go to Definition capabilities for the Search Results Editor.
This extension provides Syntax Highlighting, Symbol Information, Result Highlighting, and Go to Definition capabilities for the Search Results Editor.

View file

@ -134,7 +134,7 @@ function relativePathToUri(path: string, resultsUri: vscode.Uri): vscode.Uri | u
}
const uriFromFolderWithPath = (folder: vscode.WorkspaceFolder, path: string): vscode.Uri =>
folder.uri.with({ path: pathUtils.join(folder.uri.fsPath, path) });
vscode.Uri.joinPath(folder.uri, path);
if (vscode.workspace.workspaceFolders) {
const multiRootFormattedPath = /^(.*) • (.*)$/.exec(path);

View file

@ -19,7 +19,13 @@ export class WorkerServerProcess implements TsServerProcess {
_configuration: TypeScriptServiceConfiguration,
) {
const worker = new Worker(tsServerPath);
return new WorkerServerProcess(worker, args);
return new WorkerServerProcess(worker, [
...args,
// Explicitly give TS Server its path so it can
// load local resources
'--executingFilePath', tsServerPath,
]);
}
private _onDataHandlers = new Set<(data: Proto.Response) => void>();

View file

@ -10,6 +10,7 @@ import { ClientCapabilities, ClientCapability, ServerType } from '../typescriptS
import API from '../utils/api';
import { SeparateSyntaxServerConfiguration, TsServerLogLevel, TypeScriptServiceConfiguration } from '../utils/configuration';
import { Logger } from '../utils/logger';
import { isWeb } from '../utils/platform';
import { TypeScriptPluginPathsProvider } from '../utils/pluginPathsProvider';
import { PluginManager } from '../utils/plugins';
import { TelemetryReporter } from '../utils/telemetry';
@ -203,29 +204,35 @@ export class TypeScriptServerSpawner {
}
if (TypeScriptServerSpawner.isLoggingEnabled(configuration)) {
const logDir = this._logDirectoryProvider.getNewLogDirectory();
if (logDir) {
tsServerLogFile = path.join(logDir, `tsserver.log`);
if (isWeb()) {
args.push('--logVerbosity', TsServerLogLevel.toString(configuration.tsServerLogLevel));
args.push('--logFile', tsServerLogFile);
}
}
const pluginPaths = this._pluginPathsProvider.getPluginPaths();
if (pluginManager.plugins.length) {
args.push('--globalPlugins', pluginManager.plugins.map(x => x.name).join(','));
const isUsingBundledTypeScriptVersion = currentVersion.path === this._versionProvider.defaultVersion.path;
for (const plugin of pluginManager.plugins) {
if (isUsingBundledTypeScriptVersion || plugin.enableForWorkspaceTypeScriptVersions) {
pluginPaths.push(plugin.path);
} else {
const logDir = this._logDirectoryProvider.getNewLogDirectory();
if (logDir) {
tsServerLogFile = path.join(logDir, `tsserver.log`);
args.push('--logVerbosity', TsServerLogLevel.toString(configuration.tsServerLogLevel));
args.push('--logFile', tsServerLogFile);
}
}
}
if (pluginPaths.length !== 0) {
args.push('--pluginProbeLocations', pluginPaths.join(','));
if (!isWeb()) {
const pluginPaths = this._pluginPathsProvider.getPluginPaths();
if (pluginManager.plugins.length) {
args.push('--globalPlugins', pluginManager.plugins.map(x => x.name).join(','));
const isUsingBundledTypeScriptVersion = currentVersion.path === this._versionProvider.defaultVersion.path;
for (const plugin of pluginManager.plugins) {
if (isUsingBundledTypeScriptVersion || plugin.enableForWorkspaceTypeScriptVersions) {
pluginPaths.push(plugin.path);
}
}
}
if (pluginPaths.length !== 0) {
args.push('--pluginProbeLocations', pluginPaths.join(','));
}
}
if (configuration.npmLocation) {

View file

@ -926,7 +926,10 @@ suite('vscode API - workspace', () => {
assert.ok(await vscode.workspace.applyEdit(we));
const document = await vscode.workspace.openTextDocument(newUri);
assert.equal(document.isDirty, true);
// See https://github.com/microsoft/vscode/issues/107739
// RenameOperation currently saves the file before applying the rename
// so that is why the document is not dirty here
assert.equal(document.isDirty, false);
await document.save();
assert.equal(document.isDirty, false);

View file

@ -11,7 +11,7 @@
"extensionKind": [ "ui" ],
"scripts": {
"compile": "node ./node_modules/vscode/bin/compile -watch -p ./",
"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-test-resolver ./tsconfig.json"
"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-test-resolver"
},
"activationEvents": [
"onResolveRemoteAuthority:test",

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.50.0",
"distro": "8b294911aabcfe39d612f32d1d2aa3cd6e9732b3",
"version": "1.51.0",
"distro": "e42a5273a865424266dd22838961d286c5c45ddc",
"author": {
"name": "Microsoft Corporation"
},
@ -91,6 +91,7 @@
"@types/mocha": "2.2.39",
"@types/node": "^12.11.7",
"@types/sinon": "^1.16.36",
"@types/trusted-types": "^1.0.6",
"@types/vscode-windows-registry": "^1.0.0",
"@types/webpack": "^4.4.10",
"@types/windows-foreground-love": "^0.3.0",
@ -193,4 +194,4 @@
"windows-mutex": "0.3.0",
"windows-process-tree": "0.2.4"
}
}
}

View file

@ -61,7 +61,7 @@
},
{
"name": "ms-vscode.references-view",
"version": "0.0.67",
"version": "0.0.68",
"repo": "https://github.com/microsoft/vscode-reference-view",
"metadata": {
"id": "dc489f46-520d-4556-ae85-1f9eab3c412d",
@ -91,7 +91,7 @@
},
{
"name": "ms-vscode.js-debug",
"version": "1.50.1",
"version": "1.50.2",
"repo": "https://github.com/microsoft/vscode-js-debug",
"metadata": {
"id": "25629058-ddac-4e17-abba-74678e126c5d",
@ -106,7 +106,7 @@
},
{
"name": "ms-vscode.vscode-js-profile-table",
"version": "0.0.10",
"version": "0.0.11",
"repo": "https://github.com/microsoft/vscode-js-debug",
"metadata": {
"id": "7e52b41b-71ad-457b-ab7e-0620f1fc4feb",
@ -123,7 +123,7 @@
"webBuiltInExtensions": [
{
"name": "ms-vscode.github-browser",
"version": "0.0.12",
"version": "0.0.13",
"repo": "https://github.com/microsoft/vscode-github-browser",
"metadata": {
"id": "c1bcff4b-4ecb-466e-b8f6-b02788b5fb5a",

View file

@ -15,6 +15,7 @@
"node-pty": "0.10.0-beta17",
"semver-umd": "^5.5.7",
"spdlog": "^0.11.1",
"tas-client-umd": "0.1.2",
"vscode-nsfw": "1.2.8",
"vscode-oniguruma": "1.3.1",
"vscode-proxy-agent": "^0.5.2",

View file

@ -373,6 +373,11 @@ spdlog@^0.11.1:
mkdirp "^0.5.1"
nan "^2.14.0"
tas-client-umd@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/tas-client-umd/-/tas-client-umd-0.1.2.tgz#fe93ae085f65424292ac79feff4f1add3e50e624"
integrity sha512-rT9BdDCejckqOTQL2ShX67QtTiAUGbmPm5ZTC8giXobBvZC6JuvBVy5G32hoGZ3Q0dpTvMfgpf3iVFNN2F7wzg==
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"

View file

@ -276,12 +276,13 @@ async function handleStatic(req, res, parsedUrl) {
if (/^\/static\/extensions\//.test(parsedUrl.pathname)) {
const relativePath = decodeURIComponent(parsedUrl.pathname.substr('/static/extensions/'.length));
const filePath = getExtensionFilePath(relativePath, (await builtInExtensionsPromise).locations);
if (!filePath) {
return serveError(req, res, 400, `Bad request.`);
}
return serveFile(req, res, filePath, {
const responseHeaders = {
'Access-Control-Allow-Origin': '*'
});
};
if (!filePath) {
return serveError(req, res, 400, `Bad request.`, responseHeaders);
}
return serveFile(req, res, filePath, responseHeaders);
}
// Strip `/static/` from the path
@ -299,12 +300,13 @@ async function handleExtension(req, res, parsedUrl) {
// Strip `/extension/` from the path
const relativePath = decodeURIComponent(parsedUrl.pathname.substr('/extension/'.length));
const filePath = getExtensionFilePath(relativePath, (await commandlineProvidedExtensionsPromise).locations);
if (!filePath) {
return serveError(req, res, 400, `Bad request.`);
}
return serveFile(req, res, filePath, {
const responseHeaders = {
'Access-Control-Allow-Origin': '*'
});
};
if (!filePath) {
return serveError(req, res, 400, `Bad request.`, responseHeaders);
}
return serveFile(req, res, filePath, responseHeaders);
}
/**
@ -517,8 +519,9 @@ function getExtensionFilePath(relativePath, locations) {
* @param {import('http').ServerResponse} res
* @param {string} errorMessage
*/
function serveError(req, res, errorCode, errorMessage) {
res.writeHead(errorCode, { 'Content-Type': 'text/plain' });
function serveError(req, res, errorCode, errorMessage, responseHeaders = Object.create(null)) {
responseHeaders['Content-Type'] = 'text/plain';
res.writeHead(errorCode, responseHeaders);
res.end(errorMessage);
}
@ -583,7 +586,8 @@ async function serveFile(req, res, filePath, responseHeaders = Object.create(nul
fs.createReadStream(filePath).pipe(res);
} catch (error) {
console.error(error.toString());
res.writeHead(404, { 'Content-Type': 'text/plain' });
responseHeaders['Content-Type'] = 'text/plain';
res.writeHead(404, responseHeaders);
return res.end('Not found');
}
}

View file

@ -11,7 +11,8 @@
"mocha",
"semver",
"sinon",
"winreg"
"winreg",
"trusted-types"
]
},
"include": [

View file

@ -2,7 +2,9 @@
"extends": "./tsconfig.base.json",
"compilerOptions": {
"noEmit": true,
"types": [],
"types": [
"trusted-types"
],
"paths": {},
"module": "amd",
"moduleResolution": "classic",

View file

@ -1,36 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// see https://w3c.github.io/webappsec-trusted-types/dist/spec/
// this isn't complete nor 100% correct
type TrustedHTML = string & object;
type TrustedScript = string;
type TrustedScriptURL = string;
interface TrustedTypePolicyOptions {
createHTML?: (value: string) => string
createScript?: (value: string) => string
createScriptURL?: (value: string) => string
}
interface TrustedTypePolicy {
readonly name: string;
createHTML(input: string, ...more: any[]): TrustedHTML
createScript(input: string, ...more: any[]): TrustedScript
createScriptURL(input: string, ...more: any[]): TrustedScriptURL
}
interface TrustedTypePolicyFactory {
createPolicy(policyName: string, object: TrustedTypePolicyOptions): TrustedTypePolicy;
}
interface Window {
trustedTypes: TrustedTypePolicyFactory | undefined;
}
interface WorkerGlobalScope {
trustedTypes: TrustedTypePolicyFactory | undefined;
}

View file

@ -22,6 +22,13 @@ export function clearNode(node: HTMLElement): void {
}
}
export function trustedInnerHTML(node: Element, value: TrustedHTML): void {
// this is a workaround for innerHTML not allowing for "asymetric" accessors
// see https://github.com/microsoft/vscode/issues/106396#issuecomment-692625393
// and https://github.com/microsoft/TypeScript/issues/30024
node.innerHTML = value as unknown as string;
}
export function isInDOM(node: Node | null): boolean {
while (node) {
if (node === document.body) {
@ -1229,7 +1236,6 @@ export function asCSSUrl(uri: URI): string {
return `url('${FileAccess.asBrowserUri(uri).toString(true).replace(/'/g, '%27')}')`;
}
export function triggerDownload(dataOrUri: Uint8Array | URI, name: string): void {
// If the data is provided as Buffer, we create a

View file

@ -64,8 +64,9 @@ export class ActionBar extends Disposable implements IActionRunner {
private _onDidBlur = this._register(new Emitter<void>());
readonly onDidBlur = this._onDidBlur.event;
private _onDidCancel = this._register(new Emitter<void>());
private _onDidCancel = this._register(new Emitter<void>({ onFirstListenerAdd: () => this.cancelHasListener = true }));
readonly onDidCancel = this._onDidCancel.event;
private cancelHasListener = false;
private _onDidRun = this._register(new Emitter<IRunEvent>());
readonly onDidRun = this._onDidRun.event;
@ -138,7 +139,7 @@ export class ActionBar extends Disposable implements IActionRunner {
eventHandled = this.focusPrevious();
} else if (nextKeys && (event.equals(nextKeys[0]) || event.equals(nextKeys[1]))) {
eventHandled = this.focusNext();
} else if (event.equals(KeyCode.Escape)) {
} else if (event.equals(KeyCode.Escape) && this.cancelHasListener) {
this._onDidCancel.fire();
} else if (this.isTriggerKeyEvent(event)) {
// Staying out of the else branch even if not triggered

View file

@ -133,7 +133,7 @@
}
/** Spans in markdown hovers need a margin-bottom to avoid looking cramped: https://github.com/microsoft/vscode/issues/101496 **/
.monaco-hover .markdown-hover .hover-contents span {
.monaco-hover .markdown-hover .hover-contents:not(.code-hover-contents) span {
margin-bottom: 4px;
display: inline-block;
}

View file

@ -29,7 +29,6 @@ const SELECT_OPTION_ENTRY_TEMPLATE_ID = 'selectOption.entry.template';
interface ISelectListTemplateData {
root: HTMLElement;
text: HTMLElement;
itemDescription: HTMLElement;
decoratorRight: HTMLElement;
disposables: IDisposable[];
}
@ -44,8 +43,6 @@ class SelectListRenderer implements IListRenderer<ISelectOptionItem, ISelectList
data.root = container;
data.text = dom.append(container, $('.option-text'));
data.decoratorRight = dom.append(container, $('.option-decorator-right'));
data.itemDescription = dom.append(container, $('.option-text-description'));
data.itemDescription.classList.add('visually-hidden');
return data;
}
@ -59,13 +56,6 @@ class SelectListRenderer implements IListRenderer<ISelectOptionItem, ISelectList
data.text.textContent = text;
data.decoratorRight.innerText = (!!decoratorRight ? decoratorRight : '');
if (typeof element.description === 'string') {
const itemDescriptionId = (text.replace(/ /g, '_').toLowerCase() + '_description_' + data.root.id);
data.text.setAttribute('aria-describedby', itemDescriptionId);
data.itemDescription.id = itemDescriptionId;
data.itemDescription.innerText = element.description;
}
// pseudo-select disabled option
if (isDisabled) {
data.root.classList.add('option-disabled');
@ -705,7 +695,18 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
keyboardSupport: false,
mouseSupport: false,
accessibilityProvider: {
getAriaLabel: (element) => element.text,
getAriaLabel: element => {
let label = element.text;
if (element.decoratorRight) {
label += `. ${element.decoratorRight}`;
}
if (element.description) {
label += `. ${element.description}`;
}
return label;
},
getWidgetAriaLabel: () => localize({ key: 'selectBox', comment: ['Behave like native select dropdown element.'] }, "Select Box"),
getRole: () => 'option',
getWidgetRole: () => 'listbox'

View file

@ -234,15 +234,13 @@ export class Separator extends Action {
}
}
export type SubmenuActions = IAction[] | (() => IAction[]);
export class SubmenuAction extends Action {
get actions(): IAction[] {
return Array.isArray(this._actions) ? this._actions : this._actions();
return this._actions;
}
constructor(id: string, label: string, private _actions: SubmenuActions, cssClass?: string) {
constructor(id: string, label: string, private _actions: IAction[], cssClass?: string) {
super(id, label, cssClass, true);
}
}

View file

@ -56,6 +56,21 @@ export function raceCancellation<T>(promise: Promise<T>, token: CancellationToke
return Promise.race([promise, new Promise<T | undefined>(resolve => token.onCancellationRequested(() => resolve(defaultValue)))]);
}
/**
* Returns as soon as one of the promises is resolved and cancels remaining promises
*/
export async function raceCancellablePromises<T>(cancellablePromises: CancelablePromise<T>[]): Promise<T> {
let resolvedPromiseIndex = -1;
const promises = cancellablePromises.map((promise, index) => promise.then(result => { resolvedPromiseIndex = index; return result; }));
const result = await Promise.race(promises);
cancellablePromises.forEach((cancellablePromise, index) => {
if (index !== resolvedPromiseIndex) {
cancellablePromise.cancel();
}
});
return result;
}
export function raceTimeout<T>(promise: Promise<T>, timeout: number, onTimeout?: () => void): Promise<T | undefined> {
let promiseResolve: ((value: T | undefined) => void) | undefined = undefined;
@ -149,13 +164,13 @@ export class Throttler {
this.activePromise = promiseFactory();
return new Promise((c, e) => {
return new Promise((resolve, reject) => {
this.activePromise!.then((result: any) => {
this.activePromise = null;
c(result);
resolve(result);
}, (err: any) => {
this.activePromise = null;
e(err);
reject(err);
});
});
}
@ -924,3 +939,38 @@ export class TaskSequentializer {
}
//#endregion
//#region
/**
* The `IntervalCounter` allows to count the number
* of calls to `increment()` over a duration of
* `interval`. This utility can be used to conditionally
* throttle a frequent task when a certain threshold
* is reached.
*/
export class IntervalCounter {
private lastIncrementTime = 0;
private value = 0;
constructor(private readonly interval: number) { }
increment(): number {
const now = Date.now();
// We are outside of the range of `interval` and as such
// start counting from 0 and remember the time
if (now - this.lastIncrementTime > this.interval) {
this.lastIncrementTime = now;
this.value = 0;
}
this.value++;
return this.value;
}
}
//#endregion

View file

@ -5,9 +5,7 @@
import { URI } from 'vs/base/common/uri';
import { CharCode } from 'vs/base/common/charCode';
import { compareSubstringIgnoreCase, compare, compareSubstring } from 'vs/base/common/strings';
import { Schemas } from 'vs/base/common/network';
import { isLinux } from 'vs/base/common/platform';
import { compareSubstringIgnoreCase, compare, compareSubstring, compareIgnoreCase } from 'vs/base/common/strings';
export function getOrSet<K, V>(map: Map<K, V>, key: K, value: V): V {
let result = map.get(key);
@ -140,6 +138,8 @@ export class UriIterator implements IKeyIterator<URI> {
private _states: UriIteratorState[] = [];
private _stateIdx: number = 0;
constructor(private readonly _ignorePathCasing: boolean) { }
reset(key: URI): this {
this._value = key;
this._states = [];
@ -150,10 +150,7 @@ export class UriIterator implements IKeyIterator<URI> {
this._states.push(UriIteratorState.Authority);
}
if (this._value.path) {
//todo@jrieken the case-sensitive logic is copied form `resources.ts#hasToIgnoreCase`
// which cannot be used because it depends on this
const caseSensitive = key.scheme === Schemas.file && isLinux;
this._pathIterator = new PathIterator(false, caseSensitive);
this._pathIterator = new PathIterator(false, !this._ignorePathCasing);
this._pathIterator.reset(key.path);
if (this._pathIterator.value()) {
this._states.push(UriIteratorState.Path);
@ -185,9 +182,9 @@ export class UriIterator implements IKeyIterator<URI> {
cmp(a: string): number {
if (this._states[this._stateIdx] === UriIteratorState.Scheme) {
return compare(a, this._value.scheme);
return compareIgnoreCase(a, this._value.scheme);
} else if (this._states[this._stateIdx] === UriIteratorState.Authority) {
return compareSubstringIgnoreCase(a, this._value.authority);
return compareIgnoreCase(a, this._value.authority);
} else if (this._states[this._stateIdx] === UriIteratorState.Path) {
return this._pathIterator.cmp(a);
} else if (this._states[this._stateIdx] === UriIteratorState.Query) {
@ -229,8 +226,8 @@ class TernarySearchTreeNode<K, V> {
export class TernarySearchTree<K, V> {
static forUris<E>(): TernarySearchTree<URI, E> {
return new TernarySearchTree<URI, E>(new UriIterator());
static forUris<E>(ignorePathCasing: boolean = false): TernarySearchTree<URI, E> {
return new TernarySearchTree<URI, E>(new UriIterator(ignorePathCasing));
}
static forPaths<E>(): TernarySearchTree<string, E> {
@ -405,57 +402,44 @@ export class TernarySearchTree<K, V> {
if (!node.mid) {
return undefined;
} else {
return this._nodeIterator(node.mid);
return this._values(node.mid);
}
}
}
return undefined;
}
private _nodeIterator(node: TernarySearchTreeNode<K, V>): Iterator<V> {
let res: { done: false; value: V; };
let idx: number;
let data: V[];
const next = (): IteratorResult<V> => {
if (!data) {
// lazy till first invocation
data = [];
idx = 0;
this._forEach(node, value => data.push(value));
}
if (idx >= data.length) {
return { done: true, value: undefined };
}
if (!res) {
res = { done: false, value: data[idx++] };
} else {
res.value = data[idx++];
}
return res;
};
return { next };
forEach(callback: (value: V, index: K) => any): void {
for (const [key, value] of this) {
callback(value, key);
}
}
forEach(callback: (value: V, index: K) => any) {
this._forEach(this._root, callback);
*[Symbol.iterator](): IterableIterator<[K, V]> {
yield* this._entries(this._root);
}
private _forEach(node: TernarySearchTreeNode<K, V> | undefined, callback: (value: V, index: K) => any) {
private *_values(node: TernarySearchTreeNode<K, V>): IterableIterator<V> {
for (const [, value] of this._entries(node)) {
yield value;
}
}
private *_entries(node: TernarySearchTreeNode<K, V> | undefined): IterableIterator<[K, V]> {
if (node) {
// left
this._forEach(node.left, callback);
yield* this._entries(node.left);
// node
if (node.value) {
// callback(node.value, this._iter.join(parts));
callback(node.value, node.key);
yield [node.key, node.value];
}
// mid
this._forEach(node.mid, callback);
yield* this._entries(node.mid);
// right
this._forEach(node.right, callback);
yield* this._entries(node.right);
}
}
}

View file

@ -135,6 +135,8 @@ class FileAccessImpl {
/**
* Returns a URI to use in contexts where the browser is responsible
* for loading (e.g. fetch()) or when used within the DOM.
*
* **Note:** use `dom.ts#asCSSUrl` whenever the URL is to be used in CSS context.
*/
asBrowserUri(uri: URI): URI;
asBrowserUri(moduleId: string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI;

View file

@ -318,16 +318,16 @@ export abstract class AbstractProcess<TProgressData> {
}
private useExec(): Promise<boolean> {
return new Promise<boolean>((c, e) => {
return new Promise<boolean>(resolve => {
if (!this.shell || !Platform.isWindows) {
return c(false);
return resolve(false);
}
const cmdShell = cp.spawn(getWindowsShell(), ['/s', '/c']);
cmdShell.on('error', (error: Error) => {
return c(true);
return resolve(true);
});
cmdShell.on('exit', (data: any) => {
return c(false);
return resolve(false);
});
});
}

View file

@ -706,4 +706,17 @@ suite('Async', () => {
const r3 = await s.queue('key2', () => Promise.resolve('hello'));
assert.equal(r3, 'hello');
});
test('IntervalCounter', async () => {
const counter = new async.IntervalCounter(10);
assert.equal(counter.increment(), 1);
assert.equal(counter.increment(), 2);
assert.equal(counter.increment(), 3);
await async.timeout(20);
assert.equal(counter.increment(), 1);
assert.equal(counter.increment(), 2);
assert.equal(counter.increment(), 3);
});
});

View file

@ -368,7 +368,7 @@ suite('Map', () => {
});
test('URIIterator', function () {
const iter = new UriIterator();
const iter = new UriIterator(false);
iter.reset(URI.parse('file:///usr/bin/file.txt'));
assert.equal(iter.value(), 'file');
@ -434,11 +434,22 @@ suite('Map', () => {
map.forEach((value, key) => {
assert.equal(trie.get(key), value);
});
// forEach
let forEachCount = 0;
trie.forEach((element, key) => {
assert.equal(element, map.get(key));
map.delete(key);
forEachCount++;
});
assert.equal(map.size, 0);
assert.equal(map.size, forEachCount);
// iterator
let iterCount = 0;
for (let [key, value] of trie) {
assert.equal(value, map.get(key));
iterCount++;
}
assert.equal(map.size, iterCount);
}
test('TernarySearchTree - set', function () {
@ -609,7 +620,7 @@ suite('Map', () => {
test('TernarySearchTree (URI) - basics', function () {
let trie = new TernarySearchTree<URI, number>(new UriIterator());
let trie = new TernarySearchTree<URI, number>(new UriIterator(false));
trie.set(URI.file('/user/foo/bar'), 1);
trie.set(URI.file('/user/foo'), 2);
@ -629,7 +640,7 @@ suite('Map', () => {
test('TernarySearchTree (URI) - lookup', function () {
const map = new TernarySearchTree<URI, number>(new UriIterator());
const map = new TernarySearchTree<URI, number>(new UriIterator(false));
map.set(URI.parse('http://foo.bar/user/foo/bar'), 1);
map.set(URI.parse('http://foo.bar/user/foo?query'), 2);
map.set(URI.parse('http://foo.bar/user/foo?QUERY'), 3);
@ -646,7 +657,7 @@ suite('Map', () => {
test('TernarySearchTree (PathSegments) - superstr', function () {
const map = new TernarySearchTree<URI, number>(new UriIterator());
const map = new TernarySearchTree<URI, number>(new UriIterator(false));
map.set(URI.file('/user/foo/bar'), 1);
map.set(URI.file('/user/foo'), 2);
map.set(URI.file('/user/foo/flip/flop'), 3);

View file

@ -19,7 +19,7 @@ function getWorker(workerId: string, label: string): Worker | Promise<Worker> {
// ESM-comment-begin
if (typeof require === 'function') {
// check if the JS lives on a different origin
const workerMain = require.toUrl('./' + workerId);
const workerMain = require.toUrl('./' + workerId); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321
const workerUrl = getWorkerBootstrapUrl(workerMain, label);
return new Worker(workerUrl, { name: label });
}
@ -36,7 +36,7 @@ export function getWorkerBootstrapUrl(scriptPath: string, label: string, forceDa
// this is the cross-origin case
// i.e. the webpage is running at a different origin than where the scripts are loaded from
const myPath = 'vs/base/worker/defaultWorkerFactory.js';
const workerBaseUrl = require.toUrl(myPath).slice(0, -myPath.length);
const workerBaseUrl = require.toUrl(myPath).slice(0, -myPath.length); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321
const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};importScripts('${scriptPath}');/*${label}*/`;
if (forceDataUri) {
const url = `data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`;

View file

@ -35,7 +35,6 @@ import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifec
import { IStorageMainService } from 'vs/platform/storage/node/storageMainService';
import { IFileService } from 'vs/platform/files/common/files';
import { FileAccess, Schemas } from 'vs/base/common/network';
import { ColorScheme } from 'vs/platform/theme/common/theme';
export interface IWindowCreationOptions {
state: IWindowState;
@ -784,7 +783,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
windowConfiguration.fullscreen = this.isFullScreen;
// Set Accessibility Config
windowConfiguration.colorScheme = (nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors) ? ColorScheme.HIGH_CONTRAST : nativeTheme.shouldUseDarkColors ? ColorScheme.DARK : ColorScheme.LIGHT;
windowConfiguration.colorScheme = {
dark: nativeTheme.shouldUseDarkColors,
highContrast: nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors
};
windowConfiguration.autoDetectHighContrast = windowConfig?.autoDetectHighContrast ?? true;
windowConfiguration.accessibilitySupport = app.accessibilitySupportEnabled;

View file

@ -138,7 +138,7 @@ export async function main(argv: string[]): Promise<any> {
child.stdout!.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
child.stderr!.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
await new Promise<void>(c => child.once('exit', () => c()));
await new Promise<void>(resolve => child.once('exit', () => resolve()));
});
}
@ -332,13 +332,13 @@ export async function main(argv: string[]): Promise<any> {
const child = spawn(process.execPath, argv.slice(2), options);
if (args.wait && waitMarkerFilePath) {
return new Promise<void>(c => {
return new Promise<void>(resolve => {
// Complete when process exits
child.once('exit', () => c(undefined));
child.once('exit', () => resolve(undefined));
// Complete when wait marker file is deleted
whenDeleted(waitMarkerFilePath!).then(c, c);
whenDeleted(waitMarkerFilePath!).then(resolve, resolve);
}).then(() => {
// Make sure to delete the tmp stdin file if we have any

View file

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { isFirefox } from 'vs/base/browser/browser';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import * as types from 'vs/base/common/types';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
@ -1640,6 +1641,11 @@ export namespace CoreNavigationCommands {
super(SelectAllCommand);
}
public runDOMCommand(): void {
if (isFirefox) {
(<HTMLInputElement>document.activeElement).focus();
(<HTMLInputElement>document.activeElement).select();
}
document.execCommand('selectAll');
}
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {

View file

@ -20,6 +20,11 @@
color: var(--outline-element-color);
}
.monaco-list .outline-element .monaco-icon-label-container .monaco-highlighted-label,
.monaco-list .outline-element .monaco-icon-label-container .label-description {
white-space: nowrap;
}
.monaco-list .outline-element .outline-element-decoration {
opacity: 0.75;
font-size: 90%;

View file

@ -55,8 +55,7 @@ export class CompletionModel {
private _lineContext: LineContext;
private _refilterKind: Refilter;
private _filteredItems?: StrictCompletionItem[];
private _isIncomplete?: Set<CompletionItemProvider>;
private _allProvider?: Set<CompletionItemProvider>; // TODO@jrieken merge incomplete and all provider info
private _providerInfo?: Map<CompletionItemProvider, boolean>;
private _stats?: ICompletionStats;
constructor(
@ -100,14 +99,20 @@ export class CompletionModel {
return this._filteredItems!;
}
get allProvider(): Set<CompletionItemProvider> {
get allProvider(): IterableIterator<CompletionItemProvider> {
this._ensureCachedState();
return this._allProvider!;
return this._providerInfo!.keys();
}
get incomplete(): Set<CompletionItemProvider> {
this._ensureCachedState();
return this._isIncomplete!;
const result = new Set<CompletionItemProvider>();
for (let [provider, incomplete] of this._providerInfo!) {
if (incomplete) {
result.add(provider);
}
}
return result;
}
adopt(except: Set<CompletionItemProvider>): CompletionItem[] {
@ -141,8 +146,7 @@ export class CompletionModel {
private _createCachedState(): void {
this._isIncomplete = new Set();
this._allProvider = new Set();
this._providerInfo = new Map();
this._stats = { suggestionCount: 0, snippetCount: 0, textCount: 0 };
const { leadingLineContent, characterCountDelta } = this._lineContext;
@ -166,12 +170,8 @@ export class CompletionModel {
continue; // SKIP invalid items
}
// collect those supports that signaled having
// an incomplete result
if (item.container.incomplete) {
this._isIncomplete.add(item.provider);
}
this._allProvider.add(item.provider);
// collect all support, know if their result is incomplete
this._providerInfo.set(item.provider, Boolean(item.container.incomplete));
// 'word' is that remainder of the current line that we
// filter and score against. In theory each suggestion uses a

View file

@ -4,66 +4,147 @@
*--------------------------------------------------------------------------------------------*/
/* Suggest widget*/
.monaco-editor .suggest-widget {
width: 430px; /** Initial widths **/
z-index: 40;
display: flex;
flex-wrap: nowrap;
flex-direction: row;
}
/** Initial widths **/
.monaco-editor .suggest-widget {
width: 430px;
.monaco-editor .suggest-widget.docs-side {
flex-direction: row;
}
.monaco-editor .suggest-widget > .message,
.monaco-editor .suggest-widget > .tree,
.monaco-editor .suggest-widget > .details {
.monaco-editor .suggest-widget.docs-side.reverse {
flex-direction: row-reverse;
}
.monaco-editor .suggest-widget.docs-below {
flex-direction: column;
}
.monaco-editor .suggest-widget.docs-below.reverse {
flex-direction: column-reverse;
}
/* --- fiddle with details margin so that borders merge/overlap */
.monaco-editor .suggest-widget.docs-side>.details {
margin: 0 0 0 -1px;
}
.monaco-editor .suggest-widget.docs-side.reverse>.details {
margin: 0 -1px 0 0;
}
.monaco-editor.hc-black .suggest-widget.docs-side>.details {
margin: 0 0 0 -2px;
}
.monaco-editor.hc-black .suggest-widget.docs-side.reverse>.details {
margin: 0 -2px 0 0;
}
.monaco-editor .suggest-widget>.message,
.monaco-editor .suggest-widget>.tree,
.monaco-editor .suggest-widget>.details {
flex: 0 1 auto;
width: 100%;
border-style: solid;
border-width: 1px;
box-sizing: border-box;
}
.monaco-editor.hc-black .suggest-widget > .message,
.monaco-editor.hc-black .suggest-widget > .tree,
.monaco-editor.hc-black .suggest-widget > .details {
.monaco-editor .suggest-widget>.tree.docs-higher {
align-self: flex-end;
}
.monaco-editor.hc-black .suggest-widget>.message,
.monaco-editor.hc-black .suggest-widget>.tree,
.monaco-editor.hc-black .suggest-widget>.details {
border-width: 2px;
}
/** Adjust width when docs are expanded to the side **/
.monaco-editor .suggest-widget.docs-side {
width: 660px;
}
.monaco-editor .suggest-widget.docs-side > .tree,
.monaco-editor .suggest-widget.docs-side > .details {
.monaco-editor .suggest-widget.docs-side>.tree,
.monaco-editor .suggest-widget.docs-side>.details {
width: 50%;
float: left;
}
.monaco-editor .suggest-widget.docs-side.list-right > .tree,
.monaco-editor .suggest-widget.docs-side.list-right > .details {
float: right;
}
/* MarkupContent Layout */
.monaco-editor .suggest-widget > .details ul {
padding-left: 20px;
}
.monaco-editor .suggest-widget > .details ol {
.monaco-editor .suggest-widget>.details ul {
padding-left: 20px;
}
.monaco-editor .suggest-widget > .details p code {
.monaco-editor .suggest-widget>.details ol {
padding-left: 20px;
}
.monaco-editor .suggest-widget>.details p code {
font-family: var(--monaco-monospace-font);
}
/* Styles for status bar part */
.monaco-editor .suggest-widget .suggest-status-bar {
visibility: hidden;
box-sizing: border-box;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
width: 100%;
font-size: 80%;
padding: 0 8px 0 4px;
border-top: 1px solid transparent;
}
.monaco-editor .suggest-widget.list-right.docs-side .suggest-status-bar {
left: auto;
right: 0;
}
.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar {
visibility: visible;
}
.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-label {
min-height: 18px;
opacity: 0.5;
color: inherit;
}
.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-item:not(:last-of-type) .action-label {
margin-right: 0;
}
.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-item:not(:last-of-type) .action-label::after {
content: ', ';
margin-right: 0.3em;
}
.monaco-editor .suggest-widget.with-status-bar .monaco-list .monaco-list-row>.contents>.main>.right>.readMore,
.monaco-editor .suggest-widget.with-status-bar .monaco-list .monaco-list-row.focused.string-label>.contents>.main>.right>.readMore {
display: none;
}
.monaco-editor .suggest-widget.with-status-bar:not(.docs-side) .monaco-list .monaco-list-row:hover>.contents>.main>.right.can-expand-details>.details-label {
width: 100%;
}
/* Styles for Message element for when widget is loading or is empty */
.monaco-editor .suggest-widget > .message {
.monaco-editor .suggest-widget>.message {
padding-left: 22px;
}
/** Styles for the list element **/
.monaco-editor .suggest-widget > .tree {
.monaco-editor .suggest-widget>.tree {
height: 100%;
}
@ -87,14 +168,14 @@
touch-action: none;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents {
flex: 1;
height: 100%;
overflow: hidden;
padding-left: 2px;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main {
display: flex;
overflow: hidden;
text-overflow: ellipsis;
@ -102,8 +183,7 @@
justify-content: space-between;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left,
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .right {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.left, .monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right {
display: flex;
}
@ -111,143 +191,108 @@
font-weight: bold;
}
/** Status Bar **/
.monaco-editor .suggest-widget > .suggest-status-bar {
visibility: hidden;
position: absolute;
left: 0;
box-sizing: border-box;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
width: 100%;
font-size: 80%;
border-left-width: 1px;
border-left-style: solid;
border-right-width: 1px;
border-right-style: solid;
border-bottom-width: 1px;
border-bottom-style: solid;
padding: 0 8px 0 4px;
}
.monaco-editor .suggest-widget.list-right.docs-side > .suggest-status-bar {
left: auto;
right: 0;
}
.monaco-editor .suggest-widget.docs-side > .suggest-status-bar {
width: 50%;
}
/** ReadMore Icon styles **/
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .codicon-close,
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .right > .readMore::before {
.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.header>.codicon-close,
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right>.readMore::before {
color: inherit;
opacity: 1;
font-size: 14px;
cursor: pointer;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .codicon-close {
.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.header>.codicon-close {
position: absolute;
top: 2px;
top: 6px;
right: 2px;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .codicon-close:hover,
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .right > .readMore:hover {
.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.header>.codicon-close:hover, .monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right>.readMore:hover {
opacity: 1;
}
/** signature, qualifier, type/details opacity **/
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left > .signature-label,
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left > .qualifier-label,
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .right > .details-label {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.left>.signature-label,
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.left>.qualifier-label,
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right>.details-label {
opacity: 0.7;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left > .signature-label {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.left>.signature-label {
overflow: hidden;
text-overflow: ellipsis;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left > .qualifier-label {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.left>.qualifier-label {
margin-left: 4px;
opacity: 0.4;
font-size: 90%;
text-overflow: ellipsis;
overflow: hidden;
line-height: 17px;
align-self: center;
}
/** Type Info and icon next to the label in the focused completion item **/
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .right > .details-label {
margin-left: 0.8em;
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right>.details-label {
margin-left: 1.1em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .right > .details-label > .monaco-tokenized-source {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right>.details-label>.monaco-tokenized-source {
display: inline;
}
/** Details: if using CompletionItem#details, show on focus **/
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .right > .details-label,
.monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row.focused > .contents > .main > .right > .details-label {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right>.details-label, .monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row.focused>.contents>.main>.right>.details-label {
display: none;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused > .contents > .main > .right > .details-label {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused>.contents>.main>.right>.details-label {
display: inline;
}
/** Details: if using CompletionItemLabel#details, always show **/
.monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label) > .contents > .main > .right > .details-label,
.monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row.focused:not(.string-label) > .contents > .main > .right > .details-label {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label)>.contents>.main>.right>.details-label, .monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row.focused:not(.string-label)>.contents>.main>.right>.details-label {
display: inline;
}
/** Ellipsis on hover **/
.monaco-editor .suggest-widget:not(.docs-side) .monaco-list .monaco-list-row:hover > .contents > .main > .right.can-expand-details > .details-label {
.monaco-editor .suggest-widget:not(.docs-side) .monaco-list .monaco-list-row:hover>.contents>.main>.right.can-expand-details>.details-label {
width: calc(100% - 26px);
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.left {
flex-shrink: 1;
flex-grow: 1;
overflow: hidden;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left > .monaco-icon-label {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.left>.monaco-icon-label {
flex-shrink: 0;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label) > .contents > .main > .left > .monaco-icon-label {
max-width: 100%;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row.string-label > .contents > .main > .left > .monaco-icon-label {
flex-shrink: 1;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .right {
overflow: hidden;
margin-left: 16px;
flex-shrink: 0;
max-width: 45%;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .right > .readMore {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label)>.contents>.main>.left>.monaco-icon-label {
max-width: 100%;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row.string-label>.contents>.main>.left>.monaco-icon-label {
flex-shrink: 1;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right {
overflow: hidden;
flex-shrink: 2;
max-width: 77%;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row>.contents>.main>.right>.readMore {
display: inline-block;
position: absolute;
right: 10px;
@ -257,26 +302,28 @@
}
/** Do NOT display ReadMore when docs is side/below **/
.monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row > .contents > .main > .right > .readMore,
.monaco-editor .suggest-widget.docs-below .monaco-list .monaco-list-row > .contents > .main > .right > .readMore {
.monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row>.contents>.main>.right>.readMore, .monaco-editor .suggest-widget.docs-below .monaco-list .monaco-list-row>.contents>.main>.right>.readMore {
display: none !important;
}
/** Do NOT display ReadMore when using plain CompletionItemLabel (details/documentation might not be resolved) **/
.monaco-editor .suggest-widget .monaco-list .monaco-list-row.string-label > .contents > .main > .right > .readMore {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row.string-label>.contents>.main>.right>.readMore {
display: none;
}
/** Focused item can show ReadMore, but can't when docs is side/below **/
.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused.string-label > .contents > .main > .right > .readMore {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row.focused.string-label>.contents>.main>.right>.readMore {
display: inline-block;
}
.monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row > .contents > .main > .right > .readMore,
.monaco-editor .suggest-widget.docs-below .monaco-list .monaco-list-row > .contents > .main > .right > .readMore {
.monaco-editor .suggest-widget.docs-side .monaco-list .monaco-list-row>.contents>.main>.right>.readMore, .monaco-editor .suggest-widget.docs-below .monaco-list .monaco-list-row>.contents>.main>.right>.readMore {
display: none;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row:hover > .contents > .main > .right > .readMore {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row:hover>.contents>.main>.right>.readMore {
visibility: visible;
}
@ -286,7 +333,8 @@
opacity: 0.66;
text-decoration: unset;
}
.monaco-editor .suggest-widget .monaco-list .monaco-list-row .monaco-icon-label.deprecated > .monaco-icon-label-container > .monaco-icon-name-container {
.monaco-editor .suggest-widget .monaco-list .monaco-list-row .monaco-icon-label.deprecated>.monaco-icon-label-container>.monaco-icon-name-container {
text-decoration: line-through;
}
@ -314,8 +362,7 @@
margin-right: 4px;
}
.monaco-editor .suggest-widget.no-icons .monaco-list .monaco-list-row .icon,
.monaco-editor .suggest-widget.no-icons .monaco-list .monaco-list-row .suggest-icon::before {
.monaco-editor .suggest-widget.no-icons .monaco-list .monaco-list-row .icon, .monaco-editor .suggest-widget.no-icons .monaco-list .monaco-list-row .suggest-icon::before {
display: none;
}
@ -328,6 +375,7 @@
}
/** Styles for the docs of the completion item in focus **/
.monaco-editor .suggest-widget .details {
display: flex;
flex-direction: column;
@ -340,13 +388,19 @@
.monaco-editor .suggest-widget.docs-below .details {
border-top-width: 0;
border-bottom-width: 1px;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element {
.monaco-editor .suggest-widget.docs-below.reverse .details {
border-bottom-width: 0;
border-top-width: 1px;
}
.monaco-editor .suggest-widget .details>.monaco-scrollable-element {
flex: 1;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body {
.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body {
position: absolute;
box-sizing: border-box;
height: 100%;
@ -354,51 +408,50 @@
padding-right: 22px;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .type {
.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.header>.type {
flex: 2;
overflow: hidden;
text-overflow: ellipsis;
opacity: 0.7;
word-break: break-all;
white-space: nowrap;
margin: 0 24px 0 0;
padding: 4px 0 12px 5px;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs {
.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs {
margin: 0;
padding: 4px 5px;
white-space: pre-wrap;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs.markdown-docs {
.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs.markdown-docs {
padding: 0;
white-space: initial;
min-height: calc(1rem + 8px);
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs.markdown-docs > div,
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs.markdown-docs > span:not(:empty) {
.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs.markdown-docs>div, .monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs.markdown-docs>span:not(:empty) {
padding: 4px 5px;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs.markdown-docs > div > p:first-child {
.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs.markdown-docs>div>p:first-child {
margin-top: 0;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs.markdown-docs > div > p:last-child {
.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs.markdown-docs>div>p:last-child {
margin-bottom: 0;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs .code {
.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs .code {
white-space: pre-wrap;
word-wrap: break-word;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs.markdown-docs .codicon {
.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>.docs.markdown-docs .codicon {
vertical-align: sub;
}
.monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > p:empty {
.monaco-editor .suggest-widget .details>.monaco-scrollable-element>.body>p:empty {
display: none;
}
@ -406,11 +459,3 @@
border-radius: 3px;
padding: 0 0.4em;
}
/* replace/insert decorations */
.monaco-editor .suggest-insert-unexpected {
font-style: italic;
}

View file

@ -1,35 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar {
visibility: visible;
}
.monaco-editor .suggest-widget.with-status-bar > .tree {
margin-bottom: 18px;
}
.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-label {
min-height: 18px;
opacity: 0.5;
color: inherit;
}
.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-item:not(:last-of-type) .action-label {
margin-right: 0;
}
.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-item:not(:last-of-type) .action-label::after {
content: ', ';
margin-right: 0.3em;
}
.monaco-editor .suggest-widget.with-status-bar .monaco-list .monaco-list-row > .contents > .main > .right > .readMore,
.monaco-editor .suggest-widget.with-status-bar .monaco-list .monaco-list-row.focused.string-label > .contents > .main > .right > .readMore {
display: none;
}
.monaco-editor .suggest-widget.with-status-bar:not(.docs-side) .monaco-list .monaco-list-row:hover > .contents > .main > .right.can-expand-details > .details-label {
width: 100%;
}

View file

@ -559,7 +559,9 @@ export class SuggestModel implements IDisposable {
// Select those providers have not contributed to this completion model and re-trigger completions for
// them. Also adopt the existing items and merge them into the new completion model
const inactiveProvider = new Set(CompletionProviderRegistry.all(this._editor.getModel()!));
this._completionModel.allProvider.forEach(provider => inactiveProvider.delete(provider));
for (let provider of this._completionModel.allProvider) {
inactiveProvider.delete(provider);
}
const items = this._completionModel.adopt(new Set());
this.trigger({ auto: this._context.auto, shy: false }, true, inactiveProvider, items);
return;

View file

@ -4,24 +4,21 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/suggest';
import 'vs/css!./media/suggestStatusBar';
import 'vs/base/browser/ui/codicons/codiconStyles'; // The codicon symbol styles are defined here and must be loaded
import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded
import * as nls from 'vs/nls';
import { createMatches } from 'vs/base/common/filters';
import * as strings from 'vs/base/common/strings';
import { Event, Emitter } from 'vs/base/common/event';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IDisposable, dispose, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { append, $, hide, show, getDomNodePagePosition, addDisposableListener, addStandardDisposableListener } from 'vs/base/browser/dom';
import { IListVirtualDelegate, IListEvent, IListRenderer, IListMouseEvent, IListGestureEvent } from 'vs/base/browser/ui/list/list';
import { IListVirtualDelegate, IListEvent, IListMouseEvent, IListGestureEvent } from 'vs/base/browser/ui/list/list';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser';
import { Context as SuggestContext, CompletionItem, suggestWidgetStatusbarMenu } from './suggest';
import { Context as SuggestContext, CompletionItem } from './suggest';
import { CompletionModel } from './completionModel';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { attachListStyler } from 'vs/platform/theme/common/styler';
@ -32,50 +29,15 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { TimeoutTimer, CancelablePromise, createCancelablePromise, disposableTimeout } from 'vs/base/common/async';
import { CompletionItemKind, completionKindToCssClass, CompletionItemTag } from 'vs/editor/common/modes';
import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { IModelService } from 'vs/editor/common/services/modelService';
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { FileKind } from 'vs/platform/files/common/files';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { flatten, isFalsyOrEmpty } from 'vs/base/common/arrays';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IMenuService } from 'vs/platform/actions/common/actions';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IAction, IActionViewItemProvider } from 'vs/base/common/actions';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { SuggestionDetails, canExpandCompletionItem } from './suggestWidgetDetails';
import { SuggestWidgetStatus } from 'vs/editor/contrib/suggest/suggestWidgetStatus';
import { getAriaId, ItemRenderer } from './suggestWidgetRenderer';
const expandSuggestionDocsByDefault = false;
const suggestMoreInfoIcon = registerIcon('suggest-more-info', Codicon.chevronRight);
interface ISuggestionTemplateData {
root: HTMLElement;
/**
* Flexbox
* < ------------- left ------------ > < --- right -- >
* <icon><label><signature><qualifier> <type><readmore>
*/
left: HTMLElement;
right: HTMLElement;
icon: HTMLElement;
colorspan: HTMLElement;
iconLabel: IconLabel;
iconContainer: HTMLElement;
parametersLabel: HTMLElement;
qualifierLabel: HTMLElement;
/**
* Showing either `CompletionItem#details` or `CompletionItemLabel#type`
*/
detailsLabel: HTMLElement;
readMore: HTMLElement;
disposables: DisposableStore;
}
/**
* Suggest widget colors
@ -86,198 +48,8 @@ export const editorSuggestWidgetForeground = registerColor('editorSuggestWidget.
export const editorSuggestWidgetSelectedBackground = registerColor('editorSuggestWidget.selectedBackground', { dark: listFocusBackground, light: listFocusBackground, hc: listFocusBackground }, nls.localize('editorSuggestWidgetSelectedBackground', 'Background color of the selected entry in the suggest widget.'));
export const editorSuggestWidgetHighlightForeground = registerColor('editorSuggestWidget.highlightForeground', { dark: listHighlightForeground, light: listHighlightForeground, hc: listHighlightForeground }, nls.localize('editorSuggestWidgetHighlightForeground', 'Color of the match highlights in the suggest widget.'));
const colorRegExp = /^(#([\da-f]{3}){1,2}|(rgb|hsl)a\(\s*(\d{1,3}%?\s*,\s*){3}(1|0?\.\d+)\)|(rgb|hsl)\(\s*\d{1,3}%?(\s*,\s*\d{1,3}%?){2}\s*\))$/i;
function extractColor(item: CompletionItem, out: string[]): boolean {
const label = typeof item.completion.label === 'string'
? item.completion.label
: item.completion.label.name;
if (label.match(colorRegExp)) {
out[0] = label;
return true;
}
if (typeof item.completion.documentation === 'string' && item.completion.documentation.match(colorRegExp)) {
out[0] = item.completion.documentation;
return true;
}
return false;
}
function canExpandCompletionItem(item: CompletionItem | null) {
if (!item) {
return false;
}
const suggestion = item.completion;
if (suggestion.documentation) {
return true;
}
return (suggestion.detail && suggestion.detail !== suggestion.label);
}
function getAriaId(index: number): string {
return `suggest-aria-id:${index}`;
}
class ItemRenderer implements IListRenderer<CompletionItem, ISuggestionTemplateData> {
constructor(
private widget: SuggestWidget,
private editor: ICodeEditor,
private triggerKeybindingLabel: string,
@IModelService private readonly _modelService: IModelService,
@IModeService private readonly _modeService: IModeService,
@IThemeService private readonly _themeService: IThemeService,
) {
}
get templateId(): string {
return 'suggestion';
}
renderTemplate(container: HTMLElement): ISuggestionTemplateData {
const data = <ISuggestionTemplateData>Object.create(null);
data.disposables = new DisposableStore();
data.root = container;
data.root.classList.add('show-file-icons');
data.icon = append(container, $('.icon'));
data.colorspan = append(data.icon, $('span.colorspan'));
const text = append(container, $('.contents'));
const main = append(text, $('.main'));
data.iconContainer = append(main, $('.icon-label.codicon'));
data.left = append(main, $('span.left'));
data.right = append(main, $('span.right'));
data.iconLabel = new IconLabel(data.left, { supportHighlights: true, supportCodicons: true });
data.disposables.add(data.iconLabel);
data.parametersLabel = append(data.left, $('span.signature-label'));
data.qualifierLabel = append(data.left, $('span.qualifier-label'));
data.detailsLabel = append(data.right, $('span.details-label'));
data.readMore = append(data.right, $('span.readMore' + suggestMoreInfoIcon.cssSelector));
data.readMore.title = nls.localize('readMore', "Read More ({0})", this.triggerKeybindingLabel);
const configureFont = () => {
const options = this.editor.getOptions();
const fontInfo = options.get(EditorOption.fontInfo);
const fontFamily = fontInfo.fontFamily;
const fontFeatureSettings = fontInfo.fontFeatureSettings;
const fontSize = options.get(EditorOption.suggestFontSize) || fontInfo.fontSize;
const lineHeight = options.get(EditorOption.suggestLineHeight) || fontInfo.lineHeight;
const fontWeight = fontInfo.fontWeight;
const fontSizePx = `${fontSize}px`;
const lineHeightPx = `${lineHeight}px`;
data.root.style.fontSize = fontSizePx;
data.root.style.fontWeight = fontWeight;
main.style.fontFamily = fontFamily;
main.style.fontFeatureSettings = fontFeatureSettings;
main.style.lineHeight = lineHeightPx;
data.icon.style.height = lineHeightPx;
data.icon.style.width = lineHeightPx;
data.readMore.style.height = lineHeightPx;
data.readMore.style.width = lineHeightPx;
};
configureFont();
data.disposables.add(Event.chain<ConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
.filter(e => e.hasChanged(EditorOption.fontInfo) || e.hasChanged(EditorOption.suggestFontSize) || e.hasChanged(EditorOption.suggestLineHeight))
.on(configureFont, null));
return data;
}
renderElement(element: CompletionItem, index: number, templateData: ISuggestionTemplateData): void {
const data = <ISuggestionTemplateData>templateData;
const suggestion = (<CompletionItem>element).completion;
const textLabel = typeof suggestion.label === 'string' ? suggestion.label : suggestion.label.name;
data.root.id = getAriaId(index);
data.colorspan.style.backgroundColor = '';
const labelOptions: IIconLabelValueOptions = {
labelEscapeNewLines: true,
matches: createMatches(element.score)
};
let color: string[] = [];
if (suggestion.kind === CompletionItemKind.Color && extractColor(element, color)) {
// special logic for 'color' completion items
data.icon.className = 'icon customcolor';
data.iconContainer.className = 'icon hide';
data.colorspan.style.backgroundColor = color[0];
} else if (suggestion.kind === CompletionItemKind.File && this._themeService.getFileIconTheme().hasFileIcons) {
// special logic for 'file' completion items
data.icon.className = 'icon hide';
data.iconContainer.className = 'icon hide';
const labelClasses = getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: textLabel }), FileKind.FILE);
const detailClasses = getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.detail }), FileKind.FILE);
labelOptions.extraClasses = labelClasses.length > detailClasses.length ? labelClasses : detailClasses;
} else if (suggestion.kind === CompletionItemKind.Folder && this._themeService.getFileIconTheme().hasFolderIcons) {
// special logic for 'folder' completion items
data.icon.className = 'icon hide';
data.iconContainer.className = 'icon hide';
labelOptions.extraClasses = flatten([
getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: textLabel }), FileKind.FOLDER),
getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: suggestion.detail }), FileKind.FOLDER)
]);
} else {
// normal icon
data.icon.className = 'icon hide';
data.iconContainer.className = '';
data.iconContainer.classList.add('suggest-icon', ...completionKindToCssClass(suggestion.kind).split(' '));
}
if (suggestion.tags && suggestion.tags.indexOf(CompletionItemTag.Deprecated) >= 0) {
labelOptions.extraClasses = (labelOptions.extraClasses || []).concat(['deprecated']);
labelOptions.matches = [];
}
data.iconLabel.setLabel(textLabel, undefined, labelOptions);
if (typeof suggestion.label === 'string') {
data.parametersLabel.textContent = '';
data.qualifierLabel.textContent = '';
data.detailsLabel.textContent = (suggestion.detail || '').replace(/\n.*$/m, '');
data.root.classList.add('string-label');
} else {
data.parametersLabel.textContent = (suggestion.label.parameters || '').replace(/\n.*$/m, '');
data.qualifierLabel.textContent = (suggestion.label.qualifier || '').replace(/\n.*$/m, '');
data.detailsLabel.textContent = (suggestion.label.type || '').replace(/\n.*$/m, '');
data.root.classList.remove('string-label');
}
if (canExpandCompletionItem(element)) {
data.right.classList.add('can-expand-details');
show(data.readMore);
data.readMore.onmousedown = e => {
e.stopPropagation();
e.preventDefault();
};
data.readMore.onclick = e => {
e.stopPropagation();
e.preventDefault();
this.widget.toggleDetails();
};
} else {
data.right.classList.remove('can-expand-details');
hide(data.readMore);
data.readMore.onmousedown = null;
data.readMore.onclick = null;
}
}
disposeTemplate(templateData: ISuggestionTemplateData): void {
templateData.disposables.dispose();
}
}
const enum State {
Hidden,
@ -289,176 +61,6 @@ const enum State {
}
class SuggestionDetails {
private el: HTMLElement;
private close: HTMLElement;
private scrollbar: DomScrollableElement;
private body: HTMLElement;
private header: HTMLElement;
private type: HTMLElement;
private docs: HTMLElement;
private readonly disposables: DisposableStore;
private renderDisposeable?: IDisposable;
private borderWidth: number = 1;
constructor(
container: HTMLElement,
private readonly widget: SuggestWidget,
private readonly editor: ICodeEditor,
private readonly markdownRenderer: MarkdownRenderer,
private readonly kbToggleDetails: string,
) {
this.disposables = new DisposableStore();
this.el = append(container, $('.details'));
this.disposables.add(toDisposable(() => container.removeChild(this.el)));
this.body = $('.body');
this.scrollbar = new DomScrollableElement(this.body, {});
append(this.el, this.scrollbar.getDomNode());
this.disposables.add(this.scrollbar);
this.header = append(this.body, $('.header'));
this.close = append(this.header, $('span' + Codicon.close.cssSelector));
this.close.title = nls.localize('readLess', "Read Less ({0})", this.kbToggleDetails);
this.type = append(this.header, $('p.type'));
this.docs = append(this.body, $('p.docs'));
this.configureFont();
Event.chain<ConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
.filter(e => e.hasChanged(EditorOption.fontInfo))
.on(this.configureFont, this, this.disposables);
markdownRenderer.onDidRenderCodeBlock(() => this.scrollbar.scanDomNode(), this, this.disposables);
}
get element() {
return this.el;
}
renderLoading(): void {
this.type.textContent = nls.localize('loading', "Loading...");
this.docs.textContent = '';
}
renderItem(item: CompletionItem, explainMode: boolean): void {
dispose(this.renderDisposeable);
this.renderDisposeable = undefined;
let { documentation, detail } = item.completion;
// --- documentation
if (explainMode) {
let md = '';
md += `score: ${item.score[0]}${item.word ? `, compared '${item.completion.filterText && (item.completion.filterText + ' (filterText)') || item.completion.label}' with '${item.word}'` : ' (no prefix)'}\n`;
md += `distance: ${item.distance}, see localityBonus-setting\n`;
md += `index: ${item.idx}, based on ${item.completion.sortText && `sortText: "${item.completion.sortText}"` || 'label'}\n`;
documentation = new MarkdownString().appendCodeblock('empty', md);
detail = `Provider: ${item.provider._debugDisplayName}`;
}
if (!explainMode && !canExpandCompletionItem(item)) {
this.type.textContent = '';
this.docs.textContent = '';
this.el.classList.add('no-docs');
return;
}
this.el.classList.remove('no-docs');
if (typeof documentation === 'string') {
this.docs.classList.remove('markdown-docs');
this.docs.textContent = documentation;
} else {
this.docs.classList.add('markdown-docs');
this.docs.innerText = '';
const renderedContents = this.markdownRenderer.render(documentation);
this.renderDisposeable = renderedContents;
this.docs.appendChild(renderedContents.element);
}
// --- details
if (detail) {
this.type.innerText = detail.length > 100_000 ? `${detail.substr(0, 100_000)}` : detail;
show(this.type);
} else {
this.type.innerText = '';
hide(this.type);
}
this.el.style.height = this.header.offsetHeight + this.docs.offsetHeight + (this.borderWidth * 2) + 'px';
this.el.style.userSelect = 'text';
this.el.tabIndex = -1;
this.close.onmousedown = e => {
e.preventDefault();
e.stopPropagation();
};
this.close.onclick = e => {
e.preventDefault();
e.stopPropagation();
this.widget.toggleDetails();
};
this.body.scrollTop = 0;
this.scrollbar.scanDomNode();
}
scrollDown(much = 8): void {
this.body.scrollTop += much;
}
scrollUp(much = 8): void {
this.body.scrollTop -= much;
}
scrollTop(): void {
this.body.scrollTop = 0;
}
scrollBottom(): void {
this.body.scrollTop = this.body.scrollHeight;
}
pageDown(): void {
this.scrollDown(80);
}
pageUp(): void {
this.scrollUp(80);
}
setBorderWidth(width: number): void {
this.borderWidth = width;
}
private configureFont() {
const options = this.editor.getOptions();
const fontInfo = options.get(EditorOption.fontInfo);
const fontFamily = fontInfo.fontFamily;
const fontSize = options.get(EditorOption.suggestFontSize) || fontInfo.fontSize;
const lineHeight = options.get(EditorOption.suggestLineHeight) || fontInfo.lineHeight;
const fontWeight = fontInfo.fontWeight;
const fontSizePx = `${fontSize}px`;
const lineHeightPx = `${lineHeight}px`;
this.el.style.fontSize = fontSizePx;
this.el.style.fontWeight = fontWeight;
this.el.style.fontFeatureSettings = fontInfo.fontFeatureSettings;
this.type.style.fontFamily = fontFamily;
this.close.style.height = lineHeightPx;
this.close.style.width = lineHeightPx;
}
dispose(): void {
this.disposables.dispose();
dispose(this.renderDisposeable);
this.renderDisposeable = undefined;
}
}
export interface ISelectedSuggestion {
item: CompletionItem;
index: number;
@ -480,17 +82,18 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
private isAddedAsContentWidget: boolean = false;
private isAuto: boolean = false;
private loadingTimeout: IDisposable = Disposable.None;
private currentSuggestionDetails: CancelablePromise<void> | null = null;
private focusedItem: CompletionItem | null;
private currentSuggestionDetails?: CancelablePromise<void>;
private focusedItem?: CompletionItem;
private ignoreFocusEvents: boolean = false;
private completionModel: CompletionModel | null = null;
private completionModel?: CompletionModel;
private element: HTMLElement;
private messageElement: HTMLElement;
private listElement: HTMLElement;
private statusBarElement: HTMLElement;
private details: SuggestionDetails;
private mainElement: HTMLElement;
private listContainer: HTMLElement;
private list: List<CompletionItem>;
private status: SuggestWidgetStatus;
private details: SuggestionDetails;
private listHeight?: number;
private readonly ctxSuggestWidgetVisible: IContextKey<boolean>;
@ -498,7 +101,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
private readonly ctxSuggestWidgetMultipleSuggestions: IContextKey<boolean>;
private readonly showTimeout = new TimeoutTimer();
private readonly toDispose = new DisposableStore();
private readonly _disposables = new DisposableStore();
private readonly onDidSelectEmitter = new Emitter<ISelectedSuggestion>();
private readonly onDidFocusEmitter = new Emitter<ISelectedSuggestion>();
@ -512,14 +115,13 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
private readonly maxWidgetWidth = 660;
private readonly listWidth = 330;
private readonly storageService: IStorageService;
private detailsFocusBorderColor?: string;
private detailsBorderColor?: string;
private firstFocusInCurrentList: boolean = false;
private preferDocPositionTop: boolean = false;
private docsPositionPreviousWidgetY: number | null = null;
private docsPositionPreviousWidgetY?: number;
private explainMode: boolean = false;
private readonly _onDetailsKeydown = new Emitter<IKeyboardEvent>();
@ -528,90 +130,48 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
constructor(
private readonly editor: ICodeEditor,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IStorageService private readonly storageService: IStorageService,
@IKeybindingService keybindingService: IKeybindingService,
@IContextKeyService contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService,
@IModeService modeService: IModeService,
@IOpenerService openerService: IOpenerService,
@IMenuService menuService: IMenuService,
@IInstantiationService instantiationService: IInstantiationService,
) {
const markdownRenderer = this.toDispose.add(new MarkdownRenderer(editor, modeService, openerService));
const markdownRenderer = this._disposables.add(new MarkdownRenderer(editor, modeService, openerService));
const kbToggleDetails = keybindingService.lookupKeybinding('toggleSuggestionDetails')?.getLabel() ?? '';
this.isAuto = false;
this.focusedItem = null;
this.storageService = storageService;
this.element = $('.editor-widget.suggest-widget');
this.toDispose.add(addDisposableListener(this.element, 'click', e => {
this._disposables.add(addDisposableListener(this.element, 'click', e => {
if (e.target === this.element) {
this.hideWidget();
}
}));
this.messageElement = append(this.element, $('.message'));
this.listElement = append(this.element, $('.tree'));
this.mainElement = append(this.element, $('.tree'));
const applyStatusBarStyle = () => this.element.classList.toggle('with-status-bar', this.editor.getOption(EditorOption.suggest).statusBar.visible);
applyStatusBarStyle();
this.statusBarElement = append(this.element, $('.suggest-status-bar'));
const actionViewItemProvider = <IActionViewItemProvider>(action => {
const kb = keybindingService.lookupKeybindings(action.id);
return new class extends ActionViewItem {
constructor() {
super(undefined, action, { label: true, icon: false });
}
updateLabel() {
if (isFalsyOrEmpty(kb) || !this.label) {
return super.updateLabel();
}
const { label } = this.getAction();
this.label.textContent = /{\d}/.test(label)
? strings.format(this.getAction().label, kb[0].getLabel())
: `${this.getAction().label} (${kb[0].getLabel()})`;
}
};
});
const leftActions = new ActionBar(this.statusBarElement, { actionViewItemProvider });
const rightActions = new ActionBar(this.statusBarElement, { actionViewItemProvider });
const menu = menuService.createMenu(suggestWidgetStatusbarMenu, contextKeyService);
const renderMenu = () => {
const left: IAction[] = [];
const right: IAction[] = [];
for (let [group, actions] of menu.getActions()) {
if (group === 'left') {
left.push(...actions);
} else {
right.push(...actions);
}
}
leftActions.clear();
leftActions.push(left);
rightActions.clear();
rightActions.push(right);
};
this.toDispose.add(menu.onDidChange(() => renderMenu()));
this.toDispose.add(menu);
this.details = instantiationService.createInstance(SuggestionDetails, this.element, this, this.editor, markdownRenderer, kbToggleDetails);
this.details = instantiationService.createInstance(SuggestionDetails, this.element, this.editor, markdownRenderer, kbToggleDetails);
this.details.onDidClose(this.toggleDetails, this, this._disposables);
hide(this.details.element);
const applyIconStyle = () => this.element.classList.toggle('no-icons', !this.editor.getOption(EditorOption.suggest).showIcons);
applyIconStyle();
let renderer = instantiationService.createInstance(ItemRenderer, this, this.editor, kbToggleDetails);
this.listContainer = append(this.mainElement, $('.list-container'));
this.list = new List('SuggestWidget', this.listElement, this, [renderer], {
const renderer = instantiationService.createInstance(ItemRenderer, this.editor, kbToggleDetails);
this._disposables.add(renderer);
this._disposables.add(renderer.onDidToggleDetails(() => this.toggleDetails()));
this.list = new List('SuggestWidget', this.listContainer, this, [renderer], {
useShadows: false,
mouseSupport: false,
accessibilityProvider: {
getRole: () => 'option',
getAriaLabel: (item: CompletionItem) => {
const textLabel = typeof item.completion.label === 'string' ? item.completion.label : item.completion.label.name;
if (item.isResolved && this.expandDocsSettingFromStorage()) {
if (item.isResolved && this._isDetailsVisible()) {
const { documentation, detail } = item.completion;
const docs = strings.format(
'{0}{1}',
@ -628,18 +188,22 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
}
});
this.toDispose.add(attachListStyler(this.list, themeService, {
this.status = instantiationService.createInstance(SuggestWidgetStatus, this.mainElement);
const applyStatusBarStyle = () => this.element.classList.toggle('with-status-bar', this.editor.getOption(EditorOption.suggest).statusBar.visible);
applyStatusBarStyle();
this._disposables.add(attachListStyler(this.list, themeService, {
listInactiveFocusBackground: editorSuggestWidgetSelectedBackground,
listInactiveFocusOutline: activeContrastBorder
}));
this.toDispose.add(themeService.onDidColorThemeChange(t => this.onThemeChange(t)));
this.toDispose.add(editor.onDidLayoutChange(() => this.onEditorLayoutChange()));
this.toDispose.add(this.list.onMouseDown(e => this.onListMouseDownOrTap(e)));
this.toDispose.add(this.list.onTap(e => this.onListMouseDownOrTap(e)));
this.toDispose.add(this.list.onDidChangeSelection(e => this.onListSelection(e)));
this.toDispose.add(this.list.onDidChangeFocus(e => this.onListFocus(e)));
this.toDispose.add(this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged()));
this.toDispose.add(this.editor.onDidChangeConfiguration(e => {
this._disposables.add(themeService.onDidColorThemeChange(t => this.onThemeChange(t)));
this._disposables.add(editor.onDidLayoutChange(() => this.onEditorLayoutChange()));
this._disposables.add(this.list.onMouseDown(e => this.onListMouseDownOrTap(e)));
this._disposables.add(this.list.onTap(e => this.onListMouseDownOrTap(e)));
this._disposables.add(this.list.onDidChangeSelection(e => this.onListSelection(e)));
this._disposables.add(this.list.onDidChangeFocus(e => this.onListFocus(e)));
this._disposables.add(this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged()));
this._disposables.add(this.editor.onDidChangeConfiguration(e => {
if (e.hasChanged(EditorOption.suggest)) {
applyStatusBarStyle();
applyIconStyle();
@ -652,11 +216,11 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.onThemeChange(themeService.getColorTheme());
this.toDispose.add(addStandardDisposableListener(this.details.element, 'keydown', e => {
this._disposables.add(addStandardDisposableListener(this.details.element, 'keydown', e => {
this._onDetailsKeydown.fire(e);
}));
this.toDispose.add(this.editor.onMouseDown((e: IEditorMouseEvent) => this.onEditorMouseDown(e)));
this._disposables.add(this.editor.onMouseDown((e: IEditorMouseEvent) => this.onEditorMouseDown(e)));
}
private onEditorMouseDown(mouseEvent: IEditorMouseEvent): void {
@ -673,15 +237,13 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
}
private onCursorSelectionChanged(): void {
if (this.state === State.Hidden) {
return;
if (this.state !== State.Hidden) {
this.editor.layoutContentWidget(this);
}
this.editor.layoutContentWidget(this);
}
private onEditorLayoutChange(): void {
if ((this.state === State.Open || this.state === State.Details) && this.expandDocsSettingFromStorage()) {
if ((this.state === State.Open || this.state === State.Details) && this._isDetailsVisible()) {
this.expandSideOrBelow();
}
}
@ -720,15 +282,14 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
private onThemeChange(theme: IColorTheme) {
const backgroundColor = theme.getColor(editorSuggestWidgetBackground);
if (backgroundColor) {
this.listElement.style.backgroundColor = backgroundColor.toString();
this.statusBarElement.style.backgroundColor = backgroundColor.toString();
this.mainElement.style.backgroundColor = backgroundColor.toString();
this.details.element.style.backgroundColor = backgroundColor.toString();
this.messageElement.style.backgroundColor = backgroundColor.toString();
}
const borderColor = theme.getColor(editorSuggestWidgetBorder);
if (borderColor) {
this.listElement.style.borderColor = borderColor.toString();
this.statusBarElement.style.borderColor = borderColor.toString();
this.mainElement.style.borderColor = borderColor.toString();
this.status.element.style.borderTopColor = borderColor.toString();
this.details.element.style.borderColor = borderColor.toString();
this.messageElement.style.borderColor = borderColor.toString();
this.detailsBorderColor = borderColor.toString();
@ -748,8 +309,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
if (!e.elements.length) {
if (this.currentSuggestionDetails) {
this.currentSuggestionDetails.cancel();
this.currentSuggestionDetails = null;
this.focusedItem = null;
this.currentSuggestionDetails = undefined;
this.focusedItem = undefined;
}
this.editor.setAriaOptions({ activeDescendant: undefined });
@ -766,10 +327,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.firstFocusInCurrentList = !this.focusedItem;
if (item !== this.focusedItem) {
if (this.currentSuggestionDetails) {
this.currentSuggestionDetails.cancel();
this.currentSuggestionDetails = null;
}
this.currentSuggestionDetails?.cancel();
this.currentSuggestionDetails = undefined;
this.focusedItem = item;
@ -794,7 +353,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.list.setFocus([index]);
this.ignoreFocusEvents = false;
if (this.expandDocsSettingFromStorage()) {
if (this._isDetailsVisible()) {
this.showDetails(false);
} else {
this.element.classList.remove('docs-side');
@ -825,43 +384,43 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
switch (state) {
case State.Hidden:
hide(this.messageElement, this.details.element, this.listElement, this.statusBarElement);
hide(this.messageElement, this.details.element, this.mainElement);
this.hide();
this.listHeight = 0;
if (stateChanged) {
this.list.splice(0, this.list.length);
}
this.focusedItem = null;
this.focusedItem = undefined;
break;
case State.Loading:
this.messageElement.textContent = SuggestWidget.LOADING_MESSAGE;
hide(this.listElement, this.details.element, this.statusBarElement);
hide(this.mainElement, this.details.element);
show(this.messageElement);
this.element.classList.remove('docs-side');
this.show();
this.focusedItem = null;
this.focusedItem = undefined;
break;
case State.Empty:
this.messageElement.textContent = SuggestWidget.NO_SUGGESTIONS_MESSAGE;
hide(this.listElement, this.details.element, this.statusBarElement);
hide(this.mainElement, this.details.element);
show(this.messageElement);
this.element.classList.remove('docs-side');
this.show();
this.focusedItem = null;
this.focusedItem = undefined;
break;
case State.Open:
hide(this.messageElement);
show(this.listElement, this.statusBarElement);
show(this.mainElement);
this.show();
break;
case State.Frozen:
hide(this.messageElement);
show(this.listElement);
show(this.mainElement);
this.show();
break;
case State.Details:
hide(this.messageElement);
show(this.details.element, this.listElement, this.statusBarElement);
show(this.details.element, this.mainElement);
this.show();
break;
}
@ -881,14 +440,12 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
showSuggestions(completionModel: CompletionModel, selectionIndex: number, isFrozen: boolean, isAuto: boolean): void {
this.preferDocPositionTop = false;
this.docsPositionPreviousWidgetY = null;
this.docsPositionPreviousWidgetY = undefined;
this.loadingTimeout.dispose();
if (this.currentSuggestionDetails) {
this.currentSuggestionDetails.cancel();
this.currentSuggestionDetails = null;
}
this.currentSuggestionDetails?.cancel();
this.currentSuggestionDetails = undefined;
if (this.completionModel !== completionModel) {
this.completionModel = completionModel;
@ -911,7 +468,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.setState(State.Empty);
}
this.completionModel = null;
this.completionModel = undefined;
} else {
@ -929,7 +486,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.telemetryService.publicLog('suggestWidget', { ...stats });
}
this.focusedItem = null;
this.focusedItem = undefined;
this.list.splice(0, this.list.length, this.completionModel.items);
if (isFrozen) {
@ -1054,7 +611,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
if (this.detailsBorderColor) {
this.details.element.style.borderColor = this.detailsBorderColor;
}
} else if (this.state === State.Open && this.expandDocsSettingFromStorage()) {
} else if (this.state === State.Open && this._isDetailsVisible()) {
this.setState(State.Details);
if (this.detailsFocusBorderColor) {
this.details.element.style.borderColor = this.detailsFocusBorderColor;
@ -1064,24 +621,19 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
}
toggleDetails(): void {
if (!canExpandCompletionItem(this.list.getFocusedElements()[0])) {
return;
}
if (this.expandDocsSettingFromStorage()) {
if (this._isDetailsVisible()) {
// hide details widget
this.ctxSuggestWidgetDetailsVisible.set(false);
this.updateExpandDocsSetting(false);
this._setDetailsVisible(false);
hide(this.details.element);
this.element.classList.remove('docs-side', 'doc-below');
this.editor.layoutContentWidget(this);
this.telemetryService.publicLog2('suggestWidget:collapseDetails');
} else {
if (this.state !== State.Open && this.state !== State.Details && this.state !== State.Frozen) {
return;
}
} else if (canExpandCompletionItem(this.list.getFocusedElements()[0]) && (this.state === State.Open || this.state === State.Details || this.state === State.Frozen)) {
// show details widget (iff possible)
this.ctxSuggestWidgetDetailsVisible.set(true);
this.updateExpandDocsSetting(true);
this._setDetailsVisible(true);
this.showDetails(false);
this.telemetryService.publicLog2('suggestWidget:expandDetails');
}
@ -1103,8 +655,6 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
this.details.renderItem(this.list.getFocusedElements()[0], this.explainMode);
}
// Reset margin-top that was set as Fix for #26416
this.listElement.style.marginTop = '0px';
// with docs showing up widget width/height may change, so reposition the widget
this.editor.layoutContentWidget(this);
@ -1115,7 +665,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
}
toggleExplainMode(): void {
if (this.list.getFocusedElements()[0] && this.expandDocsSettingFromStorage()) {
if (this.list.getFocusedElements()[0] && this._isDetailsVisible()) {
this.explainMode = !this.explainMode;
this.showDetails(false);
}
@ -1179,19 +729,17 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
}
private updateListHeight(): number {
let height = 0;
let height = this.unfocusedHeight;
if (this.state === State.Empty || this.state === State.Loading) {
height = this.unfocusedHeight;
} else {
if (this.state !== State.Empty && this.state !== State.Loading) {
const suggestionCount = this.list.contentHeight / this.unfocusedHeight;
const { maxVisibleSuggestions } = this.editor.getOption(EditorOption.suggest);
height = Math.min(suggestionCount, maxVisibleSuggestions) * this.unfocusedHeight;
}
this.element.style.lineHeight = `${this.unfocusedHeight}px`;
this.listElement.style.height = `${height}px`;
this.statusBarElement.style.top = `${height}px`;
this.listContainer.style.height = `${height}px`;
this.mainElement.style.height = `${height + (this.editor.getOption(EditorOption.suggest).statusBar.visible ? this.unfocusedHeight : 0)}px`;
this.list.layout(height);
return height;
}
@ -1215,33 +763,32 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
// Fixes #27649
// Check if the Y changed to the top of the cursor and keep the widget flagged to prefer top
if (this.docsPositionPreviousWidgetY &&
if (this.docsPositionPreviousWidgetY !== undefined &&
this.docsPositionPreviousWidgetY < widgetY &&
!this.preferDocPositionTop) {
!this.preferDocPositionTop
) {
this.preferDocPositionTop = true;
this.adjustDocsPosition();
return;
}
this.docsPositionPreviousWidgetY = widgetY;
if (widgetX < cursorX - this.listWidth) {
// Widget is too far to the left of cursor, swap list and docs
this.element.classList.add('list-right');
} else {
this.element.classList.remove('list-right');
}
const aboveCursor = cursorY - lineHeight > widgetY;
const rowMode = this.element.classList.contains('docs-side');
// Compare top of the cursor (cursorY - lineheight) with widgetTop to determine if
// margin-top needs to be applied on list to make it appear right above the cursor
// Cannot compare cursorY directly as it may be a few decimals off due to zoooming
if (this.element.classList.contains('docs-side')
&& cursorY - lineHeight > widgetY
&& this.details.element.offsetHeight > this.listElement.offsetHeight) {
// row mode: reverse doc/list when being too far right
// column mode: reverse doc/list when being too far down
this.element.classList.toggle(
'reverse',
(rowMode && widgetX < cursorX - this.listWidth) || (!rowMode && aboveCursor)
);
// Fix for #26416
// Docs is bigger than list and widget is above cursor, apply margin-top so that list appears right above cursor
this.listElement.style.marginTop = `${this.details.element.offsetHeight - this.listElement.offsetHeight}px`;
}
// row mode: when detail is higher and when showing above the cursor then align
// the list at the bottom
this.mainElement.classList.toggle(
'docs-higher',
rowMode && aboveCursor && this.details.element.offsetHeight > this.mainElement.offsetHeight
);
}
/**
@ -1253,7 +800,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
return;
}
let matches = this.element.style.maxWidth!.match(/(\d+)px/);
let matches = this.element.style.maxWidth.match(/(\d+)px/);
if (!matches || Number(matches[1]) < this.maxWidgetWidth) {
this.element.classList.add('docs-below');
this.element.classList.remove('docs-side');
@ -1276,26 +823,27 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
// IDelegate
getHeight(element: CompletionItem): number {
getHeight(_element: CompletionItem): number {
return this.unfocusedHeight;
}
getTemplateId(element: CompletionItem): string {
getTemplateId(_element: CompletionItem): string {
return 'suggestion';
}
private expandDocsSettingFromStorage(): boolean {
private _isDetailsVisible(): boolean {
return this.storageService.getBoolean('expandSuggestionDocs', StorageScope.GLOBAL, expandSuggestionDocsByDefault);
}
private updateExpandDocsSetting(value: boolean) {
private _setDetailsVisible(value: boolean) {
this.storageService.store('expandSuggestionDocs', value, StorageScope.GLOBAL);
}
dispose(): void {
this.details.dispose();
this.list.dispose();
this.toDispose.dispose();
this.status.dispose();
this._disposables.dispose();
this.loadingTimeout.dispose();
this.showTimeout.dispose();
this.editor.removeContentWidget(this);

View file

@ -0,0 +1,189 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import * as dom from 'vs/base/browser/dom';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { CompletionItem } from './suggest';
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { Codicon } from 'vs/base/common/codicons';
import { Emitter, Event } from 'vs/base/common/event';
export function canExpandCompletionItem(item: CompletionItem | undefined): boolean {
return !!item && Boolean(item.completion.documentation || item.completion.detail && item.completion.detail !== item.completion.label);
}
export class SuggestionDetails {
readonly element: HTMLElement;
private readonly _onDidClose = new Emitter<void>();
readonly onDidClose: Event<void> = this._onDidClose.event;
private readonly _close: HTMLElement;
private readonly _scrollbar: DomScrollableElement;
private readonly _body: HTMLElement;
private readonly _header: HTMLElement;
private readonly _type: HTMLElement;
private readonly _docs: HTMLElement;
private readonly _disposables = new DisposableStore();
private _renderDisposeable?: IDisposable;
private _borderWidth: number = 1;
constructor(
container: HTMLElement,
private readonly _editor: ICodeEditor,
private readonly _markdownRenderer: MarkdownRenderer,
private readonly _kbToggleDetails: string
) {
this.element = dom.append(container, dom.$('.details'));
this._disposables.add(toDisposable(() => this.element.remove()));
this._body = dom.$('.body');
this._scrollbar = new DomScrollableElement(this._body, {});
dom.append(this.element, this._scrollbar.getDomNode());
this._disposables.add(this._scrollbar);
this._header = dom.append(this._body, dom.$('.header'));
this._close = dom.append(this._header, dom.$('span' + Codicon.close.cssSelector));
this._close.title = nls.localize('readLess', "Read Less ({0})", this._kbToggleDetails);
this._type = dom.append(this._header, dom.$('p.type'));
this._docs = dom.append(this._body, dom.$('p.docs'));
this._configureFont();
this._disposables.add(this._editor.onDidChangeConfiguration(e => {
if (e.hasChanged(EditorOption.fontInfo)) {
this._configureFont();
}
}));
_markdownRenderer.onDidRenderCodeBlock(() => this._scrollbar.scanDomNode(), this, this._disposables);
}
dispose(): void {
this._disposables.dispose();
this._renderDisposeable?.dispose();
this._renderDisposeable = undefined;
}
private _configureFont() {
const options = this._editor.getOptions();
const fontInfo = options.get(EditorOption.fontInfo);
const fontFamily = fontInfo.fontFamily;
const fontSize = options.get(EditorOption.suggestFontSize) || fontInfo.fontSize;
const lineHeight = options.get(EditorOption.suggestLineHeight) || fontInfo.lineHeight;
const fontWeight = fontInfo.fontWeight;
const fontSizePx = `${fontSize}px`;
const lineHeightPx = `${lineHeight}px`;
this.element.style.fontSize = fontSizePx;
this.element.style.fontWeight = fontWeight;
this.element.style.fontFeatureSettings = fontInfo.fontFeatureSettings;
this._type.style.fontFamily = fontFamily;
this._close.style.height = lineHeightPx;
this._close.style.width = lineHeightPx;
}
renderLoading(): void {
this._type.textContent = nls.localize('loading', "Loading...");
this._docs.textContent = '';
}
renderItem(item: CompletionItem, explainMode: boolean): void {
this._renderDisposeable?.dispose();
this._renderDisposeable = undefined;
let { documentation, detail } = item.completion;
// --- documentation
if (explainMode) {
let md = '';
md += `score: ${item.score[0]}${item.word ? `, compared '${item.completion.filterText && (item.completion.filterText + ' (filterText)') || item.completion.label}' with '${item.word}'` : ' (no prefix)'}\n`;
md += `distance: ${item.distance}, see localityBonus-setting\n`;
md += `index: ${item.idx}, based on ${item.completion.sortText && `sortText: "${item.completion.sortText}"` || 'label'}\n`;
documentation = new MarkdownString().appendCodeblock('empty', md);
detail = `Provider: ${item.provider._debugDisplayName}`;
}
if (!explainMode && !canExpandCompletionItem(item)) {
this._type.textContent = '';
this._docs.textContent = '';
this.element.classList.add('no-docs');
return;
}
this.element.classList.remove('no-docs');
if (typeof documentation === 'string') {
this._docs.classList.remove('markdown-docs');
this._docs.textContent = documentation;
} else {
this._docs.classList.add('markdown-docs');
this._docs.innerText = '';
const renderedContents = this._markdownRenderer.render(documentation);
this._renderDisposeable = renderedContents;
this._docs.appendChild(renderedContents.element);
}
// --- details
if (detail) {
this._type.textContent = detail.length > 100000 ? `${detail.substr(0, 100000)}` : detail;
dom.show(this._type);
} else {
dom.clearNode(this._type);
dom.hide(this._type);
}
this.element.style.height = this._header.offsetHeight + this._docs.offsetHeight + (this._borderWidth * 2) + 'px';
this.element.style.userSelect = 'text';
this.element.tabIndex = -1;
this._close.onmousedown = e => {
e.preventDefault();
e.stopPropagation();
};
this._close.onclick = e => {
e.preventDefault();
e.stopPropagation();
this._onDidClose.fire();
};
this._body.scrollTop = 0;
this._scrollbar.scanDomNode();
}
scrollDown(much = 8): void {
this._body.scrollTop += much;
}
scrollUp(much = 8): void {
this._body.scrollTop -= much;
}
scrollTop(): void {
this._body.scrollTop = 0;
}
scrollBottom(): void {
this._body.scrollTop = this._body.scrollHeight;
}
pageDown(): void {
this.scrollDown(80);
}
pageUp(): void {
this.scrollUp(80);
}
setBorderWidth(width: number): void {
this._borderWidth = width;
}
}

View file

@ -0,0 +1,241 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { createMatches } from 'vs/base/common/filters';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { append, $, hide, show } from 'vs/base/browser/dom';
import { IListRenderer } from 'vs/base/browser/ui/list/list';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { CompletionItem } from './suggest';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { CompletionItemKind, completionKindToCssClass, CompletionItemTag } from 'vs/editor/common/modes';
import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { IModelService } from 'vs/editor/common/services/modelService';
import { URI } from 'vs/base/common/uri';
import { FileKind } from 'vs/platform/files/common/files';
import { flatten } from 'vs/base/common/arrays';
import { canExpandCompletionItem } from './suggestWidgetDetails';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Emitter, Event } from 'vs/base/common/event';
export function getAriaId(index: number): string {
return `suggest-aria-id:${index}`;
}
export const suggestMoreInfoIcon = registerIcon('suggest-more-info', Codicon.chevronRight);
const colorRegExp = /^(#([\da-f]{3}){1,2}|(rgb|hsl)a\(\s*(\d{1,3}%?\s*,\s*){3}(1|0?\.\d+)\)|(rgb|hsl)\(\s*\d{1,3}%?(\s*,\s*\d{1,3}%?){2}\s*\))$/i;
function extractColor(item: CompletionItem, out: string[]): boolean {
const label = typeof item.completion.label === 'string'
? item.completion.label
: item.completion.label.name;
if (label.match(colorRegExp)) {
out[0] = label;
return true;
}
if (typeof item.completion.documentation === 'string' && item.completion.documentation.match(colorRegExp)) {
out[0] = item.completion.documentation;
return true;
}
return false;
}
export interface ISuggestionTemplateData {
root: HTMLElement;
/**
* Flexbox
* < ------------- left ------------ > < --- right -- >
* <icon><label><signature><qualifier> <type><readmore>
*/
left: HTMLElement;
right: HTMLElement;
icon: HTMLElement;
colorspan: HTMLElement;
iconLabel: IconLabel;
iconContainer: HTMLElement;
parametersLabel: HTMLElement;
qualifierLabel: HTMLElement;
/**
* Showing either `CompletionItem#details` or `CompletionItemLabel#type`
*/
detailsLabel: HTMLElement;
readMore: HTMLElement;
disposables: DisposableStore;
}
export class ItemRenderer implements IListRenderer<CompletionItem, ISuggestionTemplateData> {
private readonly _onDidToggleDetails = new Emitter<void>();
readonly onDidToggleDetails: Event<void> = this._onDidToggleDetails.event;
readonly templateId = 'suggestion';
constructor(
private readonly _editor: ICodeEditor,
private readonly _triggerKeybindingLabel: string,
@IModelService private readonly _modelService: IModelService,
@IModeService private readonly _modeService: IModeService,
@IThemeService private readonly _themeService: IThemeService
) { }
dispose(): void {
this._onDidToggleDetails.dispose();
}
renderTemplate(container: HTMLElement): ISuggestionTemplateData {
const data = <ISuggestionTemplateData>Object.create(null);
data.disposables = new DisposableStore();
data.root = container;
data.root.classList.add('show-file-icons');
data.icon = append(container, $('.icon'));
data.colorspan = append(data.icon, $('span.colorspan'));
const text = append(container, $('.contents'));
const main = append(text, $('.main'));
data.iconContainer = append(main, $('.icon-label.codicon'));
data.left = append(main, $('span.left'));
data.right = append(main, $('span.right'));
data.iconLabel = new IconLabel(data.left, { supportHighlights: true, supportCodicons: true });
data.disposables.add(data.iconLabel);
data.parametersLabel = append(data.left, $('span.signature-label'));
data.qualifierLabel = append(data.left, $('span.qualifier-label'));
data.detailsLabel = append(data.right, $('span.details-label'));
data.readMore = append(data.right, $('span.readMore' + suggestMoreInfoIcon.cssSelector));
data.readMore.title = nls.localize('readMore', "Read More ({0})", this._triggerKeybindingLabel);
const configureFont = () => {
const options = this._editor.getOptions();
const fontInfo = options.get(EditorOption.fontInfo);
const fontFamily = fontInfo.fontFamily;
const fontFeatureSettings = fontInfo.fontFeatureSettings;
const fontSize = options.get(EditorOption.suggestFontSize) || fontInfo.fontSize;
const lineHeight = options.get(EditorOption.suggestLineHeight) || fontInfo.lineHeight;
const fontWeight = fontInfo.fontWeight;
const fontSizePx = `${fontSize}px`;
const lineHeightPx = `${lineHeight}px`;
data.root.style.fontSize = fontSizePx;
data.root.style.fontWeight = fontWeight;
main.style.fontFamily = fontFamily;
main.style.fontFeatureSettings = fontFeatureSettings;
main.style.lineHeight = lineHeightPx;
data.icon.style.height = lineHeightPx;
data.icon.style.width = lineHeightPx;
data.readMore.style.height = lineHeightPx;
data.readMore.style.width = lineHeightPx;
};
configureFont();
data.disposables.add(this._editor.onDidChangeConfiguration(e => {
if (e.hasChanged(EditorOption.fontInfo) || e.hasChanged(EditorOption.suggestFontSize) || e.hasChanged(EditorOption.suggestLineHeight)) {
configureFont();
}
}));
return data;
}
renderElement(element: CompletionItem, index: number, data: ISuggestionTemplateData): void {
const { completion } = element;
const textLabel = typeof completion.label === 'string' ? completion.label : completion.label.name;
data.root.id = getAriaId(index);
data.colorspan.style.backgroundColor = '';
const labelOptions: IIconLabelValueOptions = {
labelEscapeNewLines: true,
matches: createMatches(element.score)
};
let color: string[] = [];
if (completion.kind === CompletionItemKind.Color && extractColor(element, color)) {
// special logic for 'color' completion items
data.icon.className = 'icon customcolor';
data.iconContainer.className = 'icon hide';
data.colorspan.style.backgroundColor = color[0];
} else if (completion.kind === CompletionItemKind.File && this._themeService.getFileIconTheme().hasFileIcons) {
// special logic for 'file' completion items
data.icon.className = 'icon hide';
data.iconContainer.className = 'icon hide';
const labelClasses = getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: textLabel }), FileKind.FILE);
const detailClasses = getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: completion.detail }), FileKind.FILE);
labelOptions.extraClasses = labelClasses.length > detailClasses.length ? labelClasses : detailClasses;
} else if (completion.kind === CompletionItemKind.Folder && this._themeService.getFileIconTheme().hasFolderIcons) {
// special logic for 'folder' completion items
data.icon.className = 'icon hide';
data.iconContainer.className = 'icon hide';
labelOptions.extraClasses = flatten([
getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: textLabel }), FileKind.FOLDER),
getIconClasses(this._modelService, this._modeService, URI.from({ scheme: 'fake', path: completion.detail }), FileKind.FOLDER)
]);
} else {
// normal icon
data.icon.className = 'icon hide';
data.iconContainer.className = '';
data.iconContainer.classList.add('suggest-icon', ...completionKindToCssClass(completion.kind).split(' '));
}
if (completion.tags && completion.tags.indexOf(CompletionItemTag.Deprecated) >= 0) {
labelOptions.extraClasses = (labelOptions.extraClasses || []).concat(['deprecated']);
labelOptions.matches = [];
}
data.iconLabel.setLabel(textLabel, undefined, labelOptions);
if (typeof completion.label === 'string') {
data.parametersLabel.textContent = '';
data.qualifierLabel.textContent = '';
data.detailsLabel.textContent = (completion.detail || '').replace(/\n.*$/m, '');
data.root.classList.add('string-label');
data.root.title = '';
} else {
data.parametersLabel.textContent = (completion.label.parameters || '').replace(/\n.*$/m, '');
data.qualifierLabel.textContent = (completion.label.qualifier || '').replace(/\n.*$/m, '');
data.detailsLabel.textContent = (completion.label.type || '').replace(/\n.*$/m, '');
data.root.classList.remove('string-label');
data.root.title = `${textLabel}${completion.label.parameters ?? ''} ${completion.label.qualifier ?? ''} ${completion.label.type ?? ''}`;
}
if (canExpandCompletionItem(element)) {
data.right.classList.add('can-expand-details');
show(data.readMore);
data.readMore.onmousedown = e => {
e.stopPropagation();
e.preventDefault();
};
data.readMore.onclick = e => {
e.stopPropagation();
e.preventDefault();
this._onDidToggleDetails.fire();
};
} else {
data.right.classList.remove('can-expand-details');
hide(data.readMore);
data.readMore.onmousedown = null;
data.readMore.onclick = null;
}
}
disposeTemplate(templateData: ISuggestionTemplateData): void {
templateData.disposables.dispose();
}
}

View file

@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { IActionViewItemProvider, IAction } from 'vs/base/common/actions';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { format } from 'vs/base/common/strings';
import { suggestWidgetStatusbarMenu } from 'vs/editor/contrib/suggest/suggest';
import { IMenuService } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
export class SuggestWidgetStatus {
readonly element: HTMLElement;
private readonly _disposables = new DisposableStore();
constructor(
container: HTMLElement,
@IKeybindingService keybindingService: IKeybindingService,
@IMenuService menuService: IMenuService,
@IContextKeyService contextKeyService: IContextKeyService,
) {
this.element = dom.append(container, dom.$('.suggest-status-bar'));
const actionViewItemProvider = <IActionViewItemProvider>(action => {
const kb = keybindingService.lookupKeybindings(action.id);
return new class extends ActionViewItem {
constructor() {
super(undefined, action, { label: true, icon: false });
}
updateLabel() {
if (isFalsyOrEmpty(kb) || !this.label) {
return super.updateLabel();
}
const { label } = this.getAction();
this.label.textContent = /{\d}/.test(label)
? format(this.getAction().label, kb[0].getLabel())
: `${this.getAction().label} (${kb[0].getLabel()})`;
}
};
});
const leftActions = new ActionBar(this.element, { actionViewItemProvider });
const rightActions = new ActionBar(this.element, { actionViewItemProvider });
const menu = menuService.createMenu(suggestWidgetStatusbarMenu, contextKeyService);
const renderMenu = () => {
const left: IAction[] = [];
const right: IAction[] = [];
for (let [group, actions] of menu.getActions()) {
if (group === 'left') {
left.push(...actions);
} else {
right.push(...actions);
}
}
leftActions.clear();
leftActions.push(left);
rightActions.clear();
rightActions.push(right);
};
this._disposables.add(menu.onDidChange(() => renderMenu()));
this._disposables.add(menu);
}
dispose(): void {
this._disposables.dispose();
this.element.remove();
}
}

View file

@ -302,33 +302,34 @@ export class ExecuteCommandAction extends Action {
export class SubmenuItemAction extends SubmenuAction {
readonly item: ISubmenuItem;
constructor(
readonly item: ISubmenuItem,
item: ISubmenuItem,
menuService: IMenuService,
contextKeyService: IContextKeyService,
options?: IMenuActionOptions
) {
super(`submenuitem.${item.submenu.id}`, typeof item.title === 'string' ? item.title : item.title.value, () => {
const result: IAction[] = [];
const menu = menuService.createMenu(item.submenu, contextKeyService);
const groups = menu.getActions(options);
menu.dispose();
const result: IAction[] = [];
const menu = menuService.createMenu(item.submenu, contextKeyService);
const groups = menu.getActions(options);
menu.dispose();
for (let group of groups) {
const [, actions] = group;
for (let group of groups) {
const [, actions] = group;
if (actions.length > 0) {
result.push(...actions);
result.push(new Separator());
}
if (actions.length > 0) {
result.push(...actions);
result.push(new Separator());
}
}
if (result.length) {
result.pop(); // remove last separator
}
if (result.length) {
result.pop(); // remove last separator
}
return result;
}, 'submenu');
super(`submenuitem.${item.submenu.id}`, typeof item.title === 'string' ? item.title : item.title.value, result, 'submenu');
this.item = item;
}
}

View file

@ -328,7 +328,7 @@ export abstract class AbstractContextKeyService implements IContextKeyService {
public abstract getContextValuesContainer(contextId: number): Context;
public abstract createChildContext(parentContextId?: number): number;
public abstract disposeContext(contextId: number): void;
public abstract updateParent(parentContextKeyService: IContextKeyService): void;
public abstract updateParent(parentContextKeyService?: IContextKeyService): void;
}
export class ContextKeyService extends AbstractContextKeyService implements IContextKeyService {
@ -423,6 +423,7 @@ class ScopedContextKeyService extends AbstractContextKeyService {
public dispose(): void {
this._isDisposed = true;
this._parent.disposeContext(this._myContextId);
this._parentChangeListener?.dispose();
if (this._domNode) {
this._domNode.removeAttribute(KEYBINDING_CONTEXT_ATTR);
this._domNode = undefined;

View file

@ -1139,7 +1139,7 @@ export interface IContextKeyService {
createScoped(target?: IContextKeyServiceTarget): IContextKeyService;
getContext(target: IContextKeyServiceTarget | null): IContext;
updateParent(parentContextKeyService: IContextKeyService): void;
updateParent(parentContextKeyService?: IContextKeyService): void;
}
export const SET_CONTEXT_COMMAND_ID = 'setContext';

View file

@ -56,7 +56,6 @@ export interface NativeParsedArgs {
'enable-proposed-api'?: string[]; // undefined or array of 1 or more
'open-url'?: boolean;
'skip-release-notes'?: boolean;
'disable-restore-windows'?: boolean;
'disable-telemetry'?: boolean;
'export-default-configuration'?: string;
'install-source'?: string;

View file

@ -90,7 +90,6 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
'driver': { type: 'string' },
'logExtensionHostCommunication': { type: 'boolean' },
'skip-release-notes': { type: 'boolean' },
'disable-restore-windows': { type: 'boolean' },
'disable-telemetry': { type: 'boolean' },
'disable-updates': { type: 'boolean' },
'disable-crash-reporter': { type: 'boolean' },

View file

@ -20,14 +20,14 @@ export function hasStdinWithoutTty() {
}
export function stdinDataListener(durationinMs: number): Promise<boolean> {
return new Promise(c => {
const dataListener = () => c(true);
return new Promise(resolve => {
const dataListener = () => resolve(true);
// wait for 1s maximum...
setTimeout(() => {
process.stdin.removeListener('data', dataListener);
c(false);
resolve(false);
}, durationinMs);
// ...but finish early if we detect data

View file

@ -505,19 +505,20 @@ export class FileChangesEvent {
constructor(public readonly changes: readonly IFileChange[], private readonly extUri: IExtUri) { }
/**
* Returns true if this change event contains the provided file with the given change type (if provided). In case of
* type DELETED, this method will also return true if a folder got deleted that is the parent of the
* provided file path.
* Returns true if this change event contains the provided file
* with the given change type (if provided). In case of type
* DELETED, this method will also return true if a folder got
* deleted that is the parent of the provided file path.
*/
contains(resource: URI, type?: FileChangeType): boolean {
contains(resource: URI, ...types: FileChangeType[]): boolean {
if (!resource) {
return false;
}
const checkForChangeType = !isUndefinedOrNull(type);
const hasTypesFilter = types.length > 0;
return this.changes.some(change => {
if (checkForChangeType && change.type !== type) {
if (hasTypesFilter && !types.includes(change.type)) {
return false;
}
@ -577,9 +578,7 @@ export class FileChangesEvent {
}
private hasType(type: FileChangeType): boolean {
return this.changes.some(change => {
return change.type === type;
});
return this.changes.some(change => change.type === type);
}
filter(filterFn: (change: IFileChange) => boolean): FileChangesEvent {
@ -862,11 +861,11 @@ export function whenProviderRegistered(file: URI, fileService: IFileService): Pr
return Promise.resolve();
}
return new Promise((c, e) => {
return new Promise(resolve => {
const disposable = fileService.onDidChangeFileSystemProviderRegistrations(e => {
if (e.scheme === file.scheme && e.added) {
disposable.dispose();
c();
resolve();
}
});
});

View file

@ -26,6 +26,9 @@ suite('Files', () => {
assert(!r1.contains(toResource.call(this, '/foo'), FileChangeType.UPDATED));
assert(r1.contains(toResource.call(this, '/foo/updated.txt'), FileChangeType.UPDATED));
assert(r1.contains(toResource.call(this, '/foo/updated.txt'), FileChangeType.UPDATED, FileChangeType.ADDED));
assert(r1.contains(toResource.call(this, '/foo/updated.txt'), FileChangeType.UPDATED, FileChangeType.ADDED, FileChangeType.DELETED));
assert(!r1.contains(toResource.call(this, '/foo/updated.txt'), FileChangeType.ADDED, FileChangeType.DELETED));
assert(!r1.contains(toResource.call(this, '/foo/updated.txt'), FileChangeType.ADDED));
assert(!r1.contains(toResource.call(this, '/foo/updated.txt'), FileChangeType.DELETED));

View file

@ -379,7 +379,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe
// Always allow to unload a window that is not yet ready
if (!window.isReady) {
return Promise.resolve(false);
return false;
}
this.logService.trace(`Lifecycle#unload() - window ID ${window.id}`);
@ -432,17 +432,17 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe
}
private onBeforeUnloadWindowInRenderer(window: ICodeWindow, reason: UnloadReason): Promise<boolean /* veto */> {
return new Promise<boolean>(c => {
return new Promise<boolean>(resolve => {
const oneTimeEventToken = this.oneTimeListenerTokenGenerator++;
const okChannel = `vscode:ok${oneTimeEventToken}`;
const cancelChannel = `vscode:cancel${oneTimeEventToken}`;
ipc.once(okChannel, () => {
c(false); // no veto
resolve(false); // no veto
});
ipc.once(cancelChannel, () => {
c(true); // veto
resolve(true); // veto
});
window.send('vscode:onBeforeUnload', { okChannel, cancelChannel, reason });

View file

@ -5,10 +5,9 @@
import { Event } from 'vs/base/common/event';
import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
import { IOpenedWindow, IWindowOpenable, IOpenEmptyWindowOptions, IOpenWindowOptions } from 'vs/platform/windows/common/windows';
import { IOpenedWindow, IWindowOpenable, IOpenEmptyWindowOptions, IOpenWindowOptions, IColorScheme } from 'vs/platform/windows/common/windows';
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { URI } from 'vs/base/common/uri';
export interface ICPUProperties {
@ -48,7 +47,7 @@ export interface ICommonNativeHostService {
readonly onOSResume: Event<unknown>;
readonly onColorSchemeChange: Event<ColorScheme>;
readonly onColorSchemeChange: Event<IColorScheme>;
// Window
getWindows(): Promise<IOpenedWindow[]>;

View file

@ -8,7 +8,7 @@ import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-m
import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Menu, BrowserWindow, app, clipboard, powerMonitor, nativeTheme } from 'electron';
import { OpenContext } from 'vs/platform/windows/node/window';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows';
import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions, IColorScheme } from 'vs/platform/windows/common/windows';
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { isMacintosh, isWindows, isRootUser, isLinux } from 'vs/base/common/platform';
import { ICommonNativeHostService, IOSProperties, IOSStatistics } from 'vs/platform/native/common/native';
@ -22,7 +22,6 @@ import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
import { arch, totalmem, release, platform, type, loadavg, freemem, cpus } from 'os';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { virtualMachineHint } from 'vs/base/node/id';
import { ILogService } from 'vs/platform/log/common/log';
import { dirname, join } from 'vs/base/common/path';
@ -52,16 +51,10 @@ export class NativeHostMainService implements INativeHostMainService {
// Color Scheme changes
nativeTheme.on('updated', () => {
let colorScheme: ColorScheme;
if (nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors) {
colorScheme = ColorScheme.HIGH_CONTRAST;
} else if (nativeTheme.shouldUseDarkColors) {
colorScheme = ColorScheme.DARK;
} else {
colorScheme = ColorScheme.LIGHT;
}
this._onColorSchemeChange.fire(colorScheme);
this._onColorSchemeChange.fire({
highContrast: nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors,
dark: nativeTheme.shouldUseDarkColors
});
});
}
@ -87,7 +80,7 @@ export class NativeHostMainService implements INativeHostMainService {
readonly onOSResume = Event.fromNodeEventEmitter(powerMonitor, 'resume');
private readonly _onColorSchemeChange = new Emitter<ColorScheme>();
private readonly _onColorSchemeChange = new Emitter<IColorScheme>();
readonly onColorSchemeChange = this._onColorSchemeChange.event;
//#endregion

View file

@ -20,7 +20,7 @@ if (isWeb || typeof require === 'undefined' || typeof require.__$__nodeRequire !
// Running out of sources
if (Object.keys(product).length === 0) {
Object.assign(product, {
version: '1.50.0-dev',
version: '1.51.0-dev',
nameShort: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev',
nameLong: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev',
applicationName: 'code-oss',

View file

@ -107,10 +107,17 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
this._socketsDispose.delete(localSocket.localAddress);
remoteSocket.end();
});
localSocket.on('close', () => remoteSocket.end());
localSocket.on('error', () => {
this._socketsDispose.delete(localSocket.localAddress);
remoteSocket.destroy();
});
remoteSocket.on('end', () => localSocket.end());
remoteSocket.on('close', () => localSocket.end());
remoteSocket.on('error', () => {
localSocket.destroy();
});
localSocket.pipe(remoteSocket);
remoteSocket.pipe(localSocket);

View file

@ -16,7 +16,12 @@ import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
import { InMemoryStorageDatabase } from 'vs/base/parts/storage/common/storage';
import { URI } from 'vs/base/common/uri';
suite('StorageService', () => {
suite('StorageService', function () {
// Given issues such as https://github.com/microsoft/vscode/issues/108113
// we see random test failures when accessing the native file system.
this.retries(3);
this.timeout(1000 * 10);
test('Remove Data (global, in-memory)', () => {
removeData(StorageScope.GLOBAL);

View file

@ -53,7 +53,7 @@ class ResourceStackElement {
}
public toString(): string {
return `[id:${this.id}] [group:${this.groupId}] [${this.isValid ? 'VALID' : 'INVALID'}] ${this.actual}`;
return `[id:${this.id}] [group:${this.groupId}] [${this.isValid ? ' VALID' : 'INVALID'}] ${this.actual.constructor.name} - ${this.actual}`;
}
}
@ -176,7 +176,7 @@ class WorkspaceStackElement {
}
public toString(): string {
return `[id:${this.id}] [group:${this.groupId}] [${this.invalidatedResources ? 'INVALID' : 'VALID'}] ${this.actual}`;
return `[id:${this.id}] [group:${this.groupId}] [${this.invalidatedResources ? 'INVALID' : ' VALID'}] ${this.actual.constructor.name} - ${this.actual}`;
}
}
@ -277,13 +277,6 @@ class ResourceEditStack {
}
}
this._future = [];
if (this._past.length > 0) {
const lastElement = this._past[this._past.length - 1];
if (lastElement.type === UndoRedoElementType.Resource && !lastElement.isValid) {
// clear undo stack
this._past = [];
}
}
this._past.push(element);
this.versionId++;
}

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { IFileService, IFileContent, FileChangesEvent, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
import { IFileService, IFileContent, FileChangesEvent, FileOperationResult, FileOperationError, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { VSBuffer } from 'vs/base/common/buffer';
import { URI } from 'vs/base/common/uri';
import {
@ -13,7 +13,7 @@ import {
IUserDataManifest, ISyncData, IRemoteUserData, PREVIEW_DIR_NAME, IResourcePreview as IBaseResourcePreview, Change, MergeState, IUserDataInitializer
} from 'vs/platform/userDataSync/common/userDataSync';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { joinPath, dirname, isEqual, basename } from 'vs/base/common/resources';
import { IExtUri, extUri, extUriIgnorePathCase } from 'vs/base/common/resources';
import { CancelablePromise, RunOnceScheduler, createCancelablePromise } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@ -53,8 +53,8 @@ function isSyncData(thing: any): thing is ISyncData {
return false;
}
function getLastSyncResourceUri(syncResource: SyncResource, environmentService: IEnvironmentService): URI {
return joinPath(environmentService.userDataSyncHome, syncResource, `lastSync${syncResource}.json`);
function getLastSyncResourceUri(syncResource: SyncResource, environmentService: IEnvironmentService, extUri: IExtUri): URI {
return extUri.joinPath(environmentService.userDataSyncHome, syncResource, `lastSync${syncResource}.json`);
}
export interface IResourcePreview {
@ -100,6 +100,7 @@ export abstract class AbstractSynchroniser extends Disposable {
protected readonly syncFolder: URI;
protected readonly syncPreviewFolder: URI;
protected readonly extUri: IExtUri;
private readonly currentMachineIdPromise: Promise<string>;
private _status: SyncStatus = SyncStatus.Idle;
@ -135,9 +136,10 @@ export abstract class AbstractSynchroniser extends Disposable {
) {
super();
this.syncResourceLogLabel = uppercaseFirstLetter(this.resource);
this.syncFolder = joinPath(environmentService.userDataSyncHome, resource);
this.syncPreviewFolder = joinPath(this.syncFolder, PREVIEW_DIR_NAME);
this.lastSyncResource = getLastSyncResourceUri(resource, environmentService);
this.extUri = this.fileService.hasCapability(environmentService.userDataSyncHome, FileSystemProviderCapabilities.PathCaseSensitive) ? extUri : extUriIgnorePathCase;
this.syncFolder = this.extUri.joinPath(environmentService.userDataSyncHome, resource);
this.syncPreviewFolder = this.extUri.joinPath(this.syncFolder, PREVIEW_DIR_NAME);
this.lastSyncResource = getLastSyncResourceUri(resource, environmentService, this.extUri);
this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService);
}
@ -423,7 +425,7 @@ export abstract class AbstractSynchroniser extends Disposable {
let preview = await this.syncPreviewPromise;
const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource }) =>
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
this.extUri.isEqual(localResource, resource) || this.extUri.isEqual(remoteResource, resource) || this.extUri.isEqual(previewResource, resource));
if (index === -1) {
return;
}
@ -483,7 +485,7 @@ export abstract class AbstractSynchroniser extends Disposable {
private updateConflicts(resourcePreviews: IEditableResourcePreview[]): void {
const conflicts = resourcePreviews.filter(({ mergeState }) => mergeState === MergeState.Conflict);
if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.previewResource, b.previewResource))) {
if (!equals(this._conflicts, conflicts, (a, b) => this.extUri.isEqual(a.previewResource, b.previewResource))) {
this._conflicts = conflicts;
this._onDidChangeConflicts.fire(conflicts);
}
@ -513,8 +515,8 @@ export abstract class AbstractSynchroniser extends Disposable {
}
async getMachineId({ uri }: ISyncResourceHandle): Promise<string | undefined> {
const ref = basename(uri);
if (isEqual(uri, this.toRemoteBackupResource(ref))) {
const ref = this.extUri.basename(uri);
if (this.extUri.isEqual(uri, this.toRemoteBackupResource(ref))) {
const { content } = await this.getUserData(ref);
if (content) {
const syncData = this.parseSyncData(content);
@ -525,12 +527,12 @@ export abstract class AbstractSynchroniser extends Disposable {
}
async resolveContent(uri: URI): Promise<string | null> {
const ref = basename(uri);
if (isEqual(uri, this.toRemoteBackupResource(ref))) {
const ref = this.extUri.basename(uri);
if (this.extUri.isEqual(uri, this.toRemoteBackupResource(ref))) {
const { content } = await this.getUserData(ref);
return content;
}
if (isEqual(uri, this.toLocalBackupResource(ref))) {
if (this.extUri.isEqual(uri, this.toLocalBackupResource(ref))) {
return this.userDataSyncBackupStoreService.resolveContent(this.resource, ref);
}
return null;
@ -540,13 +542,13 @@ export abstract class AbstractSynchroniser extends Disposable {
const syncPreview = this.syncPreviewPromise ? await this.syncPreviewPromise : null;
if (syncPreview) {
for (const resourcePreview of syncPreview.resourcePreviews) {
if (isEqual(resourcePreview.acceptedResource, uri)) {
if (this.extUri.isEqual(resourcePreview.acceptedResource, uri)) {
return resourcePreview.acceptResult ? resourcePreview.acceptResult.content : null;
}
if (isEqual(resourcePreview.remoteResource, uri)) {
if (this.extUri.isEqual(resourcePreview.remoteResource, uri)) {
return resourcePreview.remoteContent;
}
if (isEqual(resourcePreview.localResource, uri)) {
if (this.extUri.isEqual(resourcePreview.localResource, uri)) {
return resourcePreview.localContent;
}
}
@ -727,7 +729,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
@IConfigurationService configurationService: IConfigurationService,
) {
super(resource, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, configurationService);
this._register(this.fileService.watch(dirname(file)));
this._register(this.fileService.watch(this.extUri.dirname(file)));
this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e)));
}
@ -804,6 +806,7 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni
export abstract class AbstractInitializer implements IUserDataInitializer {
protected readonly extUri: IExtUri;
private readonly lastSyncResource: URI;
constructor(
@ -812,7 +815,8 @@ export abstract class AbstractInitializer implements IUserDataInitializer {
@IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService,
@IFileService protected readonly fileService: IFileService,
) {
this.lastSyncResource = getLastSyncResourceUri(this.resource, environmentService);
this.extUri = this.fileService.hasCapability(environmentService.userDataSyncHome, FileSystemProviderCapabilities.PathCaseSensitive) ? extUri : extUriIgnorePathCase;
this.lastSyncResource = getLastSyncResourceUri(this.resource, environmentService, extUri);
}
async initialize({ ref, content }: IUserData): Promise<void> {

View file

@ -18,7 +18,6 @@ import { merge, getIgnoredExtensions } from 'vs/platform/userDataSync/common/ext
import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { URI } from 'vs/base/common/uri';
import { joinPath, dirname, basename, isEqual } from 'vs/base/common/resources';
import { format } from 'vs/base/common/jsonFormatter';
import { applyEdits } from 'vs/base/common/jsonEdit';
import { compare } from 'vs/base/common/strings';
@ -82,7 +81,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
protected readonly version: number = 4;
protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); }
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'extensions.json');
private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'extensions.json');
private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
@ -183,17 +182,17 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
protected async getAcceptResult(resourcePreview: IExtensionResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IExtensionResourceMergeResult> {
/* Accept local resource */
if (isEqual(resource, this.localResource)) {
if (this.extUri.isEqual(resource, this.localResource)) {
return this.acceptLocal(resourcePreview);
}
/* Accept remote resource */
if (isEqual(resource, this.remoteResource)) {
if (this.extUri.isEqual(resource, this.remoteResource)) {
return this.acceptRemote(resourcePreview);
}
/* Accept preview resource */
if (isEqual(resource, this.previewResource)) {
if (this.extUri.isEqual(resource, this.previewResource)) {
return resourcePreview.previewResult;
}
@ -272,18 +271,18 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
}
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource: URI }[]> {
return [{ resource: joinPath(uri, 'extensions.json'), comparableResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI }];
return [{ resource: this.extUri.joinPath(uri, 'extensions.json'), comparableResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI }];
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) {
if (this.extUri.isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) {
const installedExtensions = await this.extensionManagementService.getInstalled();
const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService);
const localExtensions = this.getLocalExtensions(installedExtensions).filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier)));
return this.format(localExtensions);
}
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}
@ -292,11 +291,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
return content;
}
content = await super.resolveContent(dirname(uri));
content = await super.resolveContent(this.extUri.dirname(uri));
if (content) {
const syncData = this.parseSyncData(content);
if (syncData) {
switch (basename(uri)) {
switch (this.extUri.basename(uri)) {
case 'extensions.json':
return this.format(this.parseExtensions(syncData));
}

View file

@ -10,7 +10,6 @@ import {
import { VSBuffer } from 'vs/base/common/buffer';
import { Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { dirname, joinPath, basename, isEqual } from 'vs/base/common/resources';
import { IFileService } from 'vs/platform/files/common/files';
import { IStringDictionary } from 'vs/base/common/collections';
import { edit } from 'vs/platform/userDataSync/common/content';
@ -49,7 +48,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
private static readonly GLOBAL_STATE_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'globalState', path: `/globalState.json` });
protected readonly version: number = 1;
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'globalState.json');
private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'globalState.json');
private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
@ -67,7 +66,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
@IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
) {
super(SyncResource.GlobalState, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, configurationService);
this._register(this.fileService.watch(dirname(this.environmentService.argvResource)));
this._register(this.fileService.watch(this.extUri.dirname(this.environmentService.argvResource)));
this._register(
Event.any(
/* Locale change */
@ -123,17 +122,17 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
protected async getAcceptResult(resourcePreview: IGlobalStateResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IGlobalStateResourceMergeResult> {
/* Accept local resource */
if (isEqual(resource, this.localResource)) {
if (this.extUri.isEqual(resource, this.localResource)) {
return this.acceptLocal(resourcePreview);
}
/* Accept remote resource */
if (isEqual(resource, this.remoteResource)) {
if (this.extUri.isEqual(resource, this.remoteResource)) {
return this.acceptRemote(resourcePreview);
}
/* Accept preview resource */
if (isEqual(resource, this.previewResource)) {
if (this.extUri.isEqual(resource, this.previewResource)) {
return resourcePreview.previewResult;
}
@ -205,16 +204,16 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
}
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource: URI }[]> {
return [{ resource: joinPath(uri, 'globalState.json'), comparableResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI }];
return [{ resource: this.extUri.joinPath(uri, 'globalState.json'), comparableResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI }];
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(uri, GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI)) {
if (this.extUri.isEqual(uri, GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI)) {
const localGlobalState = await this.getLocalGlobalState();
return this.format(localGlobalState);
}
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}
@ -223,11 +222,11 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
return content;
}
content = await super.resolveContent(dirname(uri));
content = await super.resolveContent(this.extUri.dirname(uri));
if (content) {
const syncData = this.parseSyncData(content);
if (syncData) {
switch (basename(uri)) {
switch (this.extUri.basename(uri)) {
case 'globalState.json':
return this.format(JSON.parse(syncData.content));
}
@ -238,7 +237,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
}
private format(globalState: IGlobalState): string {
const storageKeys = Object.keys(globalState.storage).sort();
const storageKeys = globalState.storage ? Object.keys(globalState.storage).sort() : [];
const storage: IStringDictionary<IStorageValue> = {};
storageKeys.forEach(key => storage[key] = globalState.storage[key]);
globalState.storage = storage;

View file

@ -21,7 +21,6 @@ import { isNonEmptyArray } from 'vs/base/common/arrays';
import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { URI } from 'vs/base/common/uri';
import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { VSBuffer } from 'vs/base/common/buffer';
@ -55,7 +54,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
/* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
protected readonly version: number = 2;
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'keybindings.json');
private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'keybindings.json');
private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
@ -149,7 +148,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
protected async getAcceptResult(resourcePreview: IKeybindingsResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
/* Accept local resource */
if (isEqual(resource, this.localResource)) {
if (this.extUri.isEqual(resource, this.localResource)) {
return {
content: resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : null,
localChange: Change.None,
@ -158,7 +157,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
}
/* Accept remote resource */
if (isEqual(resource, this.remoteResource)) {
if (this.extUri.isEqual(resource, this.remoteResource)) {
return {
content: resourcePreview.remoteContent,
localChange: Change.Modified,
@ -167,7 +166,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
}
/* Accept preview resource */
if (isEqual(resource, this.previewResource)) {
if (this.extUri.isEqual(resource, this.previewResource)) {
if (content === undefined) {
return {
content: resourcePreview.previewResult.content,
@ -258,22 +257,22 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource: URI }[]> {
const comparableResource = (await this.fileService.exists(this.file)) ? this.file : this.localResource;
return [{ resource: joinPath(uri, 'keybindings.json'), comparableResource }];
return [{ resource: this.extUri.joinPath(uri, 'keybindings.json'), comparableResource }];
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}
let content = await super.resolveContent(uri);
if (content) {
return content;
}
content = await super.resolveContent(dirname(uri));
content = await super.resolveContent(this.extUri.dirname(uri));
if (content) {
const syncData = this.parseSyncData(content);
if (syncData) {
switch (basename(uri)) {
switch (this.extUri.basename(uri)) {
case 'keybindings.json':
return this.getKeybindingsContentFromSyncContent(syncData.content);
}

View file

@ -21,7 +21,6 @@ import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFile
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { URI } from 'vs/base/common/uri';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Edit } from 'vs/base/common/jsonFormatter';
import { setProperty, applyEdits } from 'vs/base/common/jsonEdit';
@ -49,7 +48,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
/* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
protected readonly version: number = 2;
readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'settings.json');
readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'settings.json');
readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
@ -141,7 +140,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
const ignoredSettings = await this.getIgnoredSettings();
/* Accept local resource */
if (isEqual(resource, this.localResource)) {
if (this.extUri.isEqual(resource, this.localResource)) {
return {
/* Remove ignored settings */
content: resourcePreview.fileContent ? updateIgnoredSettings(resourcePreview.fileContent.value.toString(), '{}', ignoredSettings, formattingOptions) : null,
@ -151,7 +150,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
}
/* Accept remote resource */
if (isEqual(resource, this.remoteResource)) {
if (this.extUri.isEqual(resource, this.remoteResource)) {
return {
/* Update ignored settings from local file content */
content: resourcePreview.remoteContent !== null ? updateIgnoredSettings(resourcePreview.remoteContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formattingOptions) : null,
@ -161,7 +160,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
}
/* Accept preview resource */
if (isEqual(resource, this.previewResource)) {
if (this.extUri.isEqual(resource, this.previewResource)) {
if (content === undefined) {
return {
content: resourcePreview.previewResult.content,
@ -244,24 +243,24 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource: URI }[]> {
const comparableResource = (await this.fileService.exists(this.file)) ? this.file : this.localResource;
return [{ resource: joinPath(uri, 'settings.json'), comparableResource }];
return [{ resource: this.extUri.joinPath(uri, 'settings.json'), comparableResource }];
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}
let content = await super.resolveContent(uri);
if (content) {
return content;
}
content = await super.resolveContent(dirname(uri));
content = await super.resolveContent(this.extUri.dirname(uri));
if (content) {
const syncData = this.parseSyncData(content);
if (syncData) {
const settingsSyncContent = this.parseSettingsSyncContent(syncData.content);
if (settingsSyncContent) {
switch (basename(uri)) {
switch (this.extUri.basename(uri)) {
case 'settings.json':
return settingsSyncContent.settings;
}

View file

@ -14,7 +14,6 @@ import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IFileResource
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStringDictionary } from 'vs/base/common/collections';
import { URI } from 'vs/base/common/uri';
import { joinPath, extname, relativePath, isEqualOrParent, basename, dirname } from 'vs/base/common/resources';
import { VSBuffer } from 'vs/base/common/buffer';
import { merge, IMergeResult as ISnippetsMergeResult, areSame } from 'vs/platform/userDataSync/common/snippetsMerge';
import { CancellationToken } from 'vs/base/common/cancellation';
@ -53,7 +52,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
private onFileChanges(e: FileChangesEvent): void {
if (!e.changes.some(change => isEqualOrParent(change.resource, this.snippetsFolder))) {
if (!e.changes.some(change => this.extUri.isEqualOrParent(change.resource, this.snippetsFolder))) {
return;
}
this.triggerLocalChange();
@ -82,7 +81,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
protected async getAcceptResult(resourcePreview: ISnippetsResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
/* Accept local resource */
if (isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))) {
if (this.extUri.isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))) {
return {
content: resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : null,
localChange: Change.None,
@ -93,7 +92,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
/* Accept remote resource */
if (isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))) {
if (this.extUri.isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))) {
return {
content: resourcePreview.remoteContent,
localChange: resourcePreview.remoteContent !== null
@ -104,7 +103,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
/* Accept preview resource */
if (isEqualOrParent(resource, this.syncPreviewFolder)) {
if (this.extUri.isEqualOrParent(resource, this.syncPreviewFolder)) {
if (content === undefined) {
return {
content: resourcePreview.previewResult.content,
@ -172,15 +171,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
};
resourcePreviews.set(key, {
fileContent: null,
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localContent: null,
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
@ -193,16 +192,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
remoteChange: Change.None,
};
resourcePreviews.set(key, {
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key],
localContent: localFileContent[key].value.toString(),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
@ -215,16 +214,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
remoteChange: Change.None,
};
resourcePreviews.set(key, {
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key],
localContent: localFileContent[key].value.toString(),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
@ -237,16 +236,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
remoteChange: Change.Added,
};
resourcePreviews.set(key, {
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key],
localContent: localFileContent[key].value.toString(),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
@ -259,16 +258,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
remoteChange: Change.Modified,
};
resourcePreviews.set(key, {
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key],
localContent: localFileContent[key].value.toString(),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
@ -281,16 +280,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
remoteChange: Change.Deleted,
};
resourcePreviews.set(key, {
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: null,
localContent: null,
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
@ -303,16 +302,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
remoteChange: remoteSnippets[key] ? Change.Modified : Change.Added
};
resourcePreviews.set(key, {
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key] || null,
localContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key] || null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
@ -326,16 +325,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
remoteChange: Change.None
};
resourcePreviews.set(key, {
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key] || null,
localContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key] || null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
}
@ -351,10 +350,10 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
const snippets = this.parseSnippets(syncData);
const result = [];
for (const snippet of Object.keys(snippets)) {
const resource = joinPath(uri, snippet);
const comparableResource = joinPath(this.snippetsFolder, snippet);
const resource = this.extUri.joinPath(uri, snippet);
const comparableResource = this.extUri.joinPath(this.snippetsFolder, snippet);
const exists = await this.fileService.exists(comparableResource);
result.push({ resource, comparableResource: exists ? comparableResource : joinPath(this.syncPreviewFolder, snippet).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }) });
result.push({ resource, comparableResource: exists ? comparableResource : this.extUri.joinPath(this.syncPreviewFolder, snippet).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }) });
}
return result;
}
@ -363,9 +362,9 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))
|| isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))
|| isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }))) {
if (this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))
|| this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))
|| this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }))) {
return this.resolvePreviewContent(uri);
}
@ -374,12 +373,12 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
return content;
}
content = await super.resolveContent(dirname(uri));
content = await super.resolveContent(this.extUri.dirname(uri));
if (content) {
const syncData = this.parseSyncData(content);
if (syncData) {
const snippets = this.parseSnippets(syncData);
return snippets[basename(uri)] || null;
return snippets[this.extUri.basename(uri)] || null;
}
}
@ -402,7 +401,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
const local: IStringDictionary<IFileContent> = {};
for (const resourcePreview of resourcePreviews) {
if (resourcePreview.fileContent) {
local[basename(resourcePreview.localResource!)] = resourcePreview.fileContent;
local[this.extUri.basename(resourcePreview.localResource!)] = resourcePreview.fileContent;
}
}
await this.backupLocal(JSON.stringify(this.toSnippetsContents(local)));
@ -411,28 +410,28 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
private async updateLocalSnippets(resourcePreviews: ISnippetsAcceptedResourcePreview[], force: boolean): Promise<void> {
for (const { fileContent, acceptResult, localResource, remoteResource, localChange } of resourcePreviews) {
if (localChange !== Change.None) {
const key = remoteResource ? basename(remoteResource) : basename(localResource!);
const resource = joinPath(this.snippetsFolder, key);
const key = remoteResource ? this.extUri.basename(remoteResource) : this.extUri.basename(localResource!);
const resource = this.extUri.joinPath(this.snippetsFolder, key);
// Removed
if (localChange === Change.Deleted) {
this.logService.trace(`${this.syncResourceLogLabel}: Deleting snippet...`, basename(resource));
this.logService.trace(`${this.syncResourceLogLabel}: Deleting snippet...`, this.extUri.basename(resource));
await this.fileService.del(resource);
this.logService.info(`${this.syncResourceLogLabel}: Deleted snippet`, basename(resource));
this.logService.info(`${this.syncResourceLogLabel}: Deleted snippet`, this.extUri.basename(resource));
}
// Added
else if (localChange === Change.Added) {
this.logService.trace(`${this.syncResourceLogLabel}: Creating snippet...`, basename(resource));
this.logService.trace(`${this.syncResourceLogLabel}: Creating snippet...`, this.extUri.basename(resource));
await this.fileService.createFile(resource, VSBuffer.fromString(acceptResult.content!), { overwrite: force });
this.logService.info(`${this.syncResourceLogLabel}: Created snippet`, basename(resource));
this.logService.info(`${this.syncResourceLogLabel}: Created snippet`, this.extUri.basename(resource));
}
// Updated
else {
this.logService.trace(`${this.syncResourceLogLabel}: Updating snippet...`, basename(resource));
this.logService.trace(`${this.syncResourceLogLabel}: Updating snippet...`, this.extUri.basename(resource));
await this.fileService.writeFile(resource, VSBuffer.fromString(acceptResult.content!), force ? undefined : fileContent!);
this.logService.info(`${this.syncResourceLogLabel}: Updated snippet`, basename(resource));
this.logService.info(`${this.syncResourceLogLabel}: Updated snippet`, this.extUri.basename(resource));
}
}
}
@ -444,7 +443,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
for (const { acceptResult, localResource, remoteResource, remoteChange } of resourcePreviews) {
if (remoteChange !== Change.None) {
const key = localResource ? basename(localResource) : basename(remoteResource!);
const key = localResource ? this.extUri.basename(localResource) : this.extUri.basename(remoteResource!);
if (remoteChange === Change.Deleted) {
delete newSnippets[key];
} else {
@ -489,9 +488,9 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
for (const entry of stat.children || []) {
const resource = entry.resource;
const extension = extname(resource);
const extension = this.extUri.extname(resource);
if (extension === '.json' || extension === '.code-snippets') {
const key = relativePath(this.snippetsFolder, resource)!;
const key = this.extUri.relativePath(this.snippetsFolder, resource)!;
const content = await this.fileService.readFile(resource);
snippets[key] = content;
}
@ -526,9 +525,9 @@ export class SnippetsInitializer extends AbstractInitializer {
for (const key of Object.keys(remoteSnippets)) {
const content = remoteSnippets[key];
if (content) {
const resource = joinPath(this.environmentService.snippetsHome, key);
const resource = this.extUri.joinPath(this.environmentService.snippetsHome, key);
await this.fileService.createFile(resource, VSBuffer.fromString(content));
this.logService.info('Created snippet', basename(resource));
this.logService.info('Created snippet', this.extUri.basename(resource));
}
}

Some files were not shown because too many files have changed in this diff Show more