Merge branch 'master' into clantz/dev-container

This commit is contained in:
Chuck Lantz 2020-06-29 06:47:03 -07:00 committed by GitHub
commit 0710dca120
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
967 changed files with 32022 additions and 23475 deletions

View file

@ -519,7 +519,9 @@
"**/vs/workbench/services/**/common/**",
"**/vs/workbench/api/**/common/**",
"vscode-textmate",
"vscode-oniguruma"
"vscode-oniguruma",
"iconv-lite-umd",
"semver-umd"
]
},
{

View file

@ -1,82 +1,181 @@
{
"$schema": "https://raw.githubusercontent.com/microsoft/vscode-github-triage-actions/master/classifier/apply/apply-labels/classifier-config.schema.json",
"$schema": "https://raw.githubusercontent.com/microsoft/vscode-github-triage-actions/master/classifier-deep/apply/apply-labels/deep-classifier-config.schema.json",
"assignees": {
"JacksonKearl": {
"assign": true
},
"bpasero": {
"assign": true
},
"Tyriar": {
"assign": true
},
"misolori": {
"assign": true
},
"joaomoreno": {
"assign": true
}
"JacksonKearl": {"accuracy": 0.5}
},
"labels": {
"search-editor": {
"applyLabel": true,
"assign": [
"JacksonKearl"
]
},
"snippets": {
"applyLabel": false,
"assign": [
"jrieken"
]
},
"integrated-terminal": {
"applyLabel": true,
"assign": [
"Tyriar"
]
},
"workbench-editors": {
"applyLabel": false,
"assign": [
"bpasero"
]
},
"workbench-history": {
"applyLabel": false,
"assign": [
"bpasero"
]
},
"workbench-notifications": {
"applyLabel": false,
"assign": [
"bpasero"
]
},
"workbench-tabs": {
"applyLabel": false,
"assign": [
"bpasero"
]
},
"icons-product": {
"applyLabel": true,
"assign": [
"misolori"
]
},
"ux": {
"applyLabel": true,
"assign": [
"misolori"
]
},
"git": {
"applyLabel": false,
"assign": [
"joaomoreno"
]
"L10N": {"assign": []},
"VIM": {"assign": []},
"api": {"assign": ["jrieken"]},
"api-finalization": {"assign": []},
"api-proposal": {"assign": ["jrieken"]},
"authentication": {"assign": ["RMacfarlane"]},
"breadcrumbs": {"assign": ["jrieken"]},
"callhierarchy": {"assign": ["jrieken"]},
"code-lens": {"assign": ["jrieken"]},
"color-palette": {"assign": []},
"comments": {"assign": ["rebornix"]},
"config": {"assign": ["sandy081"]},
"context-keys": {"assign": []},
"css-less-scss": {"assign": ["aeschli"]},
"custom-editors": {"assign": ["mjbvz"]},
"debug": {"assign": ["weinand"]},
"debug-console": {"assign": ["weinand"]},
"dialogs": {"assign": ["sbatten"]},
"diff-editor": {"assign": []},
"dropdown": {"assign": []},
"editor": {"assign": ["rebornix"]},
"editor-autoclosing": {"assign": []},
"editor-autoindent": {"assign": ["rebornix"]},
"editor-bracket-matching": {"assign": []},
"editor-clipboard": {"assign": ["jrieken"]},
"editor-code-actions": {"assign": []},
"editor-color-picker": {"assign": ["rebornix"]},
"editor-columnselect": {"assign": ["alexdima"]},
"editor-commands": {"assign": ["jrieken"]},
"editor-comments": {"assign": []},
"editor-contrib": {"assign": []},
"editor-core": {"assign": []},
"editor-drag-and-drop": {"assign": ["rebornix"]},
"editor-error-widget": {"assign": ["sandy081"]},
"editor-find": {"assign": ["rebornix"]},
"editor-folding": {"assign": ["aeschli"]},
"editor-hover": {"assign": []},
"editor-indent-guides": {"assign": []},
"editor-input": {"assign": ["alexdima"]},
"editor-input-IME": {"assign": ["rebornix"]},
"editor-minimap": {"assign": []},
"editor-multicursor": {"assign": ["alexdima"]},
"editor-parameter-hints": {"assign": []},
"editor-render-whitespace": {"assign": []},
"editor-rendering": {"assign": ["alexdima"]},
"editor-scrollbar": {"assign": []},
"editor-symbols": {"assign": ["jrieken"]},
"editor-synced-region": {"assign": ["aeschli"]},
"editor-textbuffer": {"assign": ["rebornix"]},
"editor-theming": {"assign": []},
"editor-wordnav": {"assign": ["alexdima"]},
"editor-wrapping": {"assign": ["alexdima"]},
"emmet": {"assign": []},
"error-list": {"assign": ["sandy081"]},
"explorer-custom": {"assign": ["sandy081"]},
"extension-host": {"assign": []},
"extensions": {"assign": ["sandy081"]},
"extensions-development": {"assign": []},
"file-decorations": {"assign": ["jrieken"]},
"file-encoding": {"assign": ["bpasero"]},
"file-explorer": {"assign": ["isidorn"]},
"file-glob": {"assign": []},
"file-guess-encoding": {"assign": ["bpasero"]},
"file-io": {"assign": ["bpasero"]},
"file-watcher": {"assign": ["bpasero"]},
"font-rendering": {"assign": []},
"formatting": {"assign": []},
"git": {"assign": ["joaomoreno"]},
"gpu": {"assign": ["deepak1556"]},
"grammar": {"assign": ["mjbvz"]},
"grid-view": {"assign": ["joaomoreno"]},
"html": {"assign": ["aeschli"]},
"i18n": {"assign": []},
"icon-brand": {"assign": []},
"icons-product": {"assign": ["misolori"]},
"install-update": {"assign": []},
"integrated-terminal": {"assign": ["Tyriar"]},
"integrated-terminal-conpty": {"assign": ["Tyriar"]},
"integrated-terminal-links": {"assign": ["Tyriar"]},
"integration-test": {"assign": []},
"intellisense-config": {"assign": []},
"ipc": {"assign": ["joaomoreno"]},
"issue-bot": {"assign": ["chrmarti"]},
"issue-reporter": {"assign": ["RMacfarlane"]},
"javascript": {"assign": ["mjbvz"]},
"json": {"assign": ["aeschli"]},
"keybindings": {"assign": []},
"keybindings-editor": {"assign": ["sandy081"]},
"keyboard-layout": {"assign": ["alexdima"]},
"languages-basic": {"assign": ["aeschli"]},
"languages-diagnostics": {"assign": ["jrieken"]},
"layout": {"assign": ["sbatten"]},
"lcd-text-rendering": {"assign": []},
"list": {"assign": ["joaomoreno"]},
"log": {"assign": []},
"markdown": {"assign": ["mjbvz"]},
"marketplace": {"assign": []},
"menus": {"assign": ["sbatten"]},
"merge-conflict": {"assign": ["chrmarti"]},
"notebook": {"assign": ["rebornix"]},
"outline": {"assign": ["jrieken"]},
"output": {"assign": []},
"perf": {"assign": []},
"perf-bloat": {"assign": []},
"perf-startup": {"assign": []},
"php": {"assign": ["roblourens"]},
"portable-mode": {"assign": ["joaomoreno"]},
"proxy": {"assign": []},
"quick-pick": {"assign": ["chrmarti"]},
"references-viewlet": {"assign": ["jrieken"]},
"release-notes": {"assign": []},
"remote": {"assign": []},
"remote-explorer": {"assign": ["alexr00"]},
"rename": {"assign": ["jrieken"]},
"scm": {"assign": ["joaomoreno"]},
"screencast-mode": {"assign": ["joaomoreno"]},
"search": {"assign": ["roblourens"]},
"search-editor": {"assign": ["JacksonKearl"]},
"search-replace": {"assign": ["sandy081"]},
"semantic-tokens": {"assign": ["aeschli"]},
"settings-editor": {"assign": ["roblourens"]},
"settings-sync": {"assign": ["sandy081"]},
"simple-file-dialog": {"assign": ["alexr00"]},
"smart-select": {"assign": ["jrieken"]},
"smoke-test": {"assign": []},
"snap": {"assign": ["joaomoreno"]},
"snippets": {"assign": ["jrieken"]},
"splitview": {"assign": ["joaomoreno"]},
"suggest": {"assign": ["jrieken"]},
"tasks": {"assign": ["alexr00"]},
"telemetry": {"assign": []},
"themes": {"assign": ["aeschli"]},
"timeline": {"assign": ["eamodio"]},
"timeline-git": {"assign": ["eamodio"]},
"titlebar": {"assign": ["sbatten"]},
"tokenization": {"assign": []},
"tree": {"assign": ["joaomoreno"]},
"typescript": {"assign": ["mjbvz"]},
"undo-redo": {"assign": []},
"unit-test": {"assign": []},
"uri": {"assign": ["jrieken"]},
"ux": {"assign": ["misolori"]},
"variable-resolving": {"assign": []},
"vscode-build": {"assign": []},
"web": {"assign": ["bpasero"]},
"webview": {"assign": ["mjbvz"]},
"workbench-cli": {"assign": []},
"workbench-diagnostics": {"assign": ["RMacfarlane"]},
"workbench-dnd": {"assign": ["bpasero"]},
"workbench-editor-grid": {"assign": ["sbatten"]},
"workbench-editors": {"assign": ["bpasero"]},
"workbench-electron": {"assign": ["deepak1556"]},
"workbench-feedback": {"assign": ["bpasero"]},
"workbench-history": {"assign": ["bpasero"]},
"workbench-hot-exit": {"assign": ["Tyriar"]},
"workbench-launch": {"assign": []},
"workbench-link": {"assign": []},
"workbench-multiroot": {"assign": ["bpasero"]},
"workbench-notifications": {"assign": ["bpasero"]},
"workbench-os-integration": {"assign": []},
"workbench-rapid-render": {"assign": ["jrieken"]},
"workbench-run-as-admin": {"assign": []},
"workbench-state": {"assign": ["bpasero"]},
"workbench-status": {"assign": ["bpasero"]},
"workbench-tabs": {"assign": ["bpasero"]},
"workbench-touchbar": {"assign": ["bpasero"]},
"workbench-views": {"assign": ["sbatten"]},
"workbench-welcome": {"assign": ["chrmarti"]},
"workbench-window": {"assign": ["bpasero"]},
"workbench-zen": {"assign": ["isidorn"]},
"workspace-edit": {"assign": ["jrieken"]},
"workspace-symbols": {"assign": []},
"zoom": {"assign": ["alexdima"] }
}
}
}

15
.github/commands.json vendored
View file

@ -221,6 +221,19 @@
"addLabel": "*caused-by-extension",
"comment": "It looks like this is caused by the C++ extension. Please file it with the repository [here](https://github.com/Microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!"
},
{
"type": "comment",
"name": "extCpp",
"allowUsers": [
"cleidigh",
"usernamehw",
"gjsjohnmurray",
"IllusionMH"
],
"action": "close",
"addLabel": "*caused-by-extension",
"comment": "It looks like this is caused by the C++ extension. Please file it with the repository [here](https://github.com/Microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!"
},
{
"type": "comment",
"name": "extTS",
@ -271,7 +284,7 @@
],
"action": "close",
"addLabel": "*caused-by-extension",
"comment": "It looks like this is caused by the Go extension. Please file it with the repository [here](https://github.com/microsoft/vscode-go). Make sure to check their [contributing guidelines](https://github.com/microsoft/vscode-go/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!"
"comment": "It looks like this is caused by the Go extension. Please file it with the repository [here](https://github.com/golang/vscode-go). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines for more information.\n\nHappy Coding!"
},
{
"type": "comment",

View file

@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v22
ref: v31
path: ./actions
- name: Install Actions
if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'author-verification-requested')
@ -32,6 +32,7 @@ jobs:
if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'author-verification-requested')
uses: ./actions/author-verified
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
requestVerificationComment: "This bug has been fixed in to the latest release of [VS Code Insiders](https://code.visualstudio.com/insiders/)!\n\n@${author}, you can help us out by commenting `/verified` if things are now working as expected.\n\nIf things still don't seem right, please ensure you're on version ${commit} of Insiders (today's or later - you can use `Help: About` in the command pallette to check), and leave a comment letting us know what isn't working as expected.\n\nHappy Coding!"
pendingReleaseLabel: awaiting-insiders-release

View file

@ -1,40 +0,0 @@
name: "Classifier: Trainer"
on:
schedule:
- cron: 0 0 12 * *
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout Actions
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: master
lfs: true
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Install Additional Dependencies
# Pulls in a bunch of other packages that arent needed for the rest of the actions
run: npm install @azure/storage-blob@12
- name: "Run Classifier: Scraper"
uses: ./actions/classifier/train/fetch-issues
with:
token: ${{secrets.ISSUE_SCRAPER_TOKEN}} # My personal token, so as to not risk going over quota on main token
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
python-version: 3.7
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade numpy scipy scikit-learn joblib nltk
- name: "Run Classifier: Generator"
run: python ./actions/classifier/train/generate-models/generate.py category
- name: "Run Classifier: Upload"
uses: ./actions/classifier/train/upload-models
with:
blobContainerName: classifier-models
blobStorageKey: ${{secrets.AZURE_BLOB_STORAGE_CONNECTION_STRING}}

47
.github/workflows/codeql.yml vendored Normal file
View file

@ -0,0 +1,47 @@
name: "Code Scanning"
on: [push, pull_request]
jobs:
CodeQL-Build:
# CodeQL runs on ubuntu-latest and windows-latest
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: javascript
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View file

@ -13,11 +13,12 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v22
ref: v31
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run Commands
uses: ./actions/commands
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
config-path: commands

View file

@ -0,0 +1,23 @@
name: "Deep Classifier: Monitor"
on:
issues:
types: [unassigned]
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout Actions
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v31
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions
- name: "Run Classifier: Monitor"
uses: ./actions/classifier-deep/monitor
with:
botName: vscode-triage-bot
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}

View file

@ -1,7 +1,9 @@
name: "Classifier: Apply"
name: "Deep Classifier: Runner"
on:
schedule:
- cron: 0,30 * * * *
- cron: 0/30 * * * *
repository_dispatch:
types: [trigger-deep-classifier-runner]
jobs:
main:
@ -11,7 +13,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v22
ref: v31
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions
@ -19,13 +21,16 @@ jobs:
# Pulls in a bunch of other packages that arent needed for the rest of the actions
run: npm install @azure/storage-blob@12
- name: "Run Classifier: Scraper"
uses: ./actions/classifier/apply/fetch-issues
uses: ./actions/classifier-deep/apply/fetch-sources
with:
# slightly overlapping to protect against issues slipping through the cracks if a run is delayed
from: 45
from: 40
until: 5
blobContainerName: classifier-models
configPath: classifier
blobContainerName: vscode-issue-classifier
blobStorageKey: ${{secrets.AZURE_BLOB_STORAGE_CONNECTION_STRING}}
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
- name: Set up Python 3.7
uses: actions/setup-python@v1
with:
@ -33,11 +38,13 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade numpy scipy scikit-learn joblib nltk
pip install --upgrade numpy scipy scikit-learn joblib nltk simpletransformers torch torchvision
- name: "Run Classifier: Generator"
run: python ./actions/classifier/apply/generate-labels/main.py
run: python ./actions/classifier-deep/apply/generate-labels/main.py
- name: "Run Classifier: Labeler"
uses: ./actions/classifier/apply/apply-labels
uses: ./actions/classifier-deep/apply/apply-labels
with:
config-path: classifier
configPath: classifier
allowLabels: "needs more info|new release"
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}

View file

@ -0,0 +1,27 @@
name: "Deep Classifier: Scraper"
on:
repository_dispatch:
types: [trigger-deep-classifier-scraper]
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout Actions
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v31
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Install Additional Dependencies
# Pulls in a bunch of other packages that arent needed for the rest of the actions
run: npm install @azure/storage-blob@12
- name: "Run Classifier: Scraper"
uses: ./actions/classifier-deep/train/fetch-issues
with:
blobContainerName: vscode-issue-classifier
blobStorageKey: ${{secrets.AZURE_BLOB_STORAGE_CONNECTION_STRING}}
token: ${{secrets.ISSUE_SCRAPER_TOKEN}}
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}

View file

@ -13,7 +13,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v22
ref: v31
path: ./actions
- name: Install Actions
if: contains(github.event.issue.labels.*.name, '*english-please')
@ -22,6 +22,7 @@ jobs:
if: contains(github.event.issue.labels.*.name, '*english-please')
uses: ./actions/english-please
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
cognitiveServicesAPIKey: ${{secrets.AZURE_TEXT_TRANSLATOR_KEY}}
nonEnglishLabel: "*english-please"

View file

@ -18,7 +18,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v22
ref: v31
- name: Install Actions
if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request')
run: npm install --production --prefix ./actions
@ -26,6 +26,7 @@ jobs:
if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request')
uses: ./actions/feature-request
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
candidateMilestoneID: 107
candidateMilestoneName: Backlog Candidates

View file

@ -2,6 +2,8 @@ name: Latest Release Monitor
on:
schedule:
- cron: 0/5 * * * *
repository_dispatch:
types: [trigger-latest-release-monitor]
jobs:
main:
@ -12,7 +14,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v22
ref: v31
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Install Storage Module
@ -20,5 +22,6 @@ jobs:
- name: Run Latest Release Monitor
uses: ./actions/latest-release-monitor
with:
storageKey: ${{secrets.AZURE_BLOB_STORAGE_CONNECTION_STRING_NEW}}
storageKey: ${{secrets.AZURE_BLOB_STORAGE_CONNECTION_STRING}}
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}

View file

@ -14,12 +14,13 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v22
ref: v31
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run Locker
uses: ./actions/locker
with:
daysSinceClose: 45
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
daysSinceUpdate: 3
ignoredLabel: "*out-of-scope"

View file

@ -14,12 +14,13 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v22
ref: v31
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run Needs More Info Closer
uses: ./actions/needs-more-info-closer
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
label: needs more info
closeDays: 7
additionalTeam: "cleidigh|usernamehw|gjsjohnmurray|IllusionMH"

View file

@ -11,7 +11,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v22
ref: v31
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions
@ -27,6 +27,7 @@ jobs:
if: contains(github.event.issue.labels.*.name, 'author-verification-requested')
uses: ./actions/author-verified
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
requestVerificationComment: "This bug has been fixed in to the latest release of [VS Code Insiders](https://code.visualstudio.com/insiders/)!\n\n@${author}, you can help us out by confirming things are working as expected in the latest Insiders release. If things look good, please leave a comment with the text `/verified` to let us know. If not, please ensure you're on version ${commit} of Insiders (today's or later - you can use `Help: About` in the command pallete to check), and leave a comment letting us know what isn't working as expected.\n\nHappy Coding!"
pendingReleaseLabel: awaiting-insiders-release
authorVerificationRequestedLabel: author-verification-requested
@ -35,6 +36,7 @@ jobs:
- name: Run Commands
uses: ./actions/commands
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
config-path: commands
@ -44,6 +46,7 @@ jobs:
if: contains(github.event.issue.labels.*.name, 'feature-request')
uses: ./actions/feature-request
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
candidateMilestoneID: 107
candidateMilestoneName: Backlog Candidates
@ -64,6 +67,7 @@ jobs:
if: contains(github.event.issue.labels.*.name, 'testplan-item') || contains(github.event.issue.labels.*.name, 'invalid-testplan-item')
uses: ./actions/test-plan-item-validator
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
label: testplan-item
invalidLabel: invalid-testplan-item
comment: Invalid test plan item. See errors below and the [test plan item spec](https://github.com/microsoft/vscode/wiki/Writing-Test-Plan-Items) for more information. This comment will go away when the issues are resolved.
@ -73,6 +77,7 @@ jobs:
if: contains(github.event.issue.labels.*.name, '*english-please')
uses: ./actions/english-please
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
cognitiveServicesAPIKey: ${{secrets.AZURE_TEXT_TRANSLATOR_KEY}}
nonEnglishLabel: "*english-please"

View file

@ -11,7 +11,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v22
ref: v31
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions
@ -19,12 +19,14 @@ jobs:
- name: Run CopyCat (JacksonKearl/testissues)
uses: ./actions/copycat
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
owner: JacksonKearl
repo: testissues
- name: Run CopyCat (chrmarti/testissues)
uses: ./actions/copycat
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
owner: chrmarti
repo: testissues
@ -33,6 +35,7 @@ jobs:
uses: ./actions/new-release
with:
label: new release
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
labelColor: "006b75"
labelDescription: Issues found in a recent release of VS Code
days: 5
@ -40,15 +43,25 @@ jobs:
- name: Run Clipboard Labeler
uses: ./actions/regex-labeler
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
label: "invalid"
mustNotMatch: "^We have written the needed data into your clipboard because it was too large to send\\. Please paste\\.$"
comment: "It looks like you're using the VS Code Issue Reporter but did not paste the text generated into the created issue. We've closed this issue, please open a new one containing the text we placed in your clipboard.\n\nHappy Coding!"
- name: Run Clipboard Labeler (Chinese)
uses: ./actions/regex-labeler
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
label: "invalid"
mustNotMatch: "^所需的数据太大,无法直接发送。我们已经将其写入剪贴板,请粘贴。$"
comment: "看起来您正在使用 VS Code 问题报告程序,但是没有将生成的文本粘贴到创建的问题中。我们将关闭这个问题,请使用剪贴板中的内容创建一个新的问题。\n\n祝您使用愉快"
# source of truth in ./english-please.yml
- name: Run English Please
uses: ./actions/english-please
with:
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
cognitiveServicesAPIKey: ${{secrets.AZURE_TEXT_TRANSLATOR_KEY}}
nonEnglishLabel: "*english-please"
needsMoreInfoLabel: "needs more info"

View file

@ -13,7 +13,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v22
ref: v31
path: ./actions
- name: Checkout Repo
if: github.event_name != 'issues'
@ -27,5 +27,6 @@ jobs:
uses: ./actions/release-pipeline
with:
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
notYetReleasedLabel: unreleased
insidersReleasedLabel: insiders-released

23
.github/workflows/rich-navigation.yml vendored Normal file
View file

@ -0,0 +1,23 @@
name: "Rich Navigation Indexing"
on:
pull_request:
push:
branches:
- master
jobs:
richnav:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
- name: Install dependencies
run: yarn --frozen-lockfile
env:
CHILD_CONCURRENCY: 1
- uses: microsoft/RichCodeNavIndexer@master
with:
languages: typescript
repo-token: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true

View file

@ -14,7 +14,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v22
ref: v31
- 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
@ -23,5 +23,6 @@ jobs:
uses: ./actions/test-plan-item-validator
with:
label: testplan-item
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
invalidLabel: invalid-testplan-item
comment: Invalid test plan item. See errors below and the [test plan item spec](https://github.com/microsoft/vscode/wiki/Writing-Test-Plan-Items) for more information. This comment will go away when the issues are resolved.

View file

@ -4,6 +4,7 @@
"recommendations": [
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"msjsdiag.debugger-for-chrome"
"msjsdiag.debugger-for-chrome",
"ms-vscode.vscode-github-issue-notebooks"
]
}

2
.vscode/launch.json vendored
View file

@ -219,7 +219,7 @@
"type": "node",
"request": "launch",
"name": "VS Code (Web)",
"program": "${workspaceFolder}/scripts/code-web.js",
"program": "${workspaceFolder}/resources/serverless/code-web.js",
"presentation": {
"group": "0_vscode",
"order": 2

36
.vscode/notebooks/inbox.github-issues vendored Normal file
View file

@ -0,0 +1,36 @@
[
{
"kind": 1,
"language": "markdown",
"value": "##### `Config`: defines the inbox query"
},
{
"kind": 2,
"language": "github-issues",
"value": "$inbox=repo:microsoft/vscode is:open no:assignee -label:feature-request -label:testplan-item -label:plan-item "
},
{
"kind": 1,
"language": "markdown",
"value": "## Triage Inbox\n\nAll inbox issues but not those that need more information. These issues need to be triaged, e.g assigned to a user or ask for more information",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$inbox -label:\"needs more info\"",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "## Inbox\n\nAll issues that have no assignee and that have neither **feature requests** nor **test plan items** nor **plan items**.",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$inbox",
"editable": true
}
]

98
.vscode/notebooks/my-work.github-issues vendored Normal file
View file

@ -0,0 +1,98 @@
[
{
"kind": 1,
"language": "markdown",
"value": "##### `Config`: This should be changed every month/milestone",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks\n\n// current milestone name\n$milestone=milestone:\"June 2020\"",
"editable": true
},
{
"kind": 1,
"language": "github-issues",
"value": "## Milestone Work",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$repos $milestone assignee:@me is:open\n",
"editable": false
},
{
"kind": 1,
"language": "github-issues",
"value": "## Bugs, Debt, Features...",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "#### My Bugs",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$repos assignee:@me is:open label:bug",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "#### Debt & Engineering",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$repos assignee:@me is:open label:debt OR $repos assignee:@me is:open label:engineering",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "#### Performance 🐌 🔜 🏎",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$repos assignee:@me is:open label:perf OR $repos assignee:@me is:open label:perf-startup OR $repos assignee:@me is:open label:perf-bloat OR $repos assignee:@me is:open label:freeze-slow-crash-leak",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "#### Feature Requests",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$repos assignee:@me is:open label:feature-request milestone:Backlog sort:reactions-+1-desc",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$repos assignee:@me is:open milestone:\"Backlog Candidates\"",
"editable": false
},
{
"kind": 1,
"language": "markdown",
"value": "#### Not Actionable",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$repos assignee:@me is:open label:\"needs more info\"",
"editable": false
}
]

View file

@ -26,6 +26,7 @@
"test/automation/out/**": true,
"test/integration/browser/out/**": true,
"src/vs/base/test/node/uri.test.data.txt": true,
"src/vs/workbench/test/browser/api/extHostDocumentData.test.perf-data.ts": true,
"src/vs/server": false
},
"lcov.path": [
@ -72,8 +73,9 @@
},
"gulp.autoDetect": "off",
"files.insertFinalNewline": true,
"[typescript]": {
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"typescript.tsc.autoDetect": "off"
"typescript.tsc.autoDetect": "off",
"typescript.tsserver.useSeparateSyntaxServer": "dynamic"
}

View file

@ -1,3 +1,3 @@
disturl "https://atom.io/download/electron"
target "7.3.1"
target "8.3.3"
runtime "electron"

27
build/.webignore Normal file
View file

@ -0,0 +1,27 @@
# cleanup rules for web node modules, .gitignore style
**/*.txt
**/*.json
**/*.md
**/*.d.ts
**/*.js.map
**/LICENSE
**/CONTRIBUTORS
jschardet/index.js
jschardet/src/**
jschardet/dist/jschardet.js
vscode-textmate/webpack.config.js
xterm/src/**
xterm-addon-search/src/**
xterm-addon-search/out/**
xterm-addon-search/fixtures/**
xterm-addon-unicode11/src/**
xterm-addon-unicode11/out/**
xterm-addon-webgl/src/**
xterm-addon-webgl/out/**

View file

@ -10,10 +10,10 @@ git clone --depth 1 https://github.com/Microsoft/vscode-node-debug2.git
git clone --depth 1 https://github.com/Microsoft/vscode-node-debug.git
git clone --depth 1 https://github.com/Microsoft/vscode-html-languageservice.git
git clone --depth 1 https://github.com/Microsoft/vscode-json-languageservice.git
$BUILD_SOURCESDIRECTORY/build/node_modules/.bin/vscode-telemetry-extractor --sourceDir $BUILD_SOURCESDIRECTORY --excludedDir $BUILD_SOURCESDIRECTORY/extensions --outputDir . --applyEndpoints
$BUILD_SOURCESDIRECTORY/build/node_modules/.bin/vscode-telemetry-extractor --config $BUILD_SOURCESDIRECTORY/build/azure-pipelines/common/telemetry-config.json -o .
node $BUILD_SOURCESDIRECTORY/build/node_modules/.bin/vscode-telemetry-extractor --sourceDir $BUILD_SOURCESDIRECTORY --excludedDir $BUILD_SOURCESDIRECTORY/extensions --outputDir . --applyEndpoints
node $BUILD_SOURCESDIRECTORY/build/node_modules/.bin/vscode-telemetry-extractor --config $BUILD_SOURCESDIRECTORY/build/azure-pipelines/common/telemetry-config.json -o .
mkdir -p $BUILD_SOURCESDIRECTORY/.build/telemetry
mv declarations-resolved.json $BUILD_SOURCESDIRECTORY/.build/telemetry/telemetry-core.json
mv config-resolved.json $BUILD_SOURCESDIRECTORY/.build/telemetry/telemetry-extensions.json
cd ..
rm -rf extraction
rm -rf extraction

View file

@ -31,10 +31,10 @@ steps:
git config user.email "vscode@microsoft.com"
git config user.name "VSCode"
git checkout origin/electron-8.0.x
git checkout origin/electron-x.y.z
git merge origin/master
# Push master branch into exploration branch
git push origin HEAD:electron-8.0.x
git push origin HEAD:electron-x.y.z
displayName: Sync & Merge Exploration

View file

@ -218,7 +218,7 @@ steps:
restoreSolution: 'build\azure-pipelines\win32\ESRPClient\packages.config'
feedsToUse: config
nugetConfigPath: 'build\azure-pipelines\win32\ESRPClient\NuGet.config'
externalFeedCredentials: 3fc0b7f7-da09-4ae7-a9c8-d69824b1819b
externalFeedCredentials: 'ESRP Nuget'
restoreDirectory: packages
- task: ESRPImportCertTask@1

View file

@ -10,7 +10,7 @@ const path = require('path');
let window = null;
app.once('ready', () => {
window = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true, webviewTag: true, enableWebSQL: false } });
window = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true, webviewTag: true, enableWebSQL: false, nativeWindowOpen: true } });
window.setMenuBarVisibility(false);
window.loadURL(url.format({ pathname: path.join(__dirname, 'index.html'), protocol: 'file:', slashes: true }));
// window.webContents.openDevTools();

View file

@ -8,9 +8,11 @@ require('events').EventEmitter.defaultMaxListeners = 100;
const gulp = require('gulp');
const path = require('path');
const nodeUtil = require('util');
const tsb = require('gulp-tsb');
const es = require('event-stream');
const filter = require('gulp-filter');
const webpack = require('webpack');
const util = require('./lib/util');
const task = require('./lib/task');
const watcher = require('./lib/watch');
@ -21,6 +23,8 @@ const nlsDev = require('vscode-nls-dev');
const root = path.dirname(__dirname);
const commit = util.getVersion(root);
const plumber = require('gulp-plumber');
const fancyLog = require('fancy-log');
const ansiColors = require('ansi-colors');
const ext = require('./lib/extensions');
const extensionsPath = path.join(path.dirname(__dirname), 'extensions');
@ -167,3 +171,78 @@ const compileExtensionsBuildTask = task.define('compile-extensions-build', task.
gulp.task(compileExtensionsBuildTask);
exports.compileExtensionsBuildTask = compileExtensionsBuildTask;
const compileWebExtensionsTask = task.define('compile-web', () => buildWebExtensions(false));
gulp.task(compileWebExtensionsTask);
exports.compileWebExtensionsTask = compileWebExtensionsTask;
const watchWebExtensionsTask = task.define('watch-web', () => buildWebExtensions(true));
gulp.task(watchWebExtensionsTask);
exports.watchWebExtensionsTask = watchWebExtensionsTask;
async function buildWebExtensions(isWatch) {
const webpackConfigLocations = await nodeUtil.promisify(glob)(
path.join(extensionsPath, '**', 'extension-browser.webpack.config.js'),
{ ignore: ['**/node_modules'] }
);
const webpackConfigs = [];
for (const webpackConfigPath of webpackConfigLocations) {
const configOrFnOrArray = require(webpackConfigPath);
function addConfig(configOrFn) {
if (typeof configOrFn === 'function') {
webpackConfigs.push(configOrFn({}, {}));
} else {
webpackConfigs.push(configOrFn);
}
}
addConfig(configOrFnOrArray);
}
function reporter(fullStats) {
if (Array.isArray(fullStats.children)) {
for (const stats of fullStats.children) {
const outputPath = stats.outputPath;
if (outputPath) {
const relativePath = path.relative(extensionsPath, outputPath).replace(/\\/g, '/');
const match = relativePath.match(/[^\/]+(\/server|\/client)?/);
fancyLog(`Finished ${ansiColors.green('packaging web extension')} ${ansiColors.cyan(match[0])} with ${stats.errors.length} errors.`);
}
if (Array.isArray(stats.errors)) {
stats.errors.forEach(error => {
fancyLog.error(error);
});
}
if (Array.isArray(stats.warnings)) {
stats.warnings.forEach(warning => {
fancyLog.warn(warning);
});
}
}
}
}
return new Promise((resolve, reject) => {
if (isWatch) {
webpack(webpackConfigs).watch({}, (err, stats) => {
if (err) {
reject();
} else {
reporter(stats.toJson());
}
});
} else {
webpack(webpackConfigs).run((err, stats) => {
if (err) {
fancyLog.error(err);
reject();
} else {
reporter(stats.toJson());
resolve();
}
});
}
});
}

View file

@ -124,7 +124,7 @@ const copyrightFilter = [
'!extensions/html-language-features/server/src/modes/typescript/*',
'!extensions/*/server/bin/*',
'!src/vs/editor/test/node/classification/typescript-test.ts',
'!scripts/code-web.js'
'!resources/serverless/code-web.js'
];
const jsHygieneFilter = [

View file

@ -47,6 +47,7 @@ const nodeModules = ['electron', 'original-fs']
const vscodeEntryPoints = _.flatten([
buildfile.entrypoint('vs/workbench/workbench.desktop.main'),
buildfile.base,
buildfile.workerExtensionHost,
buildfile.workbenchDesktop,
buildfile.code
]);
@ -58,6 +59,7 @@ const vscodeResources = [
'out-build/bootstrap.js',
'out-build/bootstrap-fork.js',
'out-build/bootstrap-amd.js',
'out-build/bootstrap-node.js',
'out-build/bootstrap-window.js',
'out-build/paths.js',
'out-build/vs/**/*.{svg,png,html}',
@ -72,6 +74,7 @@ const vscodeResources = [
'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt',
'out-build/vs/workbench/contrib/webview/browser/pre/*.js',
'out-build/vs/workbench/contrib/webview/electron-browser/pre/*.js',
'out-build/vs/workbench/services/extensions/worker/extensionHostWorkerMain.js',
'out-build/vs/**/markdown.css',
'out-build/vs/workbench/contrib/tasks/**/*.json',
'out-build/vs/platform/files/**/*.exe',

View file

@ -13,6 +13,10 @@ module.exports = new class ApiLiteralOrTypes {
create(context) {
return {
['TSTypeAnnotation TSUnionType TSLiteralType']: (node) => {
var _a;
if (((_a = node.literal) === null || _a === void 0 ? void 0 : _a.type) === 'TSNullKeyword') {
return;
}
context.report({
node: node,
messageId: 'useEnum'

View file

@ -15,6 +15,9 @@ export = new class ApiLiteralOrTypes implements eslint.Rule.RuleModule {
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
return {
['TSTypeAnnotation TSUnionType TSLiteralType']: (node: any) => {
if (node.literal?.type === 'TSNullKeyword') {
return;
}
context.report({
node: node,
messageId: 'useEnum'

View file

@ -4,7 +4,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.packageMarketplaceExtensionsStream = exports.packageLocalExtensionsStream = exports.fromMarketplace = void 0;
exports.scanBuiltinExtensions = exports.packageMarketplaceWebExtensionsStream = exports.packageMarketplaceExtensionsStream = exports.packageLocalWebExtensionsStream = exports.packageLocalExtensionsStream = exports.fromMarketplace = void 0;
const es = require("event-stream");
const fs = require("fs");
const glob = require("glob");
@ -28,11 +28,7 @@ const util = require('./util');
const root = path.dirname(path.dirname(__dirname));
const commit = util.getVersion(root);
const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`;
function fromLocal(extensionPath) {
const webpackFilename = path.join(extensionPath, 'extension.webpack.config.js');
const input = fs.existsSync(webpackFilename)
? fromLocalWebpack(extensionPath)
: fromLocalNormal(extensionPath);
function minimizeLanguageJSON(input) {
const tmLanguageJsonFilter = filter('**/*.tmLanguage.json', { restore: true });
return input
.pipe(tmLanguageJsonFilter)
@ -43,12 +39,49 @@ function fromLocal(extensionPath) {
}))
.pipe(tmLanguageJsonFilter.restore);
}
function fromLocalWebpack(extensionPath) {
function updateExtensionPackageJSON(input, update) {
const packageJsonFilter = filter('extensions/*/package.json', { restore: true });
return input
.pipe(packageJsonFilter)
.pipe(buffer())
.pipe(es.mapSync((f) => {
const data = JSON.parse(f.contents.toString('utf8'));
f.contents = Buffer.from(JSON.stringify(update(data)));
return f;
}))
.pipe(packageJsonFilter.restore);
}
function fromLocal(extensionPath, forWeb) {
const webpackConfigFileName = forWeb ? 'extension-browser.webpack.config.js' : 'extension.webpack.config.js';
const isWebPacked = fs.existsSync(path.join(extensionPath, webpackConfigFileName));
let input = isWebPacked
? fromLocalWebpack(extensionPath, webpackConfigFileName)
: fromLocalNormal(extensionPath);
if (forWeb) {
input = updateExtensionPackageJSON(input, (data) => {
if (data.browser) {
data.main = data.browser;
}
data.extensionKind = ['web'];
return data;
});
}
else if (isWebPacked) {
input = updateExtensionPackageJSON(input, (data) => {
if (data.main) {
data.main = data.main.replace('/out/', /dist/);
}
return data;
});
}
return minimizeLanguageJSON(input);
}
function fromLocalWebpack(extensionPath, webpackConfigFileName) {
const result = es.through();
const packagedDependencies = [];
const packageJsonConfig = require(path.join(extensionPath, 'package.json'));
if (packageJsonConfig.dependencies) {
const webpackRootConfig = require(path.join(extensionPath, 'extension.webpack.config.js'));
const webpackRootConfig = require(path.join(extensionPath, webpackConfigFileName));
for (const key in webpackRootConfig.externals) {
if (key in packageJsonConfig.dependencies) {
packagedDependencies.push(key);
@ -64,30 +97,9 @@ function fromLocalWebpack(extensionPath) {
base: extensionPath,
contents: fs.createReadStream(filePath)
}));
const filesStream = es.readArray(files);
// check for a webpack configuration files, then invoke webpack
// and merge its output with the files stream. also rewrite the package.json
// file to a new entry point
const webpackConfigLocations = glob.sync(path.join(extensionPath, '/**/extension.webpack.config.js'), { ignore: ['**/node_modules'] });
const packageJsonFilter = filter(f => {
if (path.basename(f.path) === 'package.json') {
// only modify package.json's next to the webpack file.
// to be safe, use existsSync instead of path comparison.
return fs.existsSync(path.join(path.dirname(f.path), 'extension.webpack.config.js'));
}
return false;
}, { restore: true });
const patchFilesStream = filesStream
.pipe(packageJsonFilter)
.pipe(buffer())
.pipe(json((data) => {
if (data.main) {
// hardcoded entry point directory!
data.main = data.main.replace('/out/', /dist/);
}
return data;
}))
.pipe(packageJsonFilter.restore);
// and merge its output with the files stream.
const webpackConfigLocations = glob.sync(path.join(extensionPath, '**', webpackConfigFileName), { ignore: ['**/node_modules'] });
const webpackStreams = webpackConfigLocations.map(webpackConfigPath => {
const webpackDone = (err, stats) => {
fancyLog(`Bundled extension: ${ansiColors.yellow(path.join(path.basename(extensionPath), path.relative(extensionPath, webpackConfigPath)))}...`);
@ -121,7 +133,7 @@ function fromLocalWebpack(extensionPath) {
this.emit('data', data);
}));
});
es.merge(...webpackStreams, patchFilesStream)
es.merge(...webpackStreams, es.readArray(files))
// .pipe(es.through(function (data) {
// // debug
// console.log('out', data.path, data.contents.length);
@ -198,15 +210,32 @@ function packageLocalExtensionsStream() {
})
.filter(({ name }) => excludedExtensions.indexOf(name) === -1)
.filter(({ name }) => builtInExtensions.every(b => b.name !== name));
const nodeModules = gulp.src('extensions/node_modules/**', { base: '.' });
const localExtensions = localExtensionDescriptions.map(extension => {
return fromLocal(extension.path)
return fromLocal(extension.path, false)
.pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`));
});
const nodeModules = gulp.src('extensions/node_modules/**', { base: '.' });
return es.merge(nodeModules, ...localExtensions)
.pipe(util2.setExecutableBit(['**/*.sh']));
}
exports.packageLocalExtensionsStream = packageLocalExtensionsStream;
function packageLocalWebExtensionsStream() {
const localExtensionDescriptions = glob.sync('extensions/*/package.json')
.filter(manifestPath => {
const packageJsonConfig = require(path.join(root, manifestPath));
return !packageJsonConfig.main || packageJsonConfig.browser;
})
.map(manifestPath => {
const extensionPath = path.dirname(path.join(root, manifestPath));
const extensionName = path.basename(extensionPath);
return { name: extensionName, path: extensionPath };
});
return es.merge(...localExtensionDescriptions.map(extension => {
return fromLocal(extension.path, true)
.pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`));
}));
}
exports.packageLocalWebExtensionsStream = packageLocalWebExtensionsStream;
function packageMarketplaceExtensionsStream() {
const extensions = builtInExtensions.map(extension => {
return fromMarketplace(extension.name, extension.version, extension.metadata)
@ -216,3 +245,47 @@ function packageMarketplaceExtensionsStream() {
.pipe(util2.setExecutableBit(['**/*.sh']));
}
exports.packageMarketplaceExtensionsStream = packageMarketplaceExtensionsStream;
function packageMarketplaceWebExtensionsStream(builtInExtensions) {
const extensions = builtInExtensions
.map(extension => {
const input = fromMarketplace(extension.name, extension.version, extension.metadata)
.pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`));
return updateExtensionPackageJSON(input, (data) => {
if (data.main) {
data.browser = data.main;
}
data.extensionKind = ['web'];
return data;
});
});
return es.merge(extensions);
}
exports.packageMarketplaceWebExtensionsStream = packageMarketplaceWebExtensionsStream;
function scanBuiltinExtensions(extensionsRoot, forWeb) {
const scannedExtensions = [];
const extensionsFolders = fs.readdirSync(extensionsRoot);
for (const extensionFolder of extensionsFolders) {
const packageJSONPath = path.join(extensionsRoot, extensionFolder, 'package.json');
if (!fs.existsSync(packageJSONPath)) {
continue;
}
const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath).toString('utf8'));
const extensionKind = packageJSON['extensionKind'] || [];
if (forWeb && extensionKind.indexOf('web') === -1) {
continue;
}
const children = fs.readdirSync(path.join(extensionsRoot, extensionFolder));
const packageNLS = children.filter(child => child === 'package.nls.json')[0];
const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
scannedExtensions.push({
extensionPath: extensionFolder,
packageJSON,
packageNLSPath: packageNLS ? path.join(extensionFolder, packageNLS) : undefined,
readmePath: readme ? path.join(extensionFolder, readme) : undefined,
changelogPath: changelog ? path.join(extensionFolder, changelog) : undefined,
});
}
return scannedExtensions;
}
exports.scanBuiltinExtensions = scanBuiltinExtensions;

View file

@ -28,14 +28,8 @@ const root = path.dirname(path.dirname(__dirname));
const commit = util.getVersion(root);
const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`;
function fromLocal(extensionPath: string): Stream {
const webpackFilename = path.join(extensionPath, 'extension.webpack.config.js');
const input = fs.existsSync(webpackFilename)
? fromLocalWebpack(extensionPath)
: fromLocalNormal(extensionPath);
function minimizeLanguageJSON(input: Stream): Stream {
const tmLanguageJsonFilter = filter('**/*.tmLanguage.json', { restore: true });
return input
.pipe(tmLanguageJsonFilter)
.pipe(buffer())
@ -46,13 +40,55 @@ function fromLocal(extensionPath: string): Stream {
.pipe(tmLanguageJsonFilter.restore);
}
function fromLocalWebpack(extensionPath: string): Stream {
function updateExtensionPackageJSON(input: Stream, update: (data: any) => any): Stream {
const packageJsonFilter = filter('extensions/*/package.json', { restore: true });
return input
.pipe(packageJsonFilter)
.pipe(buffer())
.pipe(es.mapSync((f: File) => {
const data = JSON.parse(f.contents.toString('utf8'));
f.contents = Buffer.from(JSON.stringify(update(data)));
return f;
}))
.pipe(packageJsonFilter.restore);
}
function fromLocal(extensionPath: string, forWeb: boolean): Stream {
const webpackConfigFileName = forWeb ? 'extension-browser.webpack.config.js' : 'extension.webpack.config.js';
const isWebPacked = fs.existsSync(path.join(extensionPath, webpackConfigFileName));
let input = isWebPacked
? fromLocalWebpack(extensionPath, webpackConfigFileName)
: fromLocalNormal(extensionPath);
if (forWeb) {
input = updateExtensionPackageJSON(input, (data: any) => {
if (data.browser) {
data.main = data.browser;
}
data.extensionKind = ['web'];
return data;
});
} else if (isWebPacked) {
input = updateExtensionPackageJSON(input, (data: any) => {
if (data.main) {
data.main = data.main.replace('/out/', /dist/);
}
return data;
});
}
return minimizeLanguageJSON(input);
}
function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string): Stream {
const result = es.through();
const packagedDependencies: string[] = [];
const packageJsonConfig = require(path.join(extensionPath, 'package.json'));
if (packageJsonConfig.dependencies) {
const webpackRootConfig = require(path.join(extensionPath, 'extension.webpack.config.js'));
const webpackRootConfig = require(path.join(extensionPath, webpackConfigFileName));
for (const key in webpackRootConfig.externals) {
if (key in packageJsonConfig.dependencies) {
packagedDependencies.push(key);
@ -70,38 +106,13 @@ function fromLocalWebpack(extensionPath: string): Stream {
contents: fs.createReadStream(filePath) as any
}));
const filesStream = es.readArray(files);
// check for a webpack configuration files, then invoke webpack
// and merge its output with the files stream. also rewrite the package.json
// file to a new entry point
// and merge its output with the files stream.
const webpackConfigLocations = (<string[]>glob.sync(
path.join(extensionPath, '/**/extension.webpack.config.js'),
path.join(extensionPath, '**', webpackConfigFileName),
{ ignore: ['**/node_modules'] }
));
const packageJsonFilter = filter(f => {
if (path.basename(f.path) === 'package.json') {
// only modify package.json's next to the webpack file.
// to be safe, use existsSync instead of path comparison.
return fs.existsSync(path.join(path.dirname(f.path), 'extension.webpack.config.js'));
}
return false;
}, { restore: true });
const patchFilesStream = filesStream
.pipe(packageJsonFilter)
.pipe(buffer())
.pipe(json((data: any) => {
if (data.main) {
// hardcoded entry point directory!
data.main = data.main.replace('/out/', /dist/);
}
return data;
}))
.pipe(packageJsonFilter.restore);
const webpackStreams = webpackConfigLocations.map(webpackConfigPath => {
const webpackDone = (err: any, stats: any) => {
@ -143,7 +154,7 @@ function fromLocalWebpack(extensionPath: string): Stream {
}));
});
es.merge(...webpackStreams, patchFilesStream)
es.merge(...webpackStreams, es.readArray(files))
// .pipe(es.through(function (data) {
// // debug
// console.log('out', data.path, data.contents.length);
@ -242,16 +253,35 @@ export function packageLocalExtensionsStream(): NodeJS.ReadWriteStream {
.filter(({ name }) => excludedExtensions.indexOf(name) === -1)
.filter(({ name }) => builtInExtensions.every(b => b.name !== name));
const nodeModules = gulp.src('extensions/node_modules/**', { base: '.' });
const localExtensions = localExtensionDescriptions.map(extension => {
return fromLocal(extension.path)
return fromLocal(extension.path, false)
.pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`));
});
const nodeModules = gulp.src('extensions/node_modules/**', { base: '.' });
return es.merge(nodeModules, ...localExtensions)
.pipe(util2.setExecutableBit(['**/*.sh']));
}
export function packageLocalWebExtensionsStream(): NodeJS.ReadWriteStream {
const localExtensionDescriptions = (<string[]>glob.sync('extensions/*/package.json'))
.filter(manifestPath => {
const packageJsonConfig = require(path.join(root, manifestPath));
return !packageJsonConfig.main || packageJsonConfig.browser;
})
.map(manifestPath => {
const extensionPath = path.dirname(path.join(root, manifestPath));
const extensionName = path.basename(extensionPath);
return { name: extensionName, path: extensionPath };
});
return es.merge(...localExtensionDescriptions.map(extension => {
return fromLocal(extension.path, true)
.pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`));
}));
}
export function packageMarketplaceExtensionsStream(): NodeJS.ReadWriteStream {
const extensions = builtInExtensions.map(extension => {
return fromMarketplace(extension.name, extension.version, extension.metadata)
@ -261,3 +291,55 @@ export function packageMarketplaceExtensionsStream(): NodeJS.ReadWriteStream {
return es.merge(extensions)
.pipe(util2.setExecutableBit(['**/*.sh']));
}
export function packageMarketplaceWebExtensionsStream(builtInExtensions: IBuiltInExtension[]): NodeJS.ReadWriteStream {
const extensions = builtInExtensions
.map(extension => {
const input = fromMarketplace(extension.name, extension.version, extension.metadata)
.pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`));
return updateExtensionPackageJSON(input, (data: any) => {
if (data.main) {
data.browser = data.main;
}
data.extensionKind = ['web'];
return data;
});
});
return es.merge(extensions);
}
export interface IScannedBuiltinExtension {
extensionPath: string,
packageJSON: any,
packageNLSPath?: string,
readmePath?: string,
changelogPath?: string,
}
export function scanBuiltinExtensions(extensionsRoot: string, forWeb: boolean): IScannedBuiltinExtension[] {
const scannedExtensions: IScannedBuiltinExtension[] = [];
const extensionsFolders = fs.readdirSync(extensionsRoot);
for (const extensionFolder of extensionsFolders) {
const packageJSONPath = path.join(extensionsRoot, extensionFolder, 'package.json');
if (!fs.existsSync(packageJSONPath)) {
continue;
}
const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath).toString('utf8'));
const extensionKind: string[] = packageJSON['extensionKind'] || [];
if (forWeb && extensionKind.indexOf('web') === -1) {
continue;
}
const children = fs.readdirSync(path.join(extensionsRoot, extensionFolder));
const packageNLS = children.filter(child => child === 'package.nls.json')[0];
const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
scannedExtensions.push({
extensionPath: extensionFolder,
packageJSON,
packageNLSPath: packageNLS ? path.join(extensionFolder, packageNLS) : undefined,
readmePath: readme ? path.join(extensionFolder, readme) : undefined,
changelogPath: changelog ? path.join(extensionFolder, changelog) : undefined,
});
}
return scannedExtensions;
}

View file

@ -16,7 +16,7 @@ const https = require("https");
const gulp = require("gulp");
const fancyLog = require("fancy-log");
const ansiColors = require("ansi-colors");
const iconv = require("iconv-lite");
const iconv = require("iconv-lite-umd");
const NUMBER_OF_CONCURRENT_DOWNLOADS = 4;
function log(message, ...rest) {
fancyLog(ansiColors.green('[i18n]'), message, ...rest);
@ -101,161 +101,158 @@ class TextModel {
return this._lines;
}
}
let XLF = /** @class */ (() => {
class XLF {
constructor(project) {
this.project = project;
this.buffer = [];
this.files = Object.create(null);
this.numberOfMessages = 0;
class XLF {
constructor(project) {
this.project = project;
this.buffer = [];
this.files = Object.create(null);
this.numberOfMessages = 0;
}
toString() {
this.appendHeader();
for (let file in this.files) {
this.appendNewLine(`<file original="${file}" source-language="en" datatype="plaintext"><body>`, 2);
for (let item of this.files[file]) {
this.addStringItem(file, item);
}
this.appendNewLine('</body></file>', 2);
}
toString() {
this.appendHeader();
for (let file in this.files) {
this.appendNewLine(`<file original="${file}" source-language="en" datatype="plaintext"><body>`, 2);
for (let item of this.files[file]) {
this.addStringItem(file, item);
this.appendFooter();
return this.buffer.join('\r\n');
}
addFile(original, keys, messages) {
if (keys.length === 0) {
console.log('No keys in ' + original);
return;
}
if (keys.length !== messages.length) {
throw new Error(`Unmatching keys(${keys.length}) and messages(${messages.length}).`);
}
this.numberOfMessages += keys.length;
this.files[original] = [];
let existingKeys = new Set();
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
let realKey;
let comment;
if (Is.string(key)) {
realKey = key;
comment = undefined;
}
else if (LocalizeInfo.is(key)) {
realKey = key.key;
if (key.comment && key.comment.length > 0) {
comment = key.comment.map(comment => encodeEntities(comment)).join('\r\n');
}
this.appendNewLine('</body></file>', 2);
}
this.appendFooter();
return this.buffer.join('\r\n');
}
addFile(original, keys, messages) {
if (keys.length === 0) {
console.log('No keys in ' + original);
return;
if (!realKey || existingKeys.has(realKey)) {
continue;
}
if (keys.length !== messages.length) {
throw new Error(`Unmatching keys(${keys.length}) and messages(${messages.length}).`);
}
this.numberOfMessages += keys.length;
this.files[original] = [];
let existingKeys = new Set();
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
let realKey;
let comment;
if (Is.string(key)) {
realKey = key;
comment = undefined;
}
else if (LocalizeInfo.is(key)) {
realKey = key.key;
if (key.comment && key.comment.length > 0) {
comment = key.comment.map(comment => encodeEntities(comment)).join('\r\n');
}
}
if (!realKey || existingKeys.has(realKey)) {
continue;
}
existingKeys.add(realKey);
let message = encodeEntities(messages[i]);
this.files[original].push({ id: realKey, message: message, comment: comment });
}
}
addStringItem(file, item) {
if (!item.id || item.message === undefined || item.message === null) {
throw new Error(`No item ID or value specified: ${JSON.stringify(item)}. File: ${file}`);
}
if (item.message.length === 0) {
log(`Item with id ${item.id} in file ${file} has an empty message.`);
}
this.appendNewLine(`<trans-unit id="${item.id}">`, 4);
this.appendNewLine(`<source xml:lang="en">${item.message}</source>`, 6);
if (item.comment) {
this.appendNewLine(`<note>${item.comment}</note>`, 6);
}
this.appendNewLine('</trans-unit>', 4);
}
appendHeader() {
this.appendNewLine('<?xml version="1.0" encoding="utf-8"?>', 0);
this.appendNewLine('<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">', 0);
}
appendFooter() {
this.appendNewLine('</xliff>', 0);
}
appendNewLine(content, indent) {
let line = new Line(indent);
line.append(content);
this.buffer.push(line.toString());
existingKeys.add(realKey);
let message = encodeEntities(messages[i]);
this.files[original].push({ id: realKey, message: message, comment: comment });
}
}
XLF.parsePseudo = function (xlfString) {
return new Promise((resolve) => {
let parser = new xml2js.Parser();
let files = [];
parser.parseString(xlfString, function (_err, result) {
const fileNodes = result['xliff']['file'];
fileNodes.forEach(file => {
const originalFilePath = file.$.original;
const messages = {};
const transUnits = file.body[0]['trans-unit'];
if (transUnits) {
transUnits.forEach((unit) => {
const key = unit.$.id;
const val = pseudify(unit.source[0]['_'].toString());
if (key && val) {
messages[key] = decodeEntities(val);
}
});
files.push({ messages: messages, originalFilePath: originalFilePath, language: 'ps' });
}
});
resolve(files);
});
});
};
XLF.parse = function (xlfString) {
return new Promise((resolve, reject) => {
let parser = new xml2js.Parser();
let files = [];
parser.parseString(xlfString, function (err, result) {
if (err) {
reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`));
}
const fileNodes = result['xliff']['file'];
if (!fileNodes) {
reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`));
}
fileNodes.forEach((file) => {
const originalFilePath = file.$.original;
if (!originalFilePath) {
reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`));
}
let language = file.$['target-language'];
if (!language) {
reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`));
}
const messages = {};
const transUnits = file.body[0]['trans-unit'];
if (transUnits) {
transUnits.forEach((unit) => {
const key = unit.$.id;
if (!unit.target) {
return; // No translation available
}
let val = unit.target[0];
if (typeof val !== 'string') {
val = val._;
}
if (key && val) {
messages[key] = decodeEntities(val);
}
else {
reject(new Error(`XLF parsing error: XLIFF file ${originalFilePath} does not contain full localization data. ID or target translation for one of the trans-unit nodes is not present.`));
}
});
files.push({ messages: messages, originalFilePath: originalFilePath, language: language.toLowerCase() });
}
});
resolve(files);
});
});
};
return XLF;
})();
addStringItem(file, item) {
if (!item.id || item.message === undefined || item.message === null) {
throw new Error(`No item ID or value specified: ${JSON.stringify(item)}. File: ${file}`);
}
if (item.message.length === 0) {
log(`Item with id ${item.id} in file ${file} has an empty message.`);
}
this.appendNewLine(`<trans-unit id="${item.id}">`, 4);
this.appendNewLine(`<source xml:lang="en">${item.message}</source>`, 6);
if (item.comment) {
this.appendNewLine(`<note>${item.comment}</note>`, 6);
}
this.appendNewLine('</trans-unit>', 4);
}
appendHeader() {
this.appendNewLine('<?xml version="1.0" encoding="utf-8"?>', 0);
this.appendNewLine('<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">', 0);
}
appendFooter() {
this.appendNewLine('</xliff>', 0);
}
appendNewLine(content, indent) {
let line = new Line(indent);
line.append(content);
this.buffer.push(line.toString());
}
}
exports.XLF = XLF;
XLF.parsePseudo = function (xlfString) {
return new Promise((resolve) => {
let parser = new xml2js.Parser();
let files = [];
parser.parseString(xlfString, function (_err, result) {
const fileNodes = result['xliff']['file'];
fileNodes.forEach(file => {
const originalFilePath = file.$.original;
const messages = {};
const transUnits = file.body[0]['trans-unit'];
if (transUnits) {
transUnits.forEach((unit) => {
const key = unit.$.id;
const val = pseudify(unit.source[0]['_'].toString());
if (key && val) {
messages[key] = decodeEntities(val);
}
});
files.push({ messages: messages, originalFilePath: originalFilePath, language: 'ps' });
}
});
resolve(files);
});
});
};
XLF.parse = function (xlfString) {
return new Promise((resolve, reject) => {
let parser = new xml2js.Parser();
let files = [];
parser.parseString(xlfString, function (err, result) {
if (err) {
reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`));
}
const fileNodes = result['xliff']['file'];
if (!fileNodes) {
reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`));
}
fileNodes.forEach((file) => {
const originalFilePath = file.$.original;
if (!originalFilePath) {
reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`));
}
let language = file.$['target-language'];
if (!language) {
reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`));
}
const messages = {};
const transUnits = file.body[0]['trans-unit'];
if (transUnits) {
transUnits.forEach((unit) => {
const key = unit.$.id;
if (!unit.target) {
return; // No translation available
}
let val = unit.target[0];
if (typeof val !== 'string') {
val = val._;
}
if (key && val) {
messages[key] = decodeEntities(val);
}
else {
reject(new Error(`XLF parsing error: XLIFF file ${originalFilePath} does not contain full localization data. ID or target translation for one of the trans-unit nodes is not present.`));
}
});
files.push({ messages: messages, originalFilePath: originalFilePath, language: language.toLowerCase() });
}
});
resolve(files);
});
});
};
class Limiter {
constructor(maxDegreeOfParalellism) {
this.maxDegreeOfParalellism = maxDegreeOfParalellism;
@ -1178,9 +1175,10 @@ function createIslFile(originalFilePath, messages, language, innoSetup) {
});
const basename = path.basename(originalFilePath);
const filePath = `${basename}.${language.id}.isl`;
const encoded = iconv.encode(Buffer.from(content.join('\r\n'), 'utf8').toString(), innoSetup.codePage);
return new File({
path: filePath,
contents: iconv.encode(Buffer.from(content.join('\r\n'), 'utf8').toString(), innoSetup.codePage)
contents: Buffer.from(encoded),
});
}
function encodeEntities(value) {

View file

@ -218,6 +218,10 @@
"name": "vs/workbench/contrib/userDataSync",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/views",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/services/actions",
"project": "vscode-workbench"

View file

@ -15,7 +15,7 @@ import * as https from 'https';
import * as gulp from 'gulp';
import * as fancyLog from 'fancy-log';
import * as ansiColors from 'ansi-colors';
import * as iconv from 'iconv-lite';
import * as iconv from 'iconv-lite-umd';
const NUMBER_OF_CONCURRENT_DOWNLOADS = 4;
@ -1339,10 +1339,11 @@ function createIslFile(originalFilePath: string, messages: Map<string>, language
const basename = path.basename(originalFilePath);
const filePath = `${basename}.${language.id}.isl`;
const encoded = iconv.encode(Buffer.from(content.join('\r\n'), 'utf8').toString(), innoSetup.codePage);
return new File({
path: filePath,
contents: iconv.encode(Buffer.from(content.join('\r\n'), 'utf8').toString(), innoSetup.codePage)
contents: Buffer.from(encoded),
});
}

View file

@ -138,7 +138,7 @@ function loadSourcemaps() {
version: '3',
names: [],
mappings: '',
sources: [f.relative.replace(/\//g, '/')],
sources: [f.relative],
sourcesContent: [contents]
};
cb(undefined, f);

View file

@ -186,7 +186,7 @@ export function loadSourcemaps(): NodeJS.ReadWriteStream {
version: '3',
names: [],
mappings: '',
sources: [f.relative.replace(/\//g, '/')],
sources: [f.relative],
sourcesContent: [contents]
};

View file

@ -38,15 +38,15 @@
"gulp-bom": "^1.0.0",
"gulp-sourcemaps": "^1.11.0",
"gulp-uglify": "^3.0.0",
"iconv-lite": "0.4.23",
"iconv-lite-umd": "0.6.5",
"mime": "^1.3.4",
"minimatch": "3.0.4",
"minimist": "^1.2.3",
"request": "^2.85.0",
"terser": "4.3.8",
"typescript": "^3.9.3",
"typescript": "^4.0.0-dev.20200622",
"vsce": "1.48.0",
"vscode-telemetry-extractor": "^1.5.4",
"vscode-telemetry-extractor": "^1.6.0",
"xml2js": "^0.4.17"
},
"scripts": {

View file

@ -1,7 +1,8 @@
#define RootLicenseFileName FileExists(RepoDir + '\LICENSE.rtf') ? 'LICENSE.rtf' : 'LICENSE.txt'
#define LocalizedLanguageFile(Language = "") \
DirExists(RepoDir + "\licenses") && Language != "" \
? ('; LicenseFile: "' + RepoDir + '\licenses\LICENSE-' + Language + '.rtf"') \
: '; LicenseFile: "' + RepoDir + '\LICENSE.rtf"'
: '; LicenseFile: "' + RepoDir + '\' + RootLicenseFileName + '"'
[Setup]
AppId={#AppId}
@ -1131,7 +1132,7 @@ begin
end;
end;
// http://stackoverflow.com/a/23838239/261019
// https://stackoverflow.com/a/23838239/261019
procedure Explode(var Dest: TArrayOfString; Text: String; Separator: String);
var
i, p: Integer;

View file

@ -394,6 +394,11 @@ acorn@4.X:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=
agent-base@5:
version "5.1.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
ajv@^4.9.1:
version "4.11.8"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
@ -845,7 +850,7 @@ debug@2.X, debug@^2.6.8:
dependencies:
ms "2.0.0"
debug@^4.1.1:
debug@4, debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
@ -1415,12 +1420,18 @@ http-signature@~1.2.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
iconv-lite@0.4.23:
version "0.4.23"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==
https-proxy-agent@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b"
integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==
dependencies:
safer-buffer ">= 2.1.2 < 3"
agent-base "5"
debug "4"
iconv-lite-umd@0.6.5:
version "0.6.5"
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.5.tgz#6a1f621a3b4d125f72feff813a9839e1ebd6c722"
integrity sha512-WDegH4al+e3n3jTOStRvm+jzDA3JMUQGgzdAsMxAgcgB0Oi72HjfdsoX08ieKsy3rKexXVjWZr41aOIUaCZnMg==
ignore@^5.1.1:
version "5.1.2"
@ -2053,6 +2064,11 @@ process-nextick-args@~2.0.0:
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
@ -2228,11 +2244,6 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sax@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.2.tgz#735ffaa39a1cff8ffb9598f0223abdb03a9fb2ea"
@ -2519,10 +2530,10 @@ typescript@^3.0.1:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977"
integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==
typescript@^3.9.3:
version "3.9.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a"
integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==
typescript@^4.0.0-dev.20200622:
version "4.0.0-dev.20200622"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.0-dev.20200622.tgz#33e0ffaf880b1f16bde5bc4eeb1863e52c4d7f75"
integrity sha512-KWXppG2OKfq5cDAEkc0wA7uemXnF/Af4v0j08plUCKk20rt9wYU2rU9EB53/XlVeZgV2hwpbH9hIFyeB4dWvdg==
typical@^4.0.0:
version "4.0.0"
@ -2654,19 +2665,22 @@ vsce@1.48.0:
yauzl "^2.3.1"
yazl "^2.2.2"
vscode-ripgrep@^1.5.6:
version "1.5.7"
resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.7.tgz#acb6b548af488a4bca5d0f1bb5faf761343289ce"
integrity sha512-/Vsz/+k8kTvui0q3O74pif9FK0nKopgFTiGNVvxicZANxtSA8J8gUE9GQ/4dpi7D/2yI/YVORszwVskFbz46hQ==
vscode-ripgrep@^1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.6.2.tgz#fb912c7465699f10ce0218a6676cc632c77369b4"
integrity sha512-jkZEWnQFcE+QuQFfxQXWcWtDafTmgkp3DjMKawDkajZwgnDlGKpFp15ybKrZNVTi1SLEF/12BzxYSZVVZ2XrkA==
dependencies:
https-proxy-agent "^4.0.0"
proxy-from-env "^1.1.0"
vscode-telemetry-extractor@^1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/vscode-telemetry-extractor/-/vscode-telemetry-extractor-1.5.4.tgz#bcb0d17667fa1b77715e3a3bf372ade18f846782"
integrity sha512-MN9LNPo0Rc6cy3sIWTAG97PTWkEKdRnP0VeYoS8vjKSNtG9CAsrUxHgFfYoHm2vNK/ijd0a4NzETyVGO2kT6hw==
vscode-telemetry-extractor@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/vscode-telemetry-extractor/-/vscode-telemetry-extractor-1.6.0.tgz#e9d9c1d24863cce8d3d715f0287de3b31eb90c56"
integrity sha512-zSxvkbyAMa1lTRGIHfGg7gW2e9Sey+2zGYD19uNWCsVEfoXAr2NB6uzb0sNHtbZ2SSqxSePmFXzBAavsudT5fw==
dependencies:
command-line-args "^5.1.1"
ts-morph "^3.1.3"
vscode-ripgrep "^1.5.6"
vscode-ripgrep "^1.6.2"
vso-node-api@6.1.2-preview:
version "6.1.2-preview"

View file

@ -6,7 +6,7 @@
"git": {
"name": "chromium",
"repositoryUrl": "https://chromium.googlesource.com/chromium/src",
"commitHash": "e4745133a1d3745f066e068b8033c6a269b59caf"
"commitHash": "052d3b44972e6d94ef40054d46c150b7cdd7a5d8"
}
},
"licenseDetail": [
@ -40,7 +40,7 @@
"SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
],
"isOnlyProductionDependency": true,
"version": "78.0.3904.130"
"version": "80.0.3987.165"
},
{
"component": {
@ -48,11 +48,11 @@
"git": {
"name": "nodejs",
"repositoryUrl": "https://github.com/nodejs/node",
"commitHash": "787378879acfb212ed4ff824bf9f767a24a5cb43a"
"commitHash": "42cce5a9d0fd905bf4ad7a2528c36572dfb8b5ad"
}
},
"isOnlyProductionDependency": true,
"version": "12.8.1"
"version": "12.13.0"
},
{
"component": {
@ -60,12 +60,12 @@
"git": {
"name": "electron",
"repositoryUrl": "https://github.com/electron/electron",
"commitHash": "bc8fc0d406d32e4c02f3ec9f161deaacbe4f5989"
"commitHash": "87fd06bc96bce8f46ca05b8315657fd230bcac85"
}
},
"isOnlyProductionDependency": true,
"license": "MIT",
"version": "7.3.1"
"version": "8.3.3"
},
{
"component": {

View file

@ -3,4 +3,5 @@ src/**
tsconfig.json
out/**
extension.webpack.config.js
extension-browser.webpack.config.js
yarn.lock

View file

@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const withBrowserDefaults = require('../shared.webpack.config').browser;
module.exports = withBrowserDefaults({
context: __dirname,
entry: {
extension: './src/configurationEditingMain.ts'
},
output: {
filename: 'configurationEditingMain.js'
}
});

View file

@ -12,7 +12,10 @@ const withDefaults = require('../shared.webpack.config');
module.exports = withDefaults({
context: __dirname,
entry: {
extension: './src/extension.ts',
extension: './src/configurationEditingMain.ts',
},
output: {
filename: 'configurationEditingMain.js'
},
resolve: {
mainFields: ['module', 'main']

View file

@ -12,7 +12,8 @@
"onLanguage:json",
"onLanguage:jsonc"
],
"main": "./out/extension",
"main": "./out/configurationEditingMain",
"browser": "./dist/browser/configurationEditingMain",
"scripts": {
"compile": "gulp compile-extension:configuration-editing",
"watch": "gulp watch-extension:configuration-editing"
@ -116,6 +117,10 @@
"fileMatch": "/.devcontainer.json",
"url": "./schemas/devContainer.schema.json"
},
{
"fileMatch": "%APP_SETTINGS_HOME%/globalStorage/ms-vscode-remote.remote-containers/nameConfigs/*.json",
"url": "./schemas/attachContainer.schema.json"
},
{
"fileMatch": "%APP_SETTINGS_HOME%/globalStorage/ms-vscode-remote.remote-containers/imageConfigs/*.json",
"url": "./schemas/attachContainer.schema.json"

View file

@ -42,7 +42,7 @@
"description": "An array of extensions that should be installed into the container.",
"items": {
"type": "string",
"pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)$",
"pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)(@[0-9]+\\.[0-9]+\\.[0-9]+)?$",
"errorMessage": "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'."
}
},

View file

@ -17,7 +17,7 @@
"description": "An array of extensions that should be installed into the container.",
"items": {
"type": "string",
"pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)$",
"pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)(@[0-9]+\\.[0-9]+\\.[0-9]+)?$",
"errorMessage": "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'."
}
},

View file

@ -19,7 +19,7 @@
"git": {
"name": "textmate/c.tmbundle",
"repositoryUrl": "https://github.com/textmate/c.tmbundle",
"commitHash": "9aa365882274ca52f01722f3dbb169b9539a20ee"
"commitHash": "60daf83b9d45329524f7847a75e9298b3aae5805"
}
},
"licenseDetail": [
@ -42,4 +42,4 @@
}
],
"version": 1
}
}

File diff suppressed because one or more lines are too long

View file

@ -1,46 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface Options {
locale?: string;
cacheLanguageResolution?: boolean;
}
export interface LocalizeInfo {
key: string;
comment: string[];
}
export interface LocalizeFunc {
(info: LocalizeInfo, message: string, ...args: any[]): string;
(key: string, message: string, ...args: any[]): string;
}
export interface LoadFunc {
(file?: string): LocalizeFunc;
}
function format(message: string, args: any[]): string {
let result: string;
if (args.length === 0) {
result = message;
} else {
result = message.replace(/\{(\d+)\}/g, (match, rest) => {
let index = rest[0];
return typeof args[index] !== 'undefined' ? args[index] : match;
});
}
return result;
}
function localize(_key: string | LocalizeInfo, message: string, ...args: any[]): string {
return format(message, args);
}
export function loadMessageBundle(_file?: string): LocalizeFunc {
return localize;
}
export function config(_opt?: Options | string): LoadFunc {
return loadMessageBundle;
}

View file

@ -76,17 +76,13 @@ function getCustomDataPathsInAllWorkspaces(): string[] {
function getCustomDataPathsFromAllExtensions(): string[] {
const dataPaths: string[] = [];
for (const extension of extensions.all) {
const contributes = extension.packageJSON && extension.packageJSON.contributes;
if (contributes && contributes.css && contributes.css.customData && Array.isArray(contributes.css.customData)) {
const relativePaths: string[] = contributes.css.customData;
relativePaths.forEach(rp => {
const customData = extension.packageJSON?.contributes?.css?.customData;
if (Array.isArray(customData)) {
for (const rp of customData) {
dataPaths.push(joinPath(extension.extensionUri, rp).toString());
});
}
}
}
return dataPaths;
}

View file

@ -12,12 +12,9 @@ import { TextDecoder } from 'util';
// this method is called when vs code is activated
export function activate(context: ExtensionContext) {
const clientMain = extensions.getExtension('vscode.css-language-features')?.packageJSON?.main;
const serverMain = clientMain?.replace('client', 'server').replace('cssClientMain', 'cssServerMain');
if (!serverMain) {
throw new Error('Unable to compute CSS server module path. Client: ' + clientMain);
}
const clientMain = extensions.getExtension('vscode.css-language-features')?.packageJSON?.main || '';
const serverMain = `./server/${clientMain.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/cssServerMain`;
const serverModule = context.asAbsolutePath(serverMain);
// The debug options for the server

View file

@ -7,17 +7,10 @@
'use strict';
const withDefaults = require('../shared.webpack.config');
const withBrowserDefaults = require('../shared.webpack.config').browser;
const path = require('path');
const webpack = require('webpack');
const vscodeNlsReplacement = new webpack.NormalModuleReplacementPlugin(
/vscode\-nls[\\/]lib[\\/]main\.js/,
path.join(__dirname, 'client/out/browser/vscodeNlsShim.js')
);
const clientConfig = withDefaults({
target: 'webworker',
module.exports = withBrowserDefaults({
context: path.join(__dirname, 'client'),
entry: {
extension: './src/browser/cssClientMain.ts'
@ -27,7 +20,3 @@ const clientConfig = withDefaults({
path: path.join(__dirname, 'client', 'dist', 'browser')
}
});
clientConfig.plugins[1] = vscodeNlsReplacement; // replace nls bundler
clientConfig.module.rules[0].use.shift(); // remove nls loader
module.exports = clientConfig;

View file

@ -807,7 +807,7 @@
]
},
"dependencies": {
"vscode-languageclient": "7.0.0-next.5",
"vscode-languageclient": "7.0.0-next.5.1",
"vscode-nls": "^4.1.2"
},
"devDependencies": {

View file

@ -7,17 +7,10 @@
'use strict';
const withDefaults = require('../../shared.webpack.config');
const withBrowserDefaults = require('../../shared.webpack.config').browser;
const path = require('path');
const webpack = require('webpack');
const vscodeNlsReplacement = new webpack.NormalModuleReplacementPlugin(
/vscode\-nls[\\/]lib[\\/]main\.js/,
path.join(__dirname, '../client/out/browser/vscodeNlsShim.js')
);
const serverConfig = withDefaults({
target: 'webworker',
module.exports = withBrowserDefaults({
context: __dirname,
entry: {
extension: './src/browser/cssServerMain.ts',
@ -28,7 +21,3 @@ const serverConfig = withDefaults({
libraryTarget: 'var'
}
});
serverConfig.plugins[1] = vscodeNlsReplacement; // replace nls bundler
serverConfig.module.rules[0].use.shift(); // remove nls loader
module.exports = serverConfig;

View file

@ -10,7 +10,7 @@
"main": "./out/node/cssServerMain",
"browser": "./dist/browser/cssServerMain",
"dependencies": {
"vscode-css-languageservice": "4.3.0-next.2",
"vscode-css-languageservice": "^4.3.0-next.3",
"vscode-languageserver": "7.0.0-next.3",
"vscode-uri": "^2.1.2"
},

View file

@ -5,7 +5,7 @@
import {
Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType
} from 'vscode-languageserver/lib/common/api';
} from 'vscode-languageserver';
import { URI } from 'vscode-uri';
import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet, TextDocument, Position } from 'vscode-css-languageservice';
import { getLanguageModelCache } from './languageModelCache';

View file

@ -29,7 +29,7 @@ function parseCSSData(source: string): ICSSDataProvider {
}
return newCSSDataProvider({
version: 1,
version: rawData.version || 1,
properties: rawData.properties || [],
atDirectives: rawData.atDirectives || [],
pseudoClasses: rawData.pseudoClasses || [],

View file

@ -701,10 +701,10 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
vscode-css-languageservice@4.3.0-next.2:
version "4.3.0-next.2"
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.3.0-next.2.tgz#a7a1289d8d68ddcdee55d4f18b12a455acaf5962"
integrity sha512-4h/s/N7wt6If/5EUNMtfAbwWwImH6EvveqZMf9SmQdMMMqekZkRLA68E98hGzuzI13rHEiLckwlAC+RNLq6FXg==
vscode-css-languageservice@^4.3.0-next.3:
version "4.3.0-next.3"
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.3.0-next.3.tgz#0c19ebcc7df5c41104c86f5f2bb9b4537924a33b"
integrity sha512-ILm9qONhfeCV+XZs93w9c1U8GFb67KXpbUS1i8aw2kYcuVME2XunUy5XsVlxDUzUyuWBAw+OWvcP+aTRwPaeyA==
dependencies:
vscode-languageserver-textdocument "^1.0.1"
vscode-languageserver-types "3.16.0-next.2"

View file

@ -640,10 +640,10 @@ vscode-jsonrpc@6.0.0-next.2:
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0-next.2.tgz#3d73f86d812304cb91b9fb1efee40ec60b09ed7f"
integrity sha512-dKQXRYNUY6BHALQJBJlyZyv9oWlYpbJ2vVoQNNVNPLAYQ3hzNp4zy+iSo7zGx1BPXByArJQDWTKLQh8dz3dnNw==
vscode-languageclient@7.0.0-next.5:
version "7.0.0-next.5"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-7.0.0-next.5.tgz#7ae84c598dff360bd2bc64322b74e10e5d0b9cd6"
integrity sha512-ec+fJg+JiNBIdbeKbzssSuORUaVdtLValtiYdNEUCUjpYE+Y6xXPtXwiZOlS/0OB9pC/RLCMxsj16UwWncQhYQ==
vscode-languageclient@7.0.0-next.5.1:
version "7.0.0-next.5.1"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-7.0.0-next.5.1.tgz#ed93f14e4c2cdccedf15002c7bf8ef9cb638f36c"
integrity sha512-OONvbk3IFpubwF8/Y5uPQaq5J5CEskpeET3SfK4iGlv5OUK+44JawH/SEW5wXuEPpfdMLEMZLuGLU5v5d7N7PQ==
dependencies:
semver "^6.3.0"
vscode-languageserver-protocol "3.16.0-next.4"

View file

@ -49,6 +49,9 @@
},
"emmet.includeLanguages": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"default": {},
"markdownDescription": "%emmetIncludeLanguages%"
},

View file

@ -16,6 +16,11 @@ import { languages, workspace, Disposable, TextDocument, Uri, Diagnostic, Range,
const product = JSON.parse(fs.readFileSync(path.join(env.appRoot, 'product.json'), { encoding: 'utf-8' }));
const allowedBadgeProviders: string[] = (product.extensionAllowedBadgeProviders || []).map((s: string) => s.toLowerCase());
const allowedBadgeProvidersRegex: RegExp[] = (product.extensionAllowedBadgeProvidersRegex || []).map((r: string) => new RegExp(r));
function isTrustedSVGSource(uri: Uri): boolean {
return allowedBadgeProviders.includes(uri.authority.toLowerCase()) || allowedBadgeProvidersRegex.some(r => r.test(uri.toString()));
}
const httpsRequired = localize('httpsRequired', "Images must use the HTTPS protocol.");
const svgsNotValid = localize('svgsNotValid', "SVGs are not a valid image source.");
@ -321,7 +326,7 @@ export class ExtensionLinter {
diagnostics.push(new Diagnostic(range, message, DiagnosticSeverity.Warning));
}
if (endsWith(uri.path.toLowerCase(), '.svg') && allowedBadgeProviders.indexOf(uri.authority.toLowerCase()) === -1) {
if (endsWith(uri.path.toLowerCase(), '.svg') && !isTrustedSVGSource(uri)) {
const range = new Range(document.positionAt(begin), document.positionAt(end));
diagnostics.push(new Diagnostic(range, svgsNotValid, DiagnosticSeverity.Warning));
}

View file

@ -14,7 +14,8 @@
"Other"
],
"activationEvents": [
"*"
"*",
"onFileSystem:git"
],
"main": "./out/main",
"icon": "resources/icons/git.png",
@ -431,8 +432,8 @@
},
{
"command": "git.unstageSelectedRanges",
"key": "ctrl+k ctrl+u",
"mac": "cmd+k cmd+u",
"key": "ctrl+k ctrl+n",
"mac": "cmd+k cmd+n",
"when": "isInDiffEditor"
},
{
@ -1870,14 +1871,14 @@
{
"view": "explorer",
"contents": "%view.workbench.cloneRepository%",
"when": "config.git.enabled"
"when": "config.git.enabled && git.state == initialized"
}
]
},
"dependencies": {
"byline": "^5.0.0",
"file-type": "^7.2.0",
"iconv-lite": "^0.4.24",
"iconv-lite-umd": "0.6.5",
"jschardet": "2.1.1",
"vscode-extension-telemetry": "0.1.1",
"vscode-nls": "^4.0.0",

View file

@ -134,6 +134,8 @@ export interface CommitOptions {
export interface BranchQuery {
readonly remote?: boolean;
readonly pattern?: string;
readonly count?: number;
readonly contains?: string;
}

View file

@ -8,15 +8,15 @@ import * as path from 'path';
import { Repository, GitResourceGroup } from './repository';
import { Model } from './model';
import { debounce } from './decorators';
import { filterEvent, dispose, anyEvent, fireEvent } from './util';
import { filterEvent, dispose, anyEvent, fireEvent, PromiseSource } from './util';
import { GitErrorCodes, Status } from './api/git';
type Callback = { resolve: (status: boolean) => void, reject: (err: any) => void };
class GitIgnoreDecorationProvider implements DecorationProvider {
private static Decoration: Decoration = { priority: 3, color: new ThemeColor('gitDecoration.ignoredResourceForeground') };
readonly onDidChangeDecorations: Event<Uri[]>;
private queue = new Map<string, { repository: Repository; queue: Map<string, Callback>; }>();
private queue = new Map<string, { repository: Repository; queue: Map<string, PromiseSource<Decoration | undefined>>; }>();
private disposables: Disposable[] = [];
constructor(private model: Model) {
@ -29,32 +29,29 @@ class GitIgnoreDecorationProvider implements DecorationProvider {
this.disposables.push(window.registerDecorationProvider(this));
}
provideDecoration(uri: Uri): Promise<Decoration | undefined> {
async provideDecoration(uri: Uri): Promise<Decoration | undefined> {
const repository = this.model.getRepository(uri);
if (!repository) {
return Promise.resolve(undefined);
return;
}
let queueItem = this.queue.get(repository.root);
if (!queueItem) {
queueItem = { repository, queue: new Map<string, Callback>() };
queueItem = { repository, queue: new Map<string, PromiseSource<Decoration | undefined>>() };
this.queue.set(repository.root, queueItem);
}
return new Promise<boolean>((resolve, reject) => {
queueItem!.queue.set(uri.fsPath, { resolve, reject });
let promiseSource = queueItem.queue.get(uri.fsPath);
if (!promiseSource) {
promiseSource = new PromiseSource();
queueItem!.queue.set(uri.fsPath, promiseSource);
this.checkIgnoreSoon();
}).then(ignored => {
if (ignored) {
return <Decoration>{
priority: 3,
color: new ThemeColor('gitDecoration.ignoredResourceForeground')
};
}
return undefined;
});
}
return await promiseSource.promise;
}
@debounce(500)
@ -66,16 +63,16 @@ class GitIgnoreDecorationProvider implements DecorationProvider {
const paths = [...item.queue.keys()];
item.repository.checkIgnore(paths).then(ignoreSet => {
for (const [key, value] of item.queue.entries()) {
value.resolve(ignoreSet.has(key));
for (const [path, promiseSource] of item.queue.entries()) {
promiseSource.resolve(ignoreSet.has(path) ? GitIgnoreDecorationProvider.Decoration : undefined);
}
}, err => {
if (err.gitErrorCode !== GitErrorCodes.IsInSubmodule) {
console.error(err);
}
for (const [, value] of item.queue.entries()) {
value.reject(err);
for (const [, promiseSource] of item.queue.entries()) {
promiseSource.reject(err);
}
});
}

View file

@ -133,6 +133,8 @@ export class GitFileSystemProvider implements FileSystemProvider {
}
async stat(uri: Uri): Promise<FileStat> {
await this.model.isInitialized;
const { submoduleOf, path, ref } = fromGitUri(uri);
const repository = submoduleOf ? this.model.getRepository(submoduleOf) : this.model.getRepository(uri);
if (!repository) {
@ -158,6 +160,8 @@ export class GitFileSystemProvider implements FileSystemProvider {
}
async readFile(uri: Uri): Promise<Uint8Array> {
await this.model.isInitialized;
const { path, ref, submoduleOf } = fromGitUri(uri);
if (submoduleOf) {

View file

@ -9,7 +9,7 @@ import * as os from 'os';
import * as cp from 'child_process';
import * as which from 'which';
import { EventEmitter } from 'events';
import iconv = require('iconv-lite');
import * as iconv from 'iconv-lite-umd';
import * as filetype from 'file-type';
import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter } from './util';
import { CancellationToken, Progress, Uri } from 'vscode';
@ -422,7 +422,7 @@ export class Git {
const result = await this.exec(repositoryPath, ['rev-parse', '--show-toplevel']);
// Keep trailing spaces which are part of the directory name
const repoPath = path.normalize(result.stdout.trimLeft().replace(/(\r\n|\r|\n)+$/, ''));
const repoPath = path.normalize(result.stdout.trimLeft().replace(/[\r\n]+$/, ''));
if (isWindows) {
// On Git 2.25+ if you call `rev-parse --show-toplevel` on a mapped drive, instead of getting the mapped drive path back, you get the UNC path for the mapped drive.
@ -905,7 +905,7 @@ export class Repository {
}
async buffer(object: string): Promise<Buffer> {
const child = this.stream(['show', object]);
const child = this.stream(['show', '--textconv', object]);
if (!child.stdout) {
return Promise.reject<Buffer>('Can\'t open file from git');
@ -978,7 +978,7 @@ export class Repository {
}
async detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }> {
const child = await this.stream(['show', object]);
const child = await this.stream(['show', '--textconv', object]);
const buffer = await readBytes(child.stdout!, 4100);
try {
@ -1791,13 +1791,23 @@ export class Repository {
.map(([ref]) => ({ name: ref, type: RefType.Head } as Branch));
}
async getRefs(opts?: { sort?: 'alphabetically' | 'committerdate', contains?: string }): Promise<Ref[]> {
const args = ['for-each-ref', '--format', '%(refname) %(objectname)'];
async getRefs(opts?: { sort?: 'alphabetically' | 'committerdate', contains?: string, pattern?: string, count?: number }): Promise<Ref[]> {
const args = ['for-each-ref'];
if (opts?.count) {
args.push(`--count=${opts.count}`);
}
if (opts && opts.sort && opts.sort !== 'alphabetically') {
args.push('--sort', `-${opts.sort}`);
}
args.push('--format', '%(refname) %(objectname)');
if (opts?.pattern) {
args.push(opts.pattern);
}
if (opts?.contains) {
args.push('--contains', opts.contains);
}
@ -1920,7 +1930,7 @@ export class Repository {
}
async getBranches(query: BranchQuery): Promise<Ref[]> {
const refs = await this.getRefs({ contains: query.contains });
const refs = await this.getRefs({ contains: query.contains, pattern: query.pattern ? `refs/${query.pattern}` : undefined, count: query.count });
return refs.filter(value => (value.type !== RefType.Tag) && (query.remote || !value.remote));
}

View file

@ -6,7 +6,7 @@
import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, OutputChannel, commands } from 'vscode';
import { Repository, RepositoryState } from './repository';
import { memoize, sequentialize, debounce } from './decorators';
import { dispose, anyEvent, filterEvent, isDescendant, firstIndex, pathEquals, toDisposable } from './util';
import { dispose, anyEvent, filterEvent, isDescendant, firstIndex, pathEquals, toDisposable, eventToPromise } from './util';
import { Git } from './git';
import * as path from 'path';
import * as fs from 'fs';
@ -77,6 +77,15 @@ export class Model implements IRemoteSourceProviderRegistry {
commands.executeCommand('setContext', 'git.state', state);
}
@memoize
get isInitialized(): Promise<void> {
if (this._state === 'initialized') {
return Promise.resolve();
}
return eventToPromise(filterEvent(this.onDidChangeState, s => s === 'initialized')) as Promise<any>;
}
private remoteSourceProviders = new Set<RemoteSourceProvider>();
private _onDidAddRemoteSourceProvider = new EventEmitter<RemoteSourceProvider>();

View file

@ -537,7 +537,7 @@ class DotGitWatcher implements IFileWatcher {
upstreamWatcher.event(this.emitter.fire, this.emitter, this.transientDisposables);
} catch (err) {
if (Log.logLevel <= LogLevel.Error) {
this.outputChannel.appendLine(`Failed to watch ref '${upstreamPath}', is most likely packed.\n${err.stack || err}`);
this.outputChannel.appendLine(`Warning: Failed to watch ref '${upstreamPath}', is most likely packed.`);
}
}
}
@ -729,10 +729,10 @@ export class Repository implements Disposable {
this.updateInputBoxPlaceholder();
this.disposables.push(this.onDidRunGitStatus(() => this.updateInputBoxPlaceholder()));
this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "MERGE CHANGES"));
this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "STAGED CHANGES"));
this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "CHANGES"));
this._untrackedGroup = this._sourceControl.createResourceGroup('untracked', localize('untracked changes', "UNTRACKED CHANGES"));
this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "Merge Changes"));
this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "Staged Changes"));
this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "Changes"));
this._untrackedGroup = this._sourceControl.createResourceGroup('untracked', localize('untracked changes', "Untracked Changes"));
const updateIndexGroupVisibility = () => {
const config = workspace.getConfiguration('git', root);

View file

@ -19,7 +19,7 @@ export function applyLineChanges(original: TextDocument, modified: TextDocument,
// if this is a deletion at the very end of the document,then we need to account
// for a newline at the end of the last line which may have been deleted
// https://github.com/Microsoft/vscode/issues/59670
if (isDeletion && diff.originalStartLineNumber === original.lineCount) {
if (isDeletion && diff.originalEndLineNumber === original.lineCount) {
endLine -= 1;
endCharacter = original.lineAt(endLine).range.end.character;
}

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event, Disposable } from 'vscode';
import { Event, Disposable, EventEmitter } from 'vscode';
import { dirname, sep } from 'path';
import { Readable } from 'stream';
import { promises as fs, createReadStream } from 'fs';
@ -400,3 +400,39 @@ export class Limiter<T> {
}
}
}
type Completion<T> = { success: true, value: T } | { success: false, err: any };
export class PromiseSource<T> {
private _onDidComplete = new EventEmitter<Completion<T>>();
private _promise: Promise<T> | undefined;
get promise(): Promise<T> {
if (this._promise) {
return this._promise;
}
return eventToPromise(this._onDidComplete.event).then(completion => {
if (completion.success) {
return completion.value;
} else {
throw completion.err;
}
});
}
resolve(value: T): void {
if (!this._promise) {
this._promise = Promise.resolve(value);
this._onDidComplete.fire({ success: true, value });
}
}
reject(err: any): void {
if (!this._promise) {
this._promise = Promise.reject(err);
this._onDidComplete.fire({ success: false, err });
}
}
}

View file

@ -425,12 +425,10 @@ https-proxy-agent@^2.2.1:
agent-base "^4.3.0"
debug "^3.1.0"
iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite-umd@0.6.5:
version "0.6.5"
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.5.tgz#6a1f621a3b4d125f72feff813a9839e1ebd6c722"
integrity sha512-WDegH4al+e3n3jTOStRvm+jzDA3JMUQGgzdAsMxAgcgB0Oi72HjfdsoX08ieKsy3rKexXVjWZr41aOIUaCZnMg==
inflight@^1.0.4:
version "1.0.6"
@ -748,7 +746,7 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.2:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==

View file

@ -8,11 +8,10 @@
'use strict';
const path = require('path');
const withDefaults = require('../shared.webpack.config');
const withBrowserDefaults = require('../shared.webpack.config').browser;
module.exports = withDefaults({
module.exports = withBrowserDefaults({
context: __dirname,
target: 'webworker',
node: false,
entry: {
extension: './src/extension.ts',
@ -20,30 +19,10 @@ module.exports = withDefaults({
externals: {
'keytar': 'commonjs keytar',
},
// TODO@eamodio Deal with nls properly for the browser
// Specify module here, so we can stop the vscode-nls-dev loader from overwriting nls calls
module: {
rules: [{
test: /\.ts$/,
exclude: /node_modules/,
use: [{
// configure TypeScript loader:
// * enable sources maps for end-to-end source maps
loader: 'ts-loader',
options: {
compilerOptions: {
'sourceMap': true,
}
}
}]
}]
},
resolve: {
alias: {
'node-fetch': path.resolve(__dirname, 'node_modules/node-fetch/browser.js'),
'vscode-extension-telemetry': path.resolve(__dirname, 'polyfills/vscode-extension-telemetry.js'),
'vscode-nls': path.resolve(__dirname, 'polyfills/vscode-nls.js'),
'uuid': path.resolve(__dirname, 'node_modules/uuid/dist/esm-browser/index.js')
},
}
}
});

View file

@ -32,7 +32,7 @@
},
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
"main": "./out/extension.js",
"browser": "./dist/extension.js",
"browser": "./dist/browser/extension.js",
"scripts": {
"compile": "gulp compile-extension:github-authentication",
"compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none",

View file

@ -22,7 +22,7 @@ interface SessionData {
}
export class GitHubAuthenticationProvider {
private _sessions: vscode.AuthenticationSession2[] = [];
private _sessions: vscode.AuthenticationSession[] = [];
private _githubServer = new GitHubServer();
public async initialize(): Promise<void> {
@ -37,7 +37,7 @@ export class GitHubAuthenticationProvider {
private pollForChange() {
setTimeout(async () => {
let storedSessions: vscode.AuthenticationSession2[];
let storedSessions: vscode.AuthenticationSession[];
try {
storedSessions = await this.readSessions();
} catch (e) {
@ -80,12 +80,12 @@ export class GitHubAuthenticationProvider {
}, 1000 * 30);
}
private async readSessions(): Promise<vscode.AuthenticationSession2[]> {
private async readSessions(): Promise<vscode.AuthenticationSession[]> {
const storedSessions = await keychain.getToken();
if (storedSessions) {
try {
const sessionData: SessionData[] = JSON.parse(storedSessions);
const sessionPromises = sessionData.map(async (session: SessionData): Promise<vscode.AuthenticationSession2> => {
const sessionPromises = sessionData.map(async (session: SessionData): Promise<vscode.AuthenticationSession> => {
const needsUserInfo = !session.account;
let userInfo: { id: string, accountName: string };
if (needsUserInfo) {
@ -121,11 +121,11 @@ export class GitHubAuthenticationProvider {
await keychain.setToken(JSON.stringify(this._sessions));
}
get sessions(): vscode.AuthenticationSession2[] {
get sessions(): vscode.AuthenticationSession[] {
return this._sessions;
}
public async login(scopes: string): Promise<vscode.AuthenticationSession2> {
public async login(scopes: string): Promise<vscode.AuthenticationSession> {
const token = await this._githubServer.login(scopes);
const session = await this.tokenToSession(token, scopes.split(' '));
await this.setToken(session);
@ -136,12 +136,12 @@ export class GitHubAuthenticationProvider {
this._githubServer.manuallyProvideToken();
}
private async tokenToSession(token: string, scopes: string[]): Promise<vscode.AuthenticationSession2> {
private async tokenToSession(token: string, scopes: string[]): Promise<vscode.AuthenticationSession> {
const userInfo = await this._githubServer.getUserInfo(token);
return new vscode.AuthenticationSession2(uuid(), token, { displayName: userInfo.accountName, id: userInfo.id }, scopes);
return new vscode.AuthenticationSession(uuid(), token, { displayName: userInfo.accountName, id: userInfo.id }, scopes);
}
private async setToken(session: vscode.AuthenticationSession2): Promise<void> {
private async setToken(session: vscode.AuthenticationSession): Promise<void> {
const sessionIndex = this._sessions.findIndex(s => s.id === session.id);
if (sessionIndex > -1) {
this._sessions.splice(sessionIndex, 1, session);

View file

@ -7,18 +7,19 @@
'use strict';
const path = require('path');
const withDefaults = require('../shared.webpack.config');
const withBrowserDefaults = require('../shared.webpack.config').browser;
module.exports = withDefaults({
const config = withBrowserDefaults({
context: __dirname,
target: 'webworker',
node: false,
entry: {
extension: './src/extension.ts',
extension: './src/extension.ts'
},
resolve: {
alias: {
'node-fetch': path.resolve(__dirname, 'node_modules/node-fetch/browser.js'),
},
'node-fetch': path.resolve(__dirname, 'node_modules/node-fetch/browser.js')
}
}
});
module.exports = config;

View file

@ -13,17 +13,126 @@
"Other"
],
"activationEvents": [
"onFileSystem:github"
"onFileSystem:codespace",
"onFileSystem:github",
"onCommand:githubBrowser.openRepository"
],
"browser": "./dist/extension.js",
"browser": "./dist/browser/extension.js",
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "githubBrowser.commit",
"title": "Commit",
"icon": "$(check)",
"category": "GitHub Browser"
},
{
"command": "githubBrowser.discardChanges",
"title": "Discard Changes",
"icon": "$(discard)",
"category": "GitHub Browser"
},
{
"command": "githubBrowser.openChanges",
"title": "Open Changes",
"icon": "$(git-compare)",
"category": "GitHub Browser"
},
{
"command": "githubBrowser.openFile",
"title": "Open File",
"icon": "$(go-to-file)",
"category": "GitHub Browser"
}
],
"menus": {
"commandPalette": [
{
"command": "githubBrowser.commit",
"when": "false"
},
{
"command": "githubBrowser.discardChanges",
"when": "false"
},
{
"command": "githubBrowser.openChanges",
"when": "false"
},
{
"command": "githubBrowser.openFile",
"when": "false"
}
],
"scm/title": [
{
"command": "githubBrowser.commit",
"group": "navigation",
"when": "scmProvider == github"
}
],
"scm/resourceState/context": [
{
"command": "githubBrowser.openFile",
"when": "scmProvider == github && scmResourceGroup == github.changes",
"group": "inline@0"
},
{
"command": "githubBrowser.discardChanges",
"when": "scmProvider == github && scmResourceGroup == github.changes",
"group": "inline@1"
},
{
"command": "githubBrowser.openChanges",
"when": "scmProvider == github && scmResourceGroup == github.changes",
"group": "navigation@0"
},
{
"command": "githubBrowser.openFile",
"when": "scmProvider == github && scmResourceGroup == github.changes",
"group": "navigation@1"
},
{
"command": "githubBrowser.discardChanges",
"when": "scmProvider == github && scmResourceGroup == github.changes",
"group": "1_modification@0"
}
]
},
"resourceLabelFormatters": [
{
"scheme": "github",
"authority": "HEAD",
"formatting": {
"label": "github.com${path}",
"separator": "/",
"workspaceSuffix": "GitHub"
}
},
{
"scheme": "github",
"authority": "*",
"formatting": {
"label": "${authority}${path}",
"label": "github.com${path} (${authority})",
"separator": "/",
"workspaceSuffix": "GitHub"
}
},
{
"scheme": "codespace",
"authority": "HEAD",
"formatting": {
"label": "github.com${path}",
"separator": "/",
"workspaceSuffix": "GitHub"
}
},
{
"scheme": "codespace",
"authority": "*",
"formatting": {
"label": "github.com${path} (${authority})",
"separator": "/",
"workspaceSuffix": "GitHub"
}
@ -38,14 +147,13 @@
"vscode:prepublish": "npm run compile"
},
"dependencies": {
"@octokit/graphql": "4.5.0",
"@octokit/rest": "17.11.0",
"@octokit/graphql": "4.5.1",
"@octokit/rest": "18.0.0",
"fuzzysort": "1.1.4",
"node-fetch": "2.6.0"
"node-fetch": "2.6.0",
"vscode-nls": "4.1.2"
},
"devDependencies": {
"@types/node-fetch": "2.5.7",
"webpack": "4.43.0",
"webpack-cli": "3.3.11"
"@types/node-fetch": "2.5.7"
}
}

View file

@ -3,9 +3,62 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { GitHubFS } from './githubfs';
import { ExtensionContext, Uri, workspace } from 'vscode';
import { ChangeStore, ContextStore } from './stores';
import { VirtualFS } from './fs';
import { GitHubApiContext, GitHubApi } from './github/api';
import { GitHubFS } from './github/fs';
import { VirtualSCM } from './scm';
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(new GitHubFS());
// const repositoryRegex = /^(?:(?:https:\/\/)?github.com\/)?([^\/]+)\/([^\/]+?)(?:\/|.git|$)/i;
export function activate(context: ExtensionContext) {
const contextStore = new ContextStore<GitHubApiContext>(context.workspaceState, GitHubFS.scheme);
const changeStore = new ChangeStore(context.workspaceState);
const githubApi = new GitHubApi(contextStore);
const gitHubFS = new GitHubFS(githubApi);
const virtualFS = new VirtualFS('codespace', GitHubFS.scheme, contextStore, changeStore, gitHubFS);
context.subscriptions.push(
githubApi,
gitHubFS,
virtualFS,
new VirtualSCM(GitHubFS.scheme, githubApi, changeStore)
);
// commands.registerCommand('githubBrowser.openRepository', async () => {
// const value = await window.showInputBox({
// placeHolder: 'e.g. https://github.com/microsoft/vscode',
// prompt: 'Enter a GitHub repository url',
// validateInput: value => repositoryRegex.test(value) ? undefined : 'Invalid repository url'
// });
// if (value) {
// const match = repositoryRegex.exec(value);
// if (match) {
// const [, owner, repo] = match;
// const uri = Uri.parse(`codespace://HEAD/${owner}/${repo}`);
// openWorkspace(uri, repo, 'currentWindow');
// }
// }
// });
}
export function getRelativePath(rootUri: Uri, uri: Uri) {
return uri.fsPath.substr(rootUri.fsPath.length + 1);
}
export function getRootUri(uri: Uri) {
return workspace.getWorkspaceFolder(uri)?.uri;
}
// function openWorkspace(uri: Uri, name: string, location: 'currentWindow' | 'newWindow' | 'addToCurrentWorkspace') {
// if (location === 'addToCurrentWorkspace') {
// const count = (workspace.workspaceFolders && workspace.workspaceFolders.length) || 0;
// return workspace.updateWorkspaceFolders(count, 0, { uri: uri, name: name });
// }
// return commands.executeCommand('vscode.openFolder', uri, location === 'newWindow');
// }

View file

@ -0,0 +1,210 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import {
CancellationToken,
Disposable,
Event,
EventEmitter,
FileChangeEvent,
FileChangeType,
FileSearchOptions,
FileSearchProvider,
FileSearchQuery,
FileStat,
FileSystemError,
FileSystemProvider,
FileType,
Progress,
TextSearchOptions,
TextSearchProvider,
TextSearchQuery,
TextSearchResult,
Uri,
workspace,
} from 'vscode';
import { ContextStore, IWritableChangeStore } from './stores';
import { GitHubApiContext } from './github/api';
const emptyDisposable = { dispose: () => { /* noop */ } };
const textEncoder = new TextEncoder();
export class VirtualFS implements FileSystemProvider, FileSearchProvider, TextSearchProvider, Disposable {
private _onDidChangeFile = new EventEmitter<FileChangeEvent[]>();
get onDidChangeFile(): Event<FileChangeEvent[]> {
return this._onDidChangeFile.event;
}
private readonly disposable: Disposable;
constructor(
readonly scheme: string,
private readonly originalScheme: string,
contextStore: ContextStore<GitHubApiContext>,
private readonly changeStore: IWritableChangeStore,
private readonly fs: FileSystemProvider & FileSearchProvider & TextSearchProvider
) {
// TODO@eamodio listen for workspace folder changes
for (const folder of workspace.workspaceFolders ?? []) {
const uri = this.getOriginalResource(folder.uri);
// If we have a saved context, but no longer have any changes, reset the context
// We only do this on startup/reload to keep things consistent
if (contextStore.get(uri) !== undefined && !changeStore.hasChanges(folder.uri)) {
contextStore.delete(uri);
}
}
this.disposable = Disposable.from(
workspace.registerFileSystemProvider(scheme, this, {
isCaseSensitive: true,
}),
workspace.registerFileSearchProvider(scheme, this),
workspace.registerTextSearchProvider(scheme, this),
changeStore.onDidChange(e => {
switch (e.type) {
case 'created':
this._onDidChangeFile.fire([{ type: FileChangeType.Created, uri: e.uri }]);
break;
case 'changed':
this._onDidChangeFile.fire([{ type: FileChangeType.Changed, uri: e.uri }]);
break;
case 'deleted':
this._onDidChangeFile.fire([{ type: FileChangeType.Deleted, uri: e.uri }]);
break;
}
}),
);
}
dispose() {
this.disposable?.dispose();
}
private getOriginalResource(uri: Uri): Uri {
return uri.with({ scheme: this.originalScheme });
}
//#region FileSystemProvider
watch(): Disposable {
return emptyDisposable;
}
async stat(uri: Uri): Promise<FileStat> {
let stat = this.changeStore.getStat(uri);
if (stat !== undefined) {
return stat;
}
stat = await this.fs.stat(this.getOriginalResource(uri));
return stat;
}
async readDirectory(uri: Uri): Promise<[string, FileType][]> {
let entries = await this.fs.readDirectory(this.getOriginalResource(uri));
entries = this.changeStore.updateDirectoryEntries(uri, entries);
return entries;
}
createDirectory(_uri: Uri): void | Thenable<void> {
// TODO@eamodio only support files for now
throw FileSystemError.NoPermissions();
}
async readFile(uri: Uri): Promise<Uint8Array> {
const content = this.changeStore.getContent(uri);
if (content !== undefined) {
return textEncoder.encode(content);
}
const data = await this.fs.readFile(this.getOriginalResource(uri));
return data;
}
async writeFile(uri: Uri, content: Uint8Array, options: { create: boolean, overwrite: boolean }): Promise<void> {
let stat;
try {
stat = await this.stat(uri);
if (!options.overwrite) {
throw FileSystemError.FileExists();
}
} catch (ex) {
if (ex instanceof FileSystemError && ex.code === 'FileNotFound') {
if (!options.create) {
throw FileSystemError.FileNotFound();
}
} else {
throw ex;
}
}
if (stat === undefined) {
await this.changeStore.onFileCreated(uri, content);
} else {
await this.changeStore.onFileChanged(uri, content, () => this.fs.readFile(this.getOriginalResource(uri)));
}
}
async delete(uri: Uri, _options: { recursive: boolean }): Promise<void> {
const stat = await this.stat(uri);
if (stat.type !== FileType.File) {
throw FileSystemError.NoPermissions();
}
await this.changeStore.onFileDeleted(uri);
}
async rename(oldUri: Uri, newUri: Uri, options: { overwrite: boolean }): Promise<void> {
const stat = await this.stat(oldUri);
// TODO@eamodio only support files for now
if (stat.type !== FileType.File) {
throw FileSystemError.NoPermissions();
}
const content = await this.readFile(oldUri);
await this.writeFile(newUri, content, { create: true, overwrite: options.overwrite });
await this.delete(oldUri, { recursive: false });
}
async copy(source: Uri, destination: Uri, options: { overwrite: boolean }): Promise<void> {
const stat = await this.stat(source);
// TODO@eamodio only support files for now
if (stat.type !== FileType.File) {
throw FileSystemError.NoPermissions();
}
const content = await this.readFile(source);
await this.writeFile(destination, content, { create: true, overwrite: options.overwrite });
}
//#endregion
//#region FileSearchProvider
provideFileSearchResults(
query: FileSearchQuery,
options: FileSearchOptions,
token: CancellationToken,
) {
return this.fs.provideFileSearchResults(query, { ...options, folder: this.getOriginalResource(options.folder) }, token);
}
//#endregion
//#region TextSearchProvider
provideTextSearchResults(
query: TextSearchQuery,
options: TextSearchOptions,
progress: Progress<TextSearchResult>,
token: CancellationToken,
) {
return this.fs.provideTextSearchResults(query, { ...options, folder: this.getOriginalResource(options.folder) }, progress, token);
}
//#endregion
}

View file

@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
const emptyStr = '';
function defaultResolver(...args: any[]): string {
if (args.length === 1) {
const arg0 = args[0];
if (arg0 === undefined || arg0 === null) {
return emptyStr;
}
if (typeof arg0 === 'string') {
return arg0;
}
if (typeof arg0 === 'number' || typeof arg0 === 'boolean') {
return String(arg0);
}
return JSON.stringify(arg0);
}
return JSON.stringify(args);
}
function iPromise<T>(obj: T | Promise<T>): obj is Promise<T> {
return typeof (obj as Promise<T>)?.then === 'function';
}
export function gate<T extends (...arg: any) => any>(resolver?: (...args: Parameters<T>) => string) {
return (_target: any, key: string, descriptor: PropertyDescriptor) => {
let fn: Function | undefined;
if (typeof descriptor.value === 'function') {
fn = descriptor.value;
} else if (typeof descriptor.get === 'function') {
fn = descriptor.get;
}
if (fn === undefined || fn === null) {
throw new Error('Not supported');
}
const gateKey = `$gate$${key}`;
descriptor.value = function (this: any, ...args: any[]) {
const prop =
args.length === 0 ? gateKey : `${gateKey}$${(resolver ?? defaultResolver)(...(args as Parameters<T>))}`;
if (!Object.prototype.hasOwnProperty.call(this, prop)) {
Object.defineProperty(this, prop, {
configurable: false,
enumerable: false,
writable: true,
value: undefined,
});
}
let promise = this[prop];
if (promise === undefined) {
let result;
try {
result = fn!.apply(this, args);
if (result === undefined || fn === null || !iPromise(result)) {
return result;
}
this[prop] = promise = result
.then((r: any) => {
this[prop] = undefined;
return r;
})
.catch(ex => {
this[prop] = undefined;
throw ex;
});
} catch (ex) {
this[prop] = undefined;
throw ex;
}
}
return promise;
};
};
}

View file

@ -0,0 +1,488 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { authentication, AuthenticationSession, Disposable, Event, EventEmitter, Range, Uri } from 'vscode';
import { graphql } from '@octokit/graphql';
import { Octokit } from '@octokit/rest';
import { fromGitHubUri } from './fs';
import { Iterables } from '../iterables';
import { ContextStore } from '../stores';
export const shaRegex = /^[0-9a-f]{40}$/;
export interface GitHubApiContext {
sha: string;
timestamp: number;
}
interface CreateCommitOperation {
type: 'created';
path: string;
content: string
}
interface ChangeCommitOperation {
type: 'changed';
path: string;
content: string
}
interface DeleteCommitOperation {
type: 'deleted';
path: string;
content: undefined
}
export type CommitOperation = CreateCommitOperation | ChangeCommitOperation | DeleteCommitOperation;
type ArrayElement<T extends Array<unknown>> = T extends (infer U)[] ? U : never;
type GitCreateTreeParamsTree = ArrayElement<NonNullable<Parameters<Octokit['git']['createTree']>[0]>['tree']>;
function getGitHubRootUri(uri: Uri) {
const rootIndex = uri.path.indexOf('/', uri.path.indexOf('/', 1) + 1);
return uri.with({
path: uri.path.substring(0, rootIndex === -1 ? undefined : rootIndex),
query: ''
});
}
export class GitHubApi implements Disposable {
private _onDidChangeContext = new EventEmitter<Uri>();
get onDidChangeContext(): Event<Uri> {
return this._onDidChangeContext.event;
}
private readonly disposable: Disposable;
constructor(private readonly context: ContextStore<GitHubApiContext>) {
this.disposable = Disposable.from(
context.onDidChange(e => this._onDidChangeContext.fire(e))
);
}
dispose() {
this.disposable.dispose();
}
private _session: AuthenticationSession | undefined;
async ensureAuthenticated() {
if (this._session === undefined) {
const providers = await authentication.getProviderIds();
if (!providers.includes('github')) {
await new Promise(resolve => {
authentication.onDidChangeAuthenticationProviders(e => {
if (e.added.includes('github')) {
resolve();
}
});
});
}
this._session = await authentication.getSession('github', ['repo'], { createIfNone: true });
}
return this._session;
}
private _graphql: typeof graphql | undefined;
private async graphql() {
if (this._graphql === undefined) {
const session = await this.ensureAuthenticated();
this._graphql = graphql.defaults({
headers: {
Authorization: `Bearer ${session.accessToken}`,
}
});
}
return this._graphql;
}
private _octokit: typeof Octokit | undefined;
private async octokit(options?: ConstructorParameters<typeof Octokit>[0]) {
if (this._octokit === undefined) {
const session = await this.ensureAuthenticated();
this._octokit = Octokit.defaults({ auth: `token ${session.accessToken}` });
}
return new this._octokit(options);
}
async commit(rootUri: Uri, message: string, operations: CommitOperation[]): Promise<string | undefined> {
let { owner, repo, ref } = fromGitHubUri(rootUri);
try {
if (ref === undefined || ref === 'HEAD') {
ref = await this.defaultBranchQuery(rootUri);
if (ref === undefined) {
throw new Error('Cannot commit — invalid ref');
}
}
const context = await this.getContext(rootUri);
if (context.sha === undefined) {
throw new Error('Cannot commit — invalid context');
}
const hasDeletes = operations.some(op => op.type === 'deleted');
const github = await this.octokit();
const treeResp = await github.git.getTree({
owner: owner,
repo: repo,
tree_sha: context.sha,
recursive: hasDeletes ? 'true' : undefined,
});
// 0100000000000000 (040000): Directory
// 1000000110100100 (100644): Regular non-executable file
// 1000000110110100 (100664): Regular non-executable group-writeable file
// 1000000111101101 (100755): Regular executable file
// 1010000000000000 (120000): Symbolic link
// 1110000000000000 (160000): Gitlink
let updatedTree: GitCreateTreeParamsTree[];
if (hasDeletes) {
updatedTree = treeResp.data.tree as GitCreateTreeParamsTree[];
for (const operation of operations) {
switch (operation.type) {
case 'created':
updatedTree.push({ path: operation.path, mode: '100644', type: 'blob', content: operation.content });
break;
case 'changed':
const item = updatedTree.find(item => item.path === operation.path);
if (item !== undefined) {
updatedTree.push({ ...item, content: operation.content });
}
break;
case 'deleted':
const index = updatedTree.findIndex(item => item.path === operation.path);
if (index !== -1) {
updatedTree.splice(index, 1);
}
break;
}
}
} else {
updatedTree = [];
for (const operation of operations) {
switch (operation.type) {
case 'created':
updatedTree.push({ path: operation.path, mode: '100644', type: 'blob', content: operation.content });
break;
case 'changed':
const item = treeResp.data.tree.find(item => item.path === operation.path) as GitCreateTreeParamsTree;
if (item !== undefined) {
updatedTree.push({ ...item, content: operation.content });
}
break;
}
}
}
const updatedTreeResp = await github.git.createTree({
owner: owner,
repo: repo,
base_tree: hasDeletes ? undefined : treeResp.data.sha,
tree: updatedTree
});
const resp = await github.git.createCommit({
owner: owner,
repo: repo,
message: message,
tree: updatedTreeResp.data.sha,
parents: [context.sha]
});
this.updateContext(rootUri, { sha: resp.data.sha, timestamp: Date.now() });
// TODO@eamodio need to send a file change for any open files
await github.git.updateRef({
owner: owner,
repo: repo,
ref: `heads/${ref}`,
sha: resp.data.sha
});
return resp.data.sha;
} catch (ex) {
console.log(ex);
throw ex;
}
}
async defaultBranchQuery(uri: Uri) {
const { owner, repo } = fromGitHubUri(uri);
try {
const query = `query defaultBranch($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
defaultBranchRef {
name
}
}
}`;
const rsp = await this.gqlQuery<{
repository: { defaultBranchRef: { name: string; target: { oid: string } } | null | undefined };
}>(query, {
owner: owner,
repo: repo,
});
return rsp?.repository?.defaultBranchRef?.name ?? undefined;
} catch (ex) {
return undefined;
}
}
async filesQuery(uri: Uri) {
const { owner, repo, ref } = fromGitHubUri(uri);
try {
const context = await this.getContext(uri);
const resp = await (await this.octokit()).git.getTree({
owner: owner,
repo: repo,
recursive: '1',
tree_sha: context?.sha ?? ref ?? 'HEAD',
});
return Iterables.filterMap(resp.data.tree, p => p.type === 'blob' ? p.path : undefined);
} catch (ex) {
return [];
}
}
async fsQuery<T>(uri: Uri, innerQuery: string): Promise<T | undefined> {
const { owner, repo, path, ref } = fromGitHubUri(uri);
try {
const context = await this.getContext(uri);
const query = `query fs($owner: String!, $repo: String!, $path: String) {
repository(owner: $owner, name: $repo) {
object(expression: $path) {
${innerQuery}
}
}
}`;
const rsp = await this.gqlQuery<{
repository: { object: T | null | undefined };
}>(query, {
owner: owner,
repo: repo,
path: `${context.sha ?? ref ?? 'HEAD'}:${path}`,
});
return rsp?.repository?.object ?? undefined;
} catch (ex) {
return undefined;
}
}
async latestCommitQuery(uri: Uri) {
const { owner, repo, ref } = fromGitHubUri(uri);
try {
if (ref === undefined || ref === 'HEAD') {
const query = `query latest($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
defaultBranchRef {
target {
oid
}
}
}
}`;
const rsp = await this.gqlQuery<{
repository: { defaultBranchRef: { name: string; target: { oid: string } } | null | undefined };
}>(query, {
owner: owner,
repo: repo,
});
return rsp?.repository?.defaultBranchRef?.target.oid ?? undefined;
}
const query = `query latest($owner: String!, $repo: String!, $ref: String!) {
repository(owner: $owner, name: $repo) {
ref(qualifiedName: $ref) {
target {
oid
}
}
}`;
const rsp = await this.gqlQuery<{
repository: { ref: { target: { oid: string } } | null | undefined };
}>(query, {
owner: owner,
repo: repo,
ref: ref ?? 'HEAD',
});
return rsp?.repository?.ref?.target.oid ?? undefined;
} catch (ex) {
return undefined;
}
}
async searchQuery(
query: string,
uri: Uri,
options: { maxResults?: number; context?: { before?: number; after?: number } },
): Promise<SearchQueryResults> {
const { owner, repo, ref } = fromGitHubUri(uri);
// If we have a specific ref, don't try to search, because GitHub search only works against the default branch
if (ref === undefined) {
return { matches: [], limitHit: true };
}
try {
const resp = await (await this.octokit({
request: {
headers: {
accept: 'application/vnd.github.v3.text-match+json',
},
}
})).search.code({
q: `${query} repo:${owner}/${repo}`,
});
// Since GitHub doesn't return ANY line numbers just fake it at the top of the file 😢
const range = new Range(0, 0, 0, 0);
const matches: SearchQueryMatch[] = [];
let counter = 0;
let match: SearchQueryMatch;
for (const item of resp.data.items) {
for (const m of (item as typeof item & { text_matches: GitHubSearchTextMatch[] }).text_matches) {
counter++;
if (options.maxResults !== undefined && counter > options.maxResults) {
return { matches: matches, limitHit: true };
}
match = {
path: item.path,
ranges: [],
preview: m.fragment,
matches: [],
};
for (const lm of m.matches) {
let line = 0;
let shartChar = 0;
let endChar = 0;
for (let i = 0; i < lm.indices[1]; i++) {
if (i === lm.indices[0]) {
shartChar = endChar;
}
if (m.fragment[i] === '\n') {
line++;
endChar = 0;
} else {
endChar++;
}
}
match.ranges.push(range);
match.matches.push(new Range(line, shartChar, line, endChar));
}
matches.push(match);
}
}
return { matches: matches, limitHit: false };
} catch (ex) {
return { matches: [], limitHit: true };
}
}
private async gqlQuery<T>(query: string, variables: { [key: string]: string | number }): Promise<T | undefined> {
return (await this.graphql())<T>(query, variables);
}
private readonly pendingContextRequests = new Map<string, Promise<GitHubApiContext>>();
async getContext(uri: Uri): Promise<GitHubApiContext> {
const rootUri = getGitHubRootUri(uri);
let pending = this.pendingContextRequests.get(rootUri.toString());
if (pending === undefined) {
pending = this.getContextCore(rootUri);
this.pendingContextRequests.set(rootUri.toString(), pending);
}
try {
return await pending;
} finally {
this.pendingContextRequests.delete(rootUri.toString());
}
}
private readonly rootUriToContextMap = new Map<string, GitHubApiContext>();
private async getContextCore(rootUri: Uri): Promise<GitHubApiContext> {
let context = this.rootUriToContextMap.get(rootUri.toString());
if (context === undefined) {
const { ref } = fromGitHubUri(rootUri);
if (ref !== undefined && shaRegex.test(ref)) {
context = { sha: ref, timestamp: Date.now() };
} else {
context = this.context.get(rootUri);
if (context?.sha === undefined) {
const sha = await this.latestCommitQuery(rootUri);
if (sha !== undefined) {
context = { sha: sha, timestamp: Date.now() };
} else {
context = undefined;
}
}
}
if (context !== undefined) {
this.updateContext(rootUri, context);
}
}
return context ?? { sha: rootUri.authority, timestamp: Date.now() };
}
private updateContext(rootUri: Uri, context: GitHubApiContext) {
this.rootUriToContextMap.set(rootUri.toString(), context);
this.context.set(rootUri, context);
}
}
interface GitHubSearchTextMatch {
object_url: string;
object_type: string;
property: string;
fragment: string;
matches: {
text: string;
indices: number[];
}[];
}
interface SearchQueryMatch {
path: string;
ranges: Range[];
preview: string;
matches: Range[];
}
interface SearchQueryResults {
matches: SearchQueryMatch[];
limitHit: boolean;
}

View file

@ -0,0 +1,332 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import {
CancellationToken,
Disposable,
Event,
EventEmitter,
FileChangeEvent,
FileSearchOptions,
FileSearchProvider,
FileSearchQuery,
FileStat,
FileSystemError,
FileSystemProvider,
FileType,
Progress,
TextSearchComplete,
TextSearchOptions,
TextSearchProvider,
TextSearchQuery,
TextSearchResult,
Uri,
workspace,
} from 'vscode';
import * as fuzzySort from 'fuzzysort';
import fetch from 'node-fetch';
import { GitHubApi } from './api';
import { Iterables } from '../iterables';
import { getRootUri } from '../extension';
const emptyDisposable = { dispose: () => { /* noop */ } };
const replaceBackslashRegex = /(\/|\\)/g;
const textEncoder = new TextEncoder();
interface Fuzzysort extends Fuzzysort.Fuzzysort {
prepareSlow(target: string): Fuzzysort.Prepared;
cleanup(): void;
}
export class GitHubFS implements FileSystemProvider, FileSearchProvider, TextSearchProvider, Disposable {
static scheme = 'github';
private _onDidChangeFile = new EventEmitter<FileChangeEvent[]>();
get onDidChangeFile(): Event<FileChangeEvent[]> {
return this._onDidChangeFile.event;
}
private readonly disposable: Disposable;
private fsCache = new Map<string, Map<string, any>>();
constructor(private readonly github: GitHubApi) {
this.disposable = Disposable.from(
workspace.registerFileSystemProvider(GitHubFS.scheme, this, {
isCaseSensitive: true,
isReadonly: true
}),
workspace.registerFileSearchProvider(GitHubFS.scheme, this),
workspace.registerTextSearchProvider(GitHubFS.scheme, this),
github.onDidChangeContext(e => this.fsCache.delete(e.toString()))
);
}
dispose() {
this.disposable?.dispose();
}
private getCache(uri: Uri) {
const rootUri = getRootUri(uri);
if (rootUri === undefined) {
return undefined;
}
let cache = this.fsCache.get(rootUri.toString());
if (cache === undefined) {
cache = new Map<string, any>();
this.fsCache.set(rootUri.toString(), cache);
}
return cache;
}
//#region FileSystemProvider
watch(): Disposable {
return emptyDisposable;
}
async stat(uri: Uri): Promise<FileStat> {
if (uri.path === '' || uri.path.lastIndexOf('/') === 0) {
const context = await this.github.getContext(uri);
return { type: FileType.Directory, size: 0, ctime: 0, mtime: context?.timestamp };
}
const data = await this.fsQuery<{
__typename: string;
byteSize: number | undefined;
}>(
uri,
`__typename
...on Blob {
byteSize
}`,
this.getCache(uri),
);
if (data === undefined) {
throw FileSystemError.FileNotFound();
}
const context = await this.github.getContext(uri);
return {
type: typenameToFileType(data.__typename),
size: data.byteSize ?? 0,
ctime: 0,
mtime: context?.timestamp,
};
}
async readDirectory(uri: Uri): Promise<[string, FileType][]> {
const data = await this.fsQuery<{
entries: { name: string; type: string }[];
}>(
uri,
`... on Tree {
entries {
name
type
}
}`,
this.getCache(uri),
);
return (data?.entries ?? []).map<[string, FileType]>(e => [
e.name,
typenameToFileType(e.type),
]);
}
createDirectory(_uri: Uri): void | Thenable<void> {
throw FileSystemError.NoPermissions();
}
async readFile(uri: Uri): Promise<Uint8Array> {
const data = await this.fsQuery<{
oid: string;
isBinary: boolean;
text: string;
}>(
uri,
`... on Blob {
oid,
isBinary,
text
}`,
);
if (data?.isBinary) {
const { owner, repo, path } = fromGitHubUri(uri);
// e.g. https://raw.githubusercontent.com/eamodio/vscode-gitlens/HEAD/images/gitlens-icon.png
const downloadUri = uri.with({
scheme: 'https',
authority: 'raw.githubusercontent.com',
path: `/${owner}/${repo}/HEAD/${path}`,
});
return downloadBinary(downloadUri);
}
return textEncoder.encode(data?.text ?? '');
}
async writeFile(_uri: Uri, _content: Uint8Array, _options: { create: boolean, overwrite: boolean }): Promise<void> {
throw FileSystemError.NoPermissions();
}
delete(_uri: Uri, _options: { recursive: boolean }): void | Thenable<void> {
throw FileSystemError.NoPermissions();
}
rename(_oldUri: Uri, _newUri: Uri, _options: { overwrite: boolean }): void | Thenable<void> {
throw FileSystemError.NoPermissions();
}
copy(_source: Uri, _destination: Uri, _options: { overwrite: boolean }): void | Thenable<void> {
throw FileSystemError.NoPermissions();
}
//#endregion
//#region FileSearchProvider
private fileSearchCache = new Map<string, Fuzzysort.Prepared[]>();
async provideFileSearchResults(
query: FileSearchQuery,
options: FileSearchOptions,
token: CancellationToken,
): Promise<Uri[]> {
let searchable = this.fileSearchCache.get(options.folder.toString(true));
if (searchable === undefined) {
const matches = await this.github.filesQuery(options.folder);
if (matches === undefined || token.isCancellationRequested) {
return [];
}
searchable = [...Iterables.map(matches, m => (fuzzySort as Fuzzysort).prepareSlow(m))];
this.fileSearchCache.set(options.folder.toString(true), searchable);
}
if (options.maxResults === undefined || options.maxResults === 0 || options.maxResults >= searchable.length) {
const results = searchable.map(m => Uri.joinPath(options.folder, m.target));
return results;
}
const results = fuzzySort
.go(query.pattern.replace(replaceBackslashRegex, '/'), searchable, {
allowTypo: true,
limit: options.maxResults,
})
.map(m => Uri.joinPath(options.folder, m.target));
(fuzzySort as Fuzzysort).cleanup();
return results;
}
//#endregion
//#region TextSearchProvider
async provideTextSearchResults(
query: TextSearchQuery,
options: TextSearchOptions,
progress: Progress<TextSearchResult>,
_token: CancellationToken,
): Promise<TextSearchComplete> {
const results = await this.github.searchQuery(
query.pattern,
options.folder,
{ maxResults: options.maxResults, context: { before: options.beforeContext, after: options.afterContext } },
);
if (results === undefined) { return { limitHit: true }; }
let uri;
for (const m of results.matches) {
uri = Uri.joinPath(options.folder, m.path);
progress.report({
uri: uri,
ranges: m.ranges,
preview: {
text: m.preview,
matches: m.matches,
},
});
}
return { limitHit: false };
}
//#endregion
private async fsQuery<T>(uri: Uri, query: string, cache?: Map<string, any>): Promise<T | undefined> {
const key = `${uri.toString()}:${getHashCode(query)}`;
let data = cache?.get(key);
if (data !== undefined) {
return data as T;
}
data = await this.github.fsQuery<T>(uri, query);
cache?.set(key, data);
return data;
}
}
async function downloadBinary(uri: Uri) {
const resp = await fetch(uri.toString());
const array = new Uint8Array(await resp.arrayBuffer());
return array;
}
function typenameToFileType(typename: string | undefined | null) {
if (typename) {
typename = typename.toLocaleLowerCase();
}
switch (typename) {
case 'blob':
return FileType.File;
case 'tree':
return FileType.Directory;
default:
return FileType.Unknown;
}
}
type RepoInfo = { owner: string; repo: string; path: string | undefined; ref?: string };
export function fromGitHubUri(uri: Uri): RepoInfo {
const [, owner, repo, ...rest] = uri.path.split('/');
let ref;
if (uri.authority) {
ref = uri.authority;
// The casing of HEAD is important for the GitHub api to work
if (/HEAD/i.test(ref)) {
ref = 'HEAD';
}
}
return { owner: owner, repo: repo, path: rest.join('/'), ref: ref };
}
function getHashCode(s: string): number {
let hash = 0;
if (s.length === 0) {
return hash;
}
let char;
const len = s.length;
for (let i = 0; i < len; i++) {
char = s.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash |= 0; // Convert to 32bit integer
}
return hash;
}

View file

@ -1,484 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import {
authentication,
AuthenticationSession2,
CancellationToken,
Disposable,
Event,
EventEmitter,
FileChangeEvent,
FileSearchOptions,
FileSearchProvider,
FileSearchQuery,
FileStat,
FileSystemError,
FileSystemProvider,
FileType,
Progress,
Range,
TextSearchComplete,
TextSearchOptions,
TextSearchProvider,
TextSearchQuery,
TextSearchResult,
Uri,
workspace,
} from 'vscode';
import { Octokit } from '@octokit/rest';
import { graphql } from '@octokit/graphql/';
import * as fuzzySort from 'fuzzysort';
import fetch from 'node-fetch';
import { Iterables } from './iterables';
const emptyDisposable = { dispose: () => { /* noop */ } };
const replaceBackslashRegex = /(\/|\\)/g;
const textEncoder = new TextEncoder();
export class GitHubFS implements FileSystemProvider, FileSearchProvider, TextSearchProvider, Disposable {
static scheme = 'github';
private _onDidChangeFile = new EventEmitter<FileChangeEvent[]>();
get onDidChangeFile(): Event<FileChangeEvent[]> {
return this._onDidChangeFile.event;
}
private readonly disposable: Disposable;
private fsCache = new Map<string, any>();
constructor() {
this.disposable = Disposable.from(
workspace.registerFileSystemProvider(GitHubFS.scheme, this, {
isCaseSensitive: true,
isReadonly: true,
}),
workspace.registerFileSearchProvider(GitHubFS.scheme, this),
workspace.registerTextSearchProvider(GitHubFS.scheme, this)
);
}
dispose() {
this.disposable?.dispose();
}
private _github: Promise<GitHubApi | undefined> | undefined;
get github(): Promise<GitHubApi | undefined> {
if (this._github === undefined) {
this._github = this.getGitHubApi();
}
return this._github;
}
private async getGitHubApi(): Promise<GitHubApi | undefined> {
try {
const session = await authentication.getSession('github', ['repo'], { createIfNone: true });
return new GitHubApi(session);
} catch (ex) {
this._github = undefined;
throw ex;
}
}
//#region FileSystemProvider
watch(): Disposable {
return emptyDisposable;
}
async stat(uri: Uri): Promise<FileStat> {
if (uri.path === '' || uri.path.lastIndexOf('/') === 0) {
return { type: FileType.Directory, size: 0, ctime: 0, mtime: 0 };
}
const data = await this.fsQuery<{
__typename: string;
byteSize: number | undefined;
}>(
uri,
`__typename
...on Blob {
byteSize
}`,
this.fsCache,
);
return {
type: typenameToFileType(data?.__typename),
size: data?.byteSize ?? 0,
ctime: 0,
mtime: 0,
};
}
async readDirectory(uri: Uri): Promise<[string, FileType][]> {
const data = await this.fsQuery<{
entries: { name: string; type: string }[];
}>(
uri,
`... on Tree {
entries {
name
type
}
}`,
this.fsCache,
);
return (data?.entries ?? []).map<[string, FileType]>(e => [
e.name,
typenameToFileType(e.type),
]);
}
createDirectory(): void | Thenable<void> {
throw FileSystemError.NoPermissions;
}
async readFile(uri: Uri): Promise<Uint8Array> {
const data = await this.fsQuery<{
oid: string;
isBinary: boolean;
text: string;
}>(
uri,
`... on Blob {
oid,
isBinary,
text
}`,
);
if (data?.isBinary) {
const [owner, repo, path] = fromGitHubUri(uri);
// e.g. https://raw.githubusercontent.com/eamodio/vscode-gitlens/HEAD/images/gitlens-icon.png
const downloadUri = uri.with({
scheme: 'https',
authority: 'raw.githubusercontent.com',
path: `/${owner}/${repo}/HEAD/${path}`,
});
return downloadBinary(downloadUri);
}
return textEncoder.encode(data?.text ?? '');
}
writeFile(): void | Thenable<void> {
throw FileSystemError.NoPermissions;
}
delete(): void | Thenable<void> {
throw FileSystemError.NoPermissions;
}
rename(): void | Thenable<void> {
throw FileSystemError.NoPermissions;
}
copy?(): void | Thenable<void> {
throw FileSystemError.NoPermissions;
}
//#endregion
//#region FileSearchProvider
private fileSearchCache = new Map<string, Fuzzysort.Prepared[]>();
async provideFileSearchResults(
query: FileSearchQuery,
options: FileSearchOptions,
token: CancellationToken,
): Promise<Uri[]> {
let searchable = this.fileSearchCache.get(options.folder.toString(true));
if (searchable === undefined) {
const matches = await (await this.github)?.filesQuery(options.folder);
if (matches === undefined || token.isCancellationRequested) { return []; }
searchable = [...Iterables.map(matches, m => (fuzzySort as any).prepareSlow(m)! as Fuzzysort.Prepared)];
this.fileSearchCache.set(options.folder.toString(true), searchable);
}
if (options.maxResults === undefined || options.maxResults === 0 || options.maxResults >= searchable.length) {
const results = searchable.map(m => Uri.joinPath(options.folder, m.target));
return results;
}
const results = fuzzySort
.go(query.pattern.replace(replaceBackslashRegex, '/'), searchable, {
allowTypo: true,
limit: options.maxResults,
})
.map((m: any) => Uri.joinPath(options.folder, m.target));
(fuzzySort as any).cleanup();
return results;
}
//#endregion
//#region TextSearchProvider
async provideTextSearchResults(
query: TextSearchQuery,
options: TextSearchOptions,
progress: Progress<TextSearchResult>,
token: CancellationToken,
): Promise<TextSearchComplete> {
const results = await (await this.github)?.searchQuery(
query.pattern,
options.folder,
{ maxResults: options.maxResults, context: { before: options.beforeContext, after: options.afterContext } },
token,
);
if (results === undefined) { return { limitHit: true }; }
let uri;
for (const m of results.matches) {
uri = Uri.joinPath(options.folder, m.path);
progress.report({
uri: uri,
ranges: m.ranges,
preview: {
text: m.preview,
matches: m.matches,
},
});
}
return { limitHit: false };
}
//#endregion
private async fsQuery<T>(uri: Uri, query: string, cache?: Map<string, any>): Promise<T | undefined> {
if (cache === undefined) {
return (await this.github)?.fsQuery<T>(uri, query);
}
const key = `${uri.toString()}:${getHashCode(query)}`;
let data = cache.get(key);
if (data !== undefined) { return data as T; }
data = await (await this.github)?.fsQuery<T>(uri, query);
cache.set(key, data);
return data;
}
}
async function downloadBinary(uri: Uri) {
const resp = await fetch(uri.toString());
const array = new Uint8Array(await resp.arrayBuffer());
return array;
}
function typenameToFileType(typename: string | undefined | null) {
if (typename) {
typename = typename.toLocaleLowerCase();
}
switch (typename) {
case 'blob':
return FileType.File;
case 'tree':
return FileType.Directory;
default:
return FileType.Unknown;
}
}
type RepoInfo = [string, string, string | undefined];
function fromGitHubUri(uri: Uri): RepoInfo {
const [, owner, repo, ...rest] = uri.path.split('/');
return [owner, repo, rest.join('/')];
}
function getHashCode(s: string): number {
let hash = 0;
if (s.length === 0) {
return hash;
}
let char;
const len = s.length;
for (let i = 0; i < len; i++) {
char = s.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash |= 0; // Convert to 32bit integer
}
return hash;
}
interface SearchQueryMatch {
path: string;
ranges: Range[];
preview: string;
matches: Range[];
}
interface SearchQueryResults {
matches: SearchQueryMatch[];
limitHit: boolean;
}
class GitHubApi {
constructor(private readonly session: AuthenticationSession2) { }
private _graphql: typeof graphql | undefined;
private get graphql() {
if (this._graphql === undefined) {
this._graphql = graphql.defaults({
headers: {
Authorization: `Bearer ${this.token}`,
}
});
}
return this._graphql;
}
get token() {
return this.session.accessToken;
}
async filesQuery(uri: Uri) {
const [owner, repo] = fromGitHubUri(uri);
try {
const resp = await new Octokit({
auth: `token ${this.token}`,
}).git.getTree({
owner: owner,
repo: repo,
recursive: '1',
tree_sha: 'HEAD',
});
return Iterables.filterMap(resp.data.tree, p => p.type === 'blob' ? p.path : undefined);
} catch (ex) {
return [];
}
}
async searchQuery(
query: string,
uri: Uri,
options: { maxResults?: number; context?: { before?: number; after?: number } },
_token: CancellationToken,
): Promise<SearchQueryResults> {
const [owner, repo] = fromGitHubUri(uri);
try {
const resp = await new Octokit({
auth: `token ${this.token}`,
request: {
headers: {
accept: 'application/vnd.github.v3.text-match+json',
},
}
}).search.code({
q: `${query} repo:${owner}/${repo}`,
});
// Since GitHub doesn't return ANY line numbers just fake it at the top of the file 😢
const range = new Range(0, 0, 0, 0);
const matches: SearchQueryMatch[] = [];
console.log(resp.data.items.length, resp.data.items);
let counter = 0;
let match: SearchQueryMatch;
for (const item of resp.data.items) {
for (const m of (item as typeof item & { text_matches: GitHubSearchTextMatch[] }).text_matches) {
counter++;
if (options.maxResults !== undefined && counter > options.maxResults) {
return { matches: matches, limitHit: true };
}
match = {
path: item.path,
ranges: [],
preview: m.fragment,
matches: [],
};
for (const lm of m.matches) {
let line = 0;
let shartChar = 0;
let endChar = 0;
for (let i = 0; i < lm.indices[1]; i++) {
if (i === lm.indices[0]) {
shartChar = endChar;
}
if (m.fragment[i] === '\n') {
line++;
endChar = 0;
} else {
endChar++;
}
}
match.ranges.push(range);
match.matches.push(new Range(line, shartChar, line, endChar));
}
matches.push(match);
}
}
return { matches: matches, limitHit: false };
} catch (ex) {
return { matches: [], limitHit: true };
}
}
async fsQuery<T>(uri: Uri, innerQuery: string): Promise<T | undefined> {
try {
const query = `query fs($owner: String!, $repo: String!, $path: String) {
repository(owner: $owner, name: $repo) {
object(expression: $path) {
${innerQuery}
}
}
}`;
const [owner, repo, path] = fromGitHubUri(uri);
const variables = {
owner: owner,
repo: repo,
path: `HEAD:${path}`,
};
const rsp = await this.query<{
repository: { object: T };
}>(query, variables);
return rsp?.repository.object;
} catch (ex) {
return undefined;
}
}
query<T>(query: string, variables: { [key: string]: string | number }): Promise<T | undefined> {
return this.graphql(query, variables) as Promise<T | undefined>;
}
}
interface GitHubSearchTextMatch {
object_url: string;
object_type: string;
property: string;
fragment: string;
matches: GitHubSearchMatch[];
}
interface GitHubSearchMatch {
text: string;
indices: number[];
}

View file

@ -0,0 +1,168 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { CancellationToken, commands, Disposable, scm, SourceControl, SourceControlResourceGroup, SourceControlResourceState, Uri, window, workspace } from 'vscode';
import * as nls from 'vscode-nls';
import { IChangeStore } from './stores';
import { GitHubApi, CommitOperation } from './github/api';
import { getRelativePath } from './extension';
const localize = nls.loadMessageBundle();
interface ScmProvider {
sourceControl: SourceControl,
groups: SourceControlResourceGroup[]
}
export class VirtualSCM implements Disposable {
private readonly providers: ScmProvider[] = [];
private disposable: Disposable;
constructor(
private readonly originalScheme: string,
private readonly github: GitHubApi,
private readonly changeStore: IChangeStore,
) {
this.registerCommands();
// TODO@eamodio listen for workspace folder changes
for (const folder of workspace.workspaceFolders ?? []) {
this.createScmProvider(folder.uri, folder.name);
}
this.disposable = Disposable.from(
changeStore.onDidChange(e => this.update(e.rootUri, e.uri)),
);
for (const { uri } of workspace.workspaceFolders ?? []) {
for (const operation of changeStore.getChanges(uri)) {
this.update(uri, operation.uri);
}
}
}
dispose() {
this.disposable.dispose();
}
private registerCommands() {
commands.registerCommand('githubBrowser.commit', (...args: any[]) => this.commitChanges(args[0]));
commands.registerCommand('githubBrowser.discardChanges', (resourceState: SourceControlResourceState) =>
this.discardChanges(resourceState.resourceUri)
);
commands.registerCommand('githubBrowser.openChanges', (resourceState: SourceControlResourceState) =>
this.openChanges(resourceState.resourceUri)
);
commands.registerCommand('githubBrowser.openFile', (resourceState: SourceControlResourceState) =>
this.openFile(resourceState.resourceUri)
);
}
async commitChanges(sourceControl: SourceControl): Promise<void> {
const operations = this.changeStore
.getChanges(sourceControl.rootUri!)
.map<CommitOperation>(operation => {
const path = getRelativePath(sourceControl.rootUri!, operation.uri);
switch (operation.type) {
case 'created':
return { type: operation.type, path: path, content: this.changeStore.getContent(operation.uri)! };
case 'changed':
return { type: operation.type, path: path, content: this.changeStore.getContent(operation.uri)! };
case 'deleted':
return { type: operation.type, path: path };
}
});
if (!operations.length) {
window.showInformationMessage(localize('no changes', "There are no changes to commit."));
return;
}
const message = sourceControl.inputBox.value;
if (message) {
const sha = await this.github.commit(this.getOriginalResource(sourceControl.rootUri!), message, operations);
if (sha !== undefined) {
this.changeStore.acceptAll(sourceControl.rootUri!);
sourceControl.inputBox.value = '';
}
}
}
discardChanges(uri: Uri): Promise<void> {
return this.changeStore.discard(uri);
}
openChanges(uri: Uri) {
return this.changeStore.openChanges(uri, this.getOriginalResource(uri));
}
openFile(uri: Uri) {
return this.changeStore.openFile(uri);
}
private update(rootUri: Uri, uri: Uri) {
const folder = workspace.getWorkspaceFolder(uri);
if (folder === undefined) {
return;
}
const provider = this.createScmProvider(rootUri, folder.name);
const group = this.createChangesGroup(provider);
group.resourceStates = this.changeStore.getChanges(rootUri).map<SourceControlResourceState>(op => {
const rs: SourceControlResourceState = {
decorations: {
strikeThrough: op.type === 'deleted'
},
resourceUri: op.uri,
command: {
command: 'githubBrowser.openChanges',
title: 'Open Changes',
}
};
rs.command!.arguments = [rs];
return rs;
});
}
private createScmProvider(rootUri: Uri, name: string) {
let provider = this.providers.find(sc => sc.sourceControl.rootUri?.toString() === rootUri.toString());
if (provider === undefined) {
const sourceControl = scm.createSourceControl('github', name, rootUri);
sourceControl.quickDiffProvider = { provideOriginalResource: uri => this.getOriginalResource(uri) };
sourceControl.acceptInputCommand = {
command: 'githubBrowser.commit',
title: 'Commit',
arguments: [sourceControl]
};
sourceControl.inputBox.placeholder = `Message (Ctrl+Enter to commit '${name}')`;
// sourceControl.inputBox.validateInput = value => value ? undefined : 'Invalid commit message';
provider = { sourceControl: sourceControl, groups: [] };
this.createChangesGroup(provider);
this.providers.push(provider);
}
return provider;
}
private createChangesGroup(provider: ScmProvider) {
let group = provider.groups.find(g => g.id === 'github.changes');
if (group === undefined) {
group = provider.sourceControl.createResourceGroup('github.changes', 'Changes');
provider.groups.push(group);
}
return group;
}
private getOriginalResource(uri: Uri, _token?: CancellationToken): Uri {
return uri.with({ scheme: this.originalScheme });
}
}

View file

@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
const textDecoder = new TextDecoder();
const textEncoder = new TextEncoder();
declare let WEBWORKER: boolean;
export async function sha1(s: string | Uint8Array): Promise<string> {
while (true) {
try {
if (WEBWORKER) {
const hash = await globalThis.crypto.subtle.digest({ name: 'sha-1' }, typeof s === 'string' ? textEncoder.encode(s) : s);
// Use encodeURIComponent to avoid issues with btoa and Latin-1 characters
return globalThis.btoa(encodeURIComponent(textDecoder.decode(hash)));
} else {
return (await import('crypto')).createHash('sha1').update(s).digest('base64');
}
} catch (ex) {
if (ex instanceof ReferenceError) {
(global as any).WEBWORKER = false;
}
}
}
}

View file

@ -0,0 +1,400 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { commands, Event, EventEmitter, FileStat, FileType, Memento, TextDocumentShowOptions, Uri, ViewColumn } from 'vscode';
import { getRootUri, getRelativePath } from './extension';
import { sha1 } from './sha1';
const textDecoder = new TextDecoder();
interface CreateOperation<T extends string | Uri = string> {
type: 'created';
size: number;
timestamp: number;
uri: T;
hash: string;
originalHash: string;
}
interface ChangeOperation<T extends string | Uri = string> {
type: 'changed';
size: number;
timestamp: number;
uri: T;
hash: string;
originalHash: string;
}
interface DeleteOperation<T extends string | Uri = string> {
type: 'deleted';
size: undefined;
timestamp: number;
uri: T;
hash: undefined;
originalHash: undefined;
}
export type Operation = CreateOperation<Uri> | ChangeOperation<Uri> | DeleteOperation<Uri>;
type StoredOperation = CreateOperation | ChangeOperation | DeleteOperation;
const workingOperationsKeyPrefix = 'github.working.changes|';
const workingFileKeyPrefix = 'github.working|';
function fromSerialized(operations: StoredOperation): Operation {
return { ...operations, uri: Uri.parse(operations.uri) };
}
interface CreatedFileChangeStoreEvent {
type: 'created';
rootUri: Uri;
uri: Uri;
}
interface ChangedFileChangeStoreEvent {
type: 'changed';
rootUri: Uri;
uri: Uri;
}
interface DeletedFileChangeStoreEvent {
type: 'deleted';
rootUri: Uri;
uri: Uri;
}
type ChangeStoreEvent = CreatedFileChangeStoreEvent | ChangedFileChangeStoreEvent | DeletedFileChangeStoreEvent;
function toChangeStoreEvent(operation: Operation | StoredOperation, rootUri: Uri, uri?: Uri): ChangeStoreEvent {
return {
type: operation.type,
rootUri: rootUri,
uri: uri ?? (typeof operation.uri === 'string' ? Uri.parse(operation.uri) : operation.uri)
};
}
export interface IChangeStore {
onDidChange: Event<ChangeStoreEvent>;
acceptAll(rootUri: Uri): Promise<void>;
discard(uri: Uri): Promise<void>;
discardAll(rootUri: Uri): Promise<void>;
getChanges(rootUri: Uri): Operation[];
getContent(uri: Uri): string | undefined;
openChanges(uri: Uri, original: Uri): void;
openFile(uri: Uri): void;
}
export interface IWritableChangeStore {
onDidChange: Event<ChangeStoreEvent>;
hasChanges(rootUri: Uri): boolean;
getContent(uri: Uri): string | undefined;
getStat(uri: Uri): FileStat | undefined;
updateDirectoryEntries(uri: Uri, entries: [string, FileType][]): [string, FileType][];
onFileChanged(uri: Uri, content: Uint8Array, originalContent: () => Uint8Array | Thenable<Uint8Array>): Promise<void>;
onFileCreated(uri: Uri, content: Uint8Array): Promise<void>;
onFileDeleted(uri: Uri): Promise<void>;
}
export class ChangeStore implements IChangeStore, IWritableChangeStore {
private _onDidChange = new EventEmitter<ChangeStoreEvent>();
get onDidChange(): Event<ChangeStoreEvent> {
return this._onDidChange.event;
}
constructor(private readonly memento: Memento) { }
async acceptAll(rootUri: Uri): Promise<void> {
const operations = this.getChanges(rootUri);
await this.saveWorkingOperations(rootUri, undefined);
for (const operation of operations) {
await this.discardWorkingContent(operation.uri);
this._onDidChange.fire(toChangeStoreEvent(operation, rootUri));
}
}
async discard(uri: Uri): Promise<void> {
const rootUri = getRootUri(uri);
if (rootUri === undefined) {
return;
}
const key = uri.toString();
const operations = this.getWorkingOperations(rootUri);
const index = operations.findIndex(c => c.uri === key);
if (index === -1) {
return;
}
const [operation] = operations.splice(index, 1);
await this.saveWorkingOperations(rootUri, operations);
await this.discardWorkingContent(uri);
this._onDidChange.fire({
type: operation.type === 'created' ? 'deleted' : operation.type === 'deleted' ? 'created' : 'changed',
rootUri: rootUri,
uri: uri
});
}
async discardAll(rootUri: Uri): Promise<void> {
const operations = this.getChanges(rootUri);
await this.saveWorkingOperations(rootUri, undefined);
for (const operation of operations) {
await this.discardWorkingContent(operation.uri);
this._onDidChange.fire(toChangeStoreEvent(operation, rootUri));
}
}
getChanges(rootUri: Uri) {
return this.getWorkingOperations(rootUri).map(c => fromSerialized(c));
}
getContent(uri: Uri): string | undefined {
return this.memento.get(`${workingFileKeyPrefix}${uri.toString()}`);
}
getStat(uri: Uri): FileStat | undefined {
const key = uri.toString();
const operation = this.getChanges(getRootUri(uri)!).find(c => c.uri.toString() === key);
if (operation === undefined) {
return undefined;
}
return {
type: FileType.File,
size: operation.size ?? 0,
ctime: 0,
mtime: operation.timestamp
};
}
hasChanges(rootUri: Uri): boolean {
return this.getWorkingOperations(rootUri).length !== 0;
}
updateDirectoryEntries(uri: Uri, entries: [string, FileType][]): [string, FileType][] {
const rootUri = getRootUri(uri);
if (rootUri === undefined) {
return entries;
}
const operations = this.getChanges(rootUri);
for (const operation of operations) {
switch (operation.type) {
case 'changed':
continue;
case 'created': {
const file = getRelativePath(rootUri, operation.uri);
entries.push([file, FileType.File]);
break;
}
case 'deleted': {
const file = getRelativePath(rootUri, operation.uri);
const index = entries.findIndex(([path]) => path === file);
if (index !== -1) {
entries.splice(index, 1);
}
break;
}
}
}
return entries;
}
async onFileChanged(uri: Uri, content: Uint8Array, originalContent: () => Uint8Array | Thenable<Uint8Array>): Promise<void> {
const rootUri = getRootUri(uri);
if (rootUri === undefined) {
return;
}
const key = uri.toString();
const operations = this.getWorkingOperations(rootUri);
const hash = await sha1(content);
let operation = operations.find(c => c.uri === key);
if (operation === undefined) {
const originalHash = await sha1(await originalContent!());
if (hash === originalHash) {
return;
}
operation = {
type: 'changed',
size: content.byteLength,
timestamp: Date.now(),
uri: key,
hash: hash!,
originalHash: originalHash
} as ChangeOperation;
operations.push(operation);
await this.saveWorkingOperations(rootUri, operations);
await this.saveWorkingContent(uri, textDecoder.decode(content));
} else if (hash! === operation.originalHash) {
operations.splice(operations.indexOf(operation), 1);
await this.saveWorkingOperations(rootUri, operations);
await this.discardWorkingContent(uri);
} else if (operation.hash !== hash) {
operation.hash = hash!;
operation.timestamp = Date.now();
await this.saveWorkingOperations(rootUri, operations);
await this.saveWorkingContent(uri, textDecoder.decode(content));
}
this._onDidChange.fire(toChangeStoreEvent(operation, rootUri, uri));
}
async onFileCreated(uri: Uri, content: Uint8Array): Promise<void> {
const rootUri = getRootUri(uri);
if (rootUri === undefined) {
return;
}
const key = uri.toString();
const operations = this.getWorkingOperations(rootUri);
const hash = await sha1(content);
let operation = operations.find(c => c.uri === key);
if (operation === undefined) {
operation = {
type: 'created',
size: content.byteLength,
timestamp: Date.now(),
uri: key,
hash: hash!,
originalHash: hash!
} as CreateOperation;
operations.push(operation);
await this.saveWorkingOperations(rootUri, operations);
await this.saveWorkingContent(uri, textDecoder.decode(content));
} else {
// Shouldn't happen, but if it does just update the contents
operation.hash = hash!;
operation.timestamp = Date.now();
await this.saveWorkingOperations(rootUri, operations);
await this.saveWorkingContent(uri, textDecoder.decode(content));
}
this._onDidChange.fire(toChangeStoreEvent(operation, rootUri, uri));
}
async onFileDeleted(uri: Uri): Promise<void> {
const rootUri = getRootUri(uri);
if (rootUri === undefined) {
return;
}
const key = uri.toString();
const operations = this.getWorkingOperations(rootUri);
let operation = operations.find(c => c.uri === key);
if (operation !== undefined) {
operations.splice(operations.indexOf(operation), 1);
}
const wasCreated = operation?.type === 'created';
operation = {
type: 'deleted',
timestamp: Date.now(),
uri: key,
} as DeleteOperation;
// Only track the delete, if we weren't tracking the create
if (!wasCreated) {
operations.push(operation);
}
await this.saveWorkingOperations(rootUri, operations);
await this.discardWorkingContent(uri);
this._onDidChange.fire(toChangeStoreEvent(operation, rootUri, uri));
}
async openChanges(uri: Uri, original: Uri) {
const opts: TextDocumentShowOptions = {
preserveFocus: false,
preview: true,
viewColumn: ViewColumn.Active
};
await commands.executeCommand('vscode.diff', original, uri, `${uri.fsPath} (Working Tree)`, opts);
}
async openFile(uri: Uri) {
const opts: TextDocumentShowOptions = {
preserveFocus: false,
preview: false,
viewColumn: ViewColumn.Active
};
await commands.executeCommand('vscode.open', uri, opts);
}
private getWorkingOperations(rootUri: Uri): StoredOperation[] {
return this.memento.get(`${workingOperationsKeyPrefix}${rootUri.toString()}`, []);
}
private async saveWorkingOperations(rootUri: Uri, operations: StoredOperation[] | undefined): Promise<void> {
await this.memento.update(`${workingOperationsKeyPrefix}${rootUri.toString()}`, operations);
}
private async saveWorkingContent(uri: Uri, content: string): Promise<void> {
await this.memento.update(`${workingFileKeyPrefix}${uri.toString()}`, content);
}
private async discardWorkingContent(uri: Uri): Promise<void> {
await this.memento.update(`${workingFileKeyPrefix}${uri.toString()}`, undefined);
}
}
const contextKeyPrefix = 'github.context|';
export class ContextStore<T> {
private _onDidChange = new EventEmitter<Uri>();
get onDidChange(): Event<Uri> {
return this._onDidChange.event;
}
constructor(private readonly memento: Memento, private readonly scheme: string) { }
delete(uri: Uri) {
return this.set(uri, undefined);
}
get(uri: Uri): T | undefined {
return this.memento.get<T>(`${contextKeyPrefix}${uri.toString()}`);
}
async set(uri: Uri, context: T | undefined) {
if (uri.scheme !== this.scheme) {
throw new Error(`Invalid context scheme: ${uri.scheme}`);
}
await this.memento.update(`${contextKeyPrefix}${uri.toString()}`, context);
this._onDidChange.fire(uri);
}
}

View file

@ -1,11 +1,12 @@
{
"extends": "../shared.tsconfig.json",
"compilerOptions": {
"outDir": "./out",
// "experimentalDecorators": true,
// "typeRoots": [
// "./node_modules/@types"
// ]
"experimentalDecorators": true,
"lib": [
"es2018",
"dom"
],
"outDir": "./out"
},
"include": [
"src/**/*"

File diff suppressed because it is too large Load diff

View file

@ -27,21 +27,15 @@ function getAgent(url: string | undefined = process.env.HTTPS_PROXY): Agent {
const scopes = ['repo', 'workflow'];
export async function getSession(): Promise<AuthenticationSession> {
const authenticationSessions = await authentication.getSessions('github', scopes);
if (authenticationSessions.length) {
return await authenticationSessions[0];
} else {
return await authentication.login('github', scopes);
}
return await authentication.getSession('github', scopes, { createIfNone: true });
}
let _octokit: Promise<Octokit> | undefined;
export function getOctokit(): Promise<Octokit> {
if (!_octokit) {
_octokit = getSession().then(async session => {
const token = await session.getAccessToken();
_octokit = getSession().then(session => {
const token = session.accessToken;
const agent = getAgent();
return new Octokit({

View file

@ -17,7 +17,7 @@ class GitHubCredentialProvider implements CredentialsProvider {
}
const session = await getSession();
return { username: session.account.id, password: await session.getAccessToken() };
return { username: session.account.id, password: session.accessToken };
}
}

View file

@ -11,9 +11,15 @@ import { GithubCredentialProviderManager } from './credentialProvider';
export async function activate(context: vscode.ExtensionContext) {
const gitExtension = vscode.extensions.getExtension<GitExtension>('vscode.git')!.exports;
const gitAPI = gitExtension.getAPI(1);
context.subscriptions.push(...registerCommands(gitAPI));
context.subscriptions.push(gitAPI.registerRemoteSourceProvider(new GithubRemoteSourceProvider(gitAPI)));
context.subscriptions.push(new GithubCredentialProviderManager(gitAPI));
try {
const gitAPI = gitExtension.getAPI(1);
context.subscriptions.push(...registerCommands(gitAPI));
context.subscriptions.push(gitAPI.registerRemoteSourceProvider(new GithubRemoteSourceProvider(gitAPI)));
context.subscriptions.push(new GithubCredentialProviderManager(gitAPI));
} catch (err) {
console.error('Could not initialize GitHub extension');
console.warn(err);
}
}

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