mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-20 01:25:37 -05:00
Merge VS Code 1.31.1 (#4283)
This commit is contained in:
@@ -87,9 +87,12 @@ export class IssueReporter extends Disposable {
|
||||
extensionsDisabled: !!this.environmentService.disableExtensions,
|
||||
});
|
||||
|
||||
this.previewButton = new Button(document.getElementById('issue-reporter'));
|
||||
const issueReporterElement = this.getElementById('issue-reporter');
|
||||
if (issueReporterElement) {
|
||||
this.previewButton = new Button(issueReporterElement);
|
||||
}
|
||||
|
||||
ipcRenderer.on('vscode:issuePerformanceInfoResponse', (event, info) => {
|
||||
ipcRenderer.on('vscode:issuePerformanceInfoResponse', (_, info) => {
|
||||
this.logService.trace('issueReporter: Received performance data');
|
||||
this.issueReporterModel.update(info);
|
||||
this.receivedPerformanceInfo = true;
|
||||
@@ -100,7 +103,7 @@ export class IssueReporter extends Disposable {
|
||||
this.updatePreviewButtonState();
|
||||
});
|
||||
|
||||
ipcRenderer.on('vscode:issueSystemInfoResponse', (event, info) => {
|
||||
ipcRenderer.on('vscode:issueSystemInfoResponse', (_, info) => {
|
||||
this.logService.trace('issueReporter: Received system data');
|
||||
this.issueReporterModel.update({ systemInfo: info });
|
||||
this.receivedSystemInfo = true;
|
||||
@@ -116,7 +119,7 @@ export class IssueReporter extends Disposable {
|
||||
this.logService.trace('issueReporter: Sent data requests');
|
||||
|
||||
if (window.document.documentElement.lang !== 'en') {
|
||||
show(document.getElementById('english'));
|
||||
show(this.getElementById('english'));
|
||||
}
|
||||
|
||||
this.setUpTypes();
|
||||
@@ -208,7 +211,7 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
styleTag.innerHTML = content.join('\n');
|
||||
document.head.appendChild(styleTag);
|
||||
document.body.style.color = styles.color;
|
||||
document.body.style.color = styles.color || null;
|
||||
}
|
||||
|
||||
private handleExtensionData(extensions: IssueReporterExtensionData[]) {
|
||||
@@ -221,7 +224,7 @@ export class IssueReporter extends Disposable {
|
||||
this.updateExtensionTable(nonThemes, numberOfThemeExtesions);
|
||||
|
||||
if (this.environmentService.disableExtensions || extensions.length === 0) {
|
||||
(<HTMLButtonElement>document.getElementById('disableExtensions')).disabled = true;
|
||||
(<HTMLButtonElement>this.getElementById('disableExtensions')).disabled = true;
|
||||
}
|
||||
|
||||
this.updateExtensionSelector(extensions);
|
||||
@@ -239,32 +242,33 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
private updateSettingsSearchDetails(data: ISettingsSearchIssueReporterData): void {
|
||||
const target = document.querySelector('.block-settingsSearchResults .block-info');
|
||||
|
||||
const details = `
|
||||
if (target) {
|
||||
const details = `
|
||||
<div class='block-settingsSearchResults-details'>
|
||||
<div>Query: "${data.query}"</div>
|
||||
<div>Literal match count: ${data.filterResultCount}</div>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
|
||||
let table = `
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Extension</th>
|
||||
<th>Score</th>
|
||||
</tr>`;
|
||||
let table = `
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Extension</th>
|
||||
<th>Score</th>
|
||||
</tr>`;
|
||||
|
||||
data.actualSearchResults
|
||||
.forEach(setting => {
|
||||
table += `
|
||||
<tr>
|
||||
<td>${setting.key}</td>
|
||||
<td>${setting.extensionId}</td>
|
||||
<td>${String(setting.score).slice(0, 5)}</td>
|
||||
</tr>`;
|
||||
});
|
||||
data.actualSearchResults
|
||||
.forEach(setting => {
|
||||
table += `
|
||||
<tr>
|
||||
<td>${setting.key}</td>
|
||||
<td>${setting.extensionId}</td>
|
||||
<td>${String(setting.score).slice(0, 5)}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
target.innerHTML = `${details}<table>${table}</table>`;
|
||||
target.innerHTML = `${details}<table>${table}</table>`;
|
||||
}
|
||||
}
|
||||
|
||||
private initServices(configuration: IWindowConfiguration): void {
|
||||
@@ -286,7 +290,7 @@ export class IssueReporter extends Disposable {
|
||||
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
const channel = getDelayedChannel(sharedProcess.then(c => c.getChannel('telemetryAppender')));
|
||||
const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService));
|
||||
const commonProperties = resolveCommonProperties(product.commit, pkg.version, configuration.machineId, this.environmentService.installSourcePath);
|
||||
const commonProperties = resolveCommonProperties(product.commit || 'Commit unknown', pkg.version, configuration.machineId, this.environmentService.installSourcePath);
|
||||
const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath];
|
||||
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths };
|
||||
|
||||
@@ -320,27 +324,29 @@ export class IssueReporter extends Disposable {
|
||||
const showInfoElements = document.getElementsByClassName('showInfo');
|
||||
for (let i = 0; i < showInfoElements.length; i++) {
|
||||
const showInfo = showInfoElements.item(i);
|
||||
showInfo.addEventListener('click', (e) => {
|
||||
showInfo!.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const label = (<HTMLDivElement>e.target);
|
||||
const containingElement = label.parentElement.parentElement;
|
||||
const info = containingElement.lastElementChild;
|
||||
if (info.classList.contains('hidden')) {
|
||||
show(info);
|
||||
label.textContent = localize('hide', "hide");
|
||||
} else {
|
||||
hide(info);
|
||||
label.textContent = localize('show', "show");
|
||||
if (label) {
|
||||
const containingElement = label.parentElement && label.parentElement.parentElement;
|
||||
const info = containingElement && containingElement.lastElementChild;
|
||||
if (info && info.classList.contains('hidden')) {
|
||||
show(info);
|
||||
label.textContent = localize('hide', "hide");
|
||||
} else {
|
||||
hide(info);
|
||||
label.textContent = localize('show', "show");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.addEventListener('issue-source', 'change', (event: Event) => {
|
||||
const fileOnExtension = JSON.parse((<HTMLInputElement>event.target).value);
|
||||
this.addEventListener('issue-source', 'change', (e: Event) => {
|
||||
const fileOnExtension = JSON.parse((<HTMLInputElement>e.target).value);
|
||||
this.issueReporterModel.update({ fileOnExtension: fileOnExtension, includeExtensions: !fileOnExtension });
|
||||
this.render();
|
||||
|
||||
const title = (<HTMLInputElement>document.getElementById('issue-title')).value;
|
||||
const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
|
||||
if (fileOnExtension) {
|
||||
this.searchExtensionIssues(title);
|
||||
} else {
|
||||
@@ -349,20 +355,20 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener('description', 'input', (event: Event) => {
|
||||
const issueDescription = (<HTMLInputElement>event.target).value;
|
||||
this.addEventListener('description', 'input', (e: Event) => {
|
||||
const issueDescription = (<HTMLInputElement>e.target).value;
|
||||
this.issueReporterModel.update({ issueDescription });
|
||||
|
||||
// Only search for extension issues on title change
|
||||
if (!this.issueReporterModel.fileOnExtension()) {
|
||||
const title = (<HTMLInputElement>document.getElementById('issue-title')).value;
|
||||
const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
|
||||
this.searchVSCodeIssues(title, issueDescription);
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener('issue-title', 'input', (e) => {
|
||||
const title = (<HTMLInputElement>event.target).value;
|
||||
const lengthValidationMessage = document.getElementById('issue-title-length-validation-error');
|
||||
this.addEventListener('issue-title', 'input', (e: Event) => {
|
||||
const title = (<HTMLInputElement>e.target).value;
|
||||
const lengthValidationMessage = this.getElementById('issue-title-length-validation-error');
|
||||
if (title && this.getIssueUrlWithTitle(title).length > MAX_URL_LENGTH) {
|
||||
show(lengthValidationMessage);
|
||||
} else {
|
||||
@@ -409,7 +415,7 @@ export class IssueReporter extends Disposable {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
const issueTitle = (<HTMLInputElement>document.getElementById('issue-title'))!.value;
|
||||
const issueTitle = (<HTMLInputElement>this.getElementById('issue-title'))!.value;
|
||||
const { issueDescription } = this.issueReporterModel.getData();
|
||||
if (!this.hasBeenSubmitted && (issueTitle || issueDescription)) {
|
||||
ipcRenderer.send('vscode:issueReporterConfirmClose');
|
||||
@@ -471,17 +477,17 @@ export class IssueReporter extends Disposable {
|
||||
return false;
|
||||
}
|
||||
|
||||
private getExtensionRepositoryUrl(): string {
|
||||
private getExtensionRepositoryUrl(): string | undefined {
|
||||
const selectedExtension = this.issueReporterModel.getData().selectedExtension;
|
||||
return selectedExtension && selectedExtension.repositoryUrl;
|
||||
}
|
||||
|
||||
private getExtensionBugsUrl(): string {
|
||||
private getExtensionBugsUrl(): string | undefined {
|
||||
const selectedExtension = this.issueReporterModel.getData().selectedExtension;
|
||||
return selectedExtension && selectedExtension.bugsUrl;
|
||||
}
|
||||
|
||||
private searchVSCodeIssues(title: string, issueDescription: string): void {
|
||||
private searchVSCodeIssues(title: string, issueDescription?: string): void {
|
||||
if (title) {
|
||||
this.searchDuplicates(title, issueDescription);
|
||||
} else {
|
||||
@@ -510,7 +516,7 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
|
||||
private clearSearchResults(): void {
|
||||
const similarIssues = document.getElementById('similar-issues');
|
||||
const similarIssues = this.getElementById('similar-issues')!;
|
||||
similarIssues.innerHTML = '';
|
||||
this.numberOfSearchResultsDisplayed = 0;
|
||||
}
|
||||
@@ -518,7 +524,7 @@ export class IssueReporter extends Disposable {
|
||||
@debounce(300)
|
||||
private searchGitHub(repo: string, title: string): void {
|
||||
const query = `is:issue+repo:${repo}+${title}`;
|
||||
const similarIssues = document.getElementById('similar-issues');
|
||||
const similarIssues = this.getElementById('similar-issues')!;
|
||||
|
||||
window.fetch(`https://api.github.com/search/issues?q=${query}`).then((response) => {
|
||||
response.json().then(result => {
|
||||
@@ -532,7 +538,7 @@ export class IssueReporter extends Disposable {
|
||||
similarIssues.appendChild(message);
|
||||
|
||||
const resetTime = response.headers.get('X-RateLimit-Reset');
|
||||
const timeToWait = parseInt(resetTime) - Math.floor(Date.now() / 1000);
|
||||
const timeToWait = resetTime ? parseInt(resetTime) - Math.floor(Date.now() / 1000) : 1;
|
||||
if (this.shouldQueueSearch) {
|
||||
this.shouldQueueSearch = false;
|
||||
setTimeout(() => {
|
||||
@@ -550,7 +556,7 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
|
||||
@debounce(300)
|
||||
private searchDuplicates(title: string, body: string): void {
|
||||
private searchDuplicates(title: string, body?: string): void {
|
||||
const url = 'https://vscode-probot.westus.cloudapp.azure.com:7890/duplicate_candidates';
|
||||
const init = {
|
||||
method: 'POST',
|
||||
@@ -582,7 +588,7 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
|
||||
private displaySearchResults(results: SearchResult[]) {
|
||||
const similarIssues = document.getElementById('similar-issues');
|
||||
const similarIssues = this.getElementById('similar-issues')!;
|
||||
if (results.length) {
|
||||
const issues = $('div.issues-container');
|
||||
const issuesText = $('div.list-title');
|
||||
@@ -598,6 +604,7 @@ export class IssueReporter extends Disposable {
|
||||
link.addEventListener('auxclick', (e) => this.openLink(<MouseEvent>e));
|
||||
|
||||
let issueState: HTMLElement;
|
||||
let item: HTMLElement;
|
||||
if (issue.state) {
|
||||
issueState = $('span.issue-state');
|
||||
|
||||
@@ -611,9 +618,12 @@ export class IssueReporter extends Disposable {
|
||||
issueState.title = issue.state === 'open' ? localize('open', "Open") : localize('closed', "Closed");
|
||||
issueState.appendChild(issueIcon);
|
||||
issueState.appendChild(issueStateLabel);
|
||||
|
||||
item = $('div.issue', {}, issueState, link);
|
||||
} else {
|
||||
item = $('div.issue', {}, link);
|
||||
}
|
||||
|
||||
const item = $('div.issue', {}, issueState, link);
|
||||
issues.appendChild(item);
|
||||
}
|
||||
|
||||
@@ -639,7 +649,7 @@ export class IssueReporter extends Disposable {
|
||||
private setUpTypes(): void {
|
||||
const makeOption = (issueType: IssueType, description: string) => `<option value="${issueType.valueOf()}">${escape(description)}</option>`;
|
||||
|
||||
const typeSelect = (<HTMLSelectElement>document.getElementById('issue-type'));
|
||||
const typeSelect = this.getElementById('issue-type')! as HTMLSelectElement;
|
||||
const { issueType } = this.issueReporterModel.getData();
|
||||
if (issueType === IssueType.SettingsSearchIssue) {
|
||||
typeSelect.innerHTML = makeOption(IssueType.SettingsSearchIssue, localize('settingsSearchIssue', "Settings Search Issue"));
|
||||
@@ -658,7 +668,7 @@ export class IssueReporter extends Disposable {
|
||||
private renderBlocks(): void {
|
||||
// Depending on Issue Type, we render different blocks and text
|
||||
const { issueType, fileOnExtension } = this.issueReporterModel.getData();
|
||||
const blockContainer = document.getElementById('block-container');
|
||||
const blockContainer = this.getElementById('block-container');
|
||||
const systemBlock = document.querySelector('.block-system');
|
||||
const processBlock = document.querySelector('.block-process');
|
||||
const workspaceBlock = document.querySelector('.block-workspace');
|
||||
@@ -666,11 +676,11 @@ export class IssueReporter extends Disposable {
|
||||
const searchedExtensionsBlock = document.querySelector('.block-searchedExtensions');
|
||||
const settingsSearchResultsBlock = document.querySelector('.block-settingsSearchResults');
|
||||
|
||||
const problemSource = document.getElementById('problem-source');
|
||||
const problemSourceHelpText = document.getElementById('problem-source-help-text');
|
||||
const descriptionTitle = document.getElementById('issue-description-label');
|
||||
const descriptionSubtitle = document.getElementById('issue-description-subtitle');
|
||||
const extensionSelector = document.getElementById('extension-selection');
|
||||
const problemSource = this.getElementById('problem-source')!;
|
||||
const problemSourceHelpText = this.getElementById('problem-source-help-text')!;
|
||||
const descriptionTitle = this.getElementById('issue-description-label')!;
|
||||
const descriptionSubtitle = this.getElementById('issue-description-subtitle')!;
|
||||
const extensionSelector = this.getElementById('extension-selection')!;
|
||||
|
||||
// Hide all by default
|
||||
hide(blockContainer);
|
||||
@@ -733,7 +743,7 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
|
||||
private validateInput(inputId: string): boolean {
|
||||
const inputElement = (<HTMLInputElement>document.getElementById(inputId));
|
||||
const inputElement = (<HTMLInputElement>this.getElementById(inputId));
|
||||
if (!inputElement.value) {
|
||||
inputElement.classList.add('invalid-input');
|
||||
return false;
|
||||
@@ -765,16 +775,16 @@ export class IssueReporter extends Disposable {
|
||||
(<HTMLInputElement>invalidInput[0]).focus();
|
||||
}
|
||||
|
||||
document.getElementById('issue-title').addEventListener('input', (event) => {
|
||||
this.addEventListener('issue-title', 'input', _ => {
|
||||
this.validateInput('issue-title');
|
||||
});
|
||||
|
||||
document.getElementById('description').addEventListener('input', (event) => {
|
||||
this.addEventListener('description', 'input', _ => {
|
||||
this.validateInput('description');
|
||||
});
|
||||
|
||||
if (this.issueReporterModel.fileOnExtension()) {
|
||||
document.getElementById('extension-selector').addEventListener('change', (event) => {
|
||||
this.addEventListener('extension-selector', 'change', _ => {
|
||||
this.validateInput('extension-selector');
|
||||
});
|
||||
}
|
||||
@@ -791,7 +801,7 @@ export class IssueReporter extends Disposable {
|
||||
this.telemetryService.publicLog('issueReporterSubmit', { issueType: this.issueReporterModel.getData().issueType, numSimilarIssuesDisplayed: this.numberOfSearchResultsDisplayed });
|
||||
this.hasBeenSubmitted = true;
|
||||
|
||||
const baseUrl = this.getIssueUrlWithTitle((<HTMLInputElement>document.getElementById('issue-title')).value);
|
||||
const baseUrl = this.getIssueUrlWithTitle((<HTMLInputElement>this.getElementById('issue-title')).value);
|
||||
const issueBody = this.issueReporterModel.serialize();
|
||||
let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`;
|
||||
|
||||
@@ -834,19 +844,21 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
private updateSystemInfo = (state) => {
|
||||
const target = document.querySelector('.block-system .block-info');
|
||||
let tableHtml = '';
|
||||
Object.keys(state.systemInfo).forEach(k => {
|
||||
const data = typeof state.systemInfo[k] === 'object'
|
||||
? Object.keys(state.systemInfo[k]).map(key => `${key}: ${state.systemInfo[k][key]}`).join('<br>')
|
||||
: state.systemInfo[k];
|
||||
if (target) {
|
||||
let tableHtml = '';
|
||||
Object.keys(state.systemInfo).forEach(k => {
|
||||
const data = typeof state.systemInfo[k] === 'object'
|
||||
? Object.keys(state.systemInfo[k]).map(key => `${key}: ${state.systemInfo[k][key]}`).join('<br>')
|
||||
: state.systemInfo[k];
|
||||
|
||||
tableHtml += `
|
||||
<tr>
|
||||
<td>${k}</td>
|
||||
<td>${data}</td>
|
||||
</tr>`;
|
||||
});
|
||||
target.innerHTML = `<table>${tableHtml}</table>`;
|
||||
tableHtml += `
|
||||
<tr>
|
||||
<td>${k}</td>
|
||||
<td>${data}</td>
|
||||
</tr>`;
|
||||
});
|
||||
target.innerHTML = `<table>${tableHtml}</table>`;
|
||||
}
|
||||
}
|
||||
|
||||
private updateExtensionSelector(extensions: IssueReporterExtensionData[]): void {
|
||||
@@ -878,64 +890,70 @@ export class IssueReporter extends Disposable {
|
||||
});
|
||||
|
||||
const makeOption = (extension: IOption) => `<option value="${extension.id}">${escape(extension.name)}</option>`;
|
||||
const extensionsSelector = document.getElementById('extension-selector');
|
||||
extensionsSelector.innerHTML = '<option></option>' + extensionOptions.map(makeOption).join('\n');
|
||||
const extensionsSelector = this.getElementById('extension-selector');
|
||||
if (extensionsSelector) {
|
||||
extensionsSelector.innerHTML = '<option></option>' + extensionOptions.map(makeOption).join('\n');
|
||||
|
||||
this.addEventListener('extension-selector', 'change', (e: Event) => {
|
||||
const selectedExtensionId = (<HTMLInputElement>e.target).value;
|
||||
const extensions = this.issueReporterModel.getData().allExtensions;
|
||||
const matches = extensions.filter(extension => extension.id === selectedExtensionId);
|
||||
if (matches.length) {
|
||||
this.issueReporterModel.update({ selectedExtension: matches[0] });
|
||||
this.addEventListener('extension-selector', 'change', (e: Event) => {
|
||||
const selectedExtensionId = (<HTMLInputElement>e.target).value;
|
||||
const extensions = this.issueReporterModel.getData().allExtensions;
|
||||
const matches = extensions.filter(extension => extension.id === selectedExtensionId);
|
||||
if (matches.length) {
|
||||
this.issueReporterModel.update({ selectedExtension: matches[0] });
|
||||
|
||||
const title = (<HTMLInputElement>document.getElementById('issue-title')).value;
|
||||
this.searchExtensionIssues(title);
|
||||
} else {
|
||||
this.issueReporterModel.update({ selectedExtension: null });
|
||||
this.clearSearchResults();
|
||||
}
|
||||
});
|
||||
const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
|
||||
this.searchExtensionIssues(title);
|
||||
} else {
|
||||
this.issueReporterModel.update({ selectedExtension: undefined });
|
||||
this.clearSearchResults();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private updateProcessInfo = (state) => {
|
||||
const target = document.querySelector('.block-process .block-info');
|
||||
target.innerHTML = `<code>${state.processInfo}</code>`;
|
||||
if (target) {
|
||||
target.innerHTML = `<code>${state.processInfo}</code>`;
|
||||
}
|
||||
}
|
||||
|
||||
private updateWorkspaceInfo = (state) => {
|
||||
document.querySelector('.block-workspace .block-info code').textContent = '\n' + state.workspaceInfo;
|
||||
document.querySelector('.block-workspace .block-info code')!.textContent = '\n' + state.workspaceInfo;
|
||||
}
|
||||
|
||||
private updateExtensionTable(extensions: IssueReporterExtensionData[], numThemeExtensions: number): void {
|
||||
const target = document.querySelector('.block-extensions .block-info');
|
||||
if (target) {
|
||||
if (this.environmentService.disableExtensions) {
|
||||
target.innerHTML = localize('disabledExtensions', "Extensions are disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.environmentService.disableExtensions) {
|
||||
target.innerHTML = localize('disabledExtensions', "Extensions are disabled");
|
||||
return;
|
||||
const themeExclusionStr = numThemeExtensions ? `\n(${numThemeExtensions} theme extensions excluded)` : '';
|
||||
extensions = extensions || [];
|
||||
|
||||
if (!extensions.length) {
|
||||
target.innerHTML = 'Extensions: none' + themeExclusionStr;
|
||||
return;
|
||||
}
|
||||
|
||||
const table = this.getExtensionTableHtml(extensions);
|
||||
target.innerHTML = `<table>${table}</table>${themeExclusionStr}`;
|
||||
}
|
||||
|
||||
const themeExclusionStr = numThemeExtensions ? `\n(${numThemeExtensions} theme extensions excluded)` : '';
|
||||
extensions = extensions || [];
|
||||
|
||||
if (!extensions.length) {
|
||||
target.innerHTML = 'Extensions: none' + themeExclusionStr;
|
||||
return;
|
||||
}
|
||||
|
||||
const table = this.getExtensionTableHtml(extensions);
|
||||
target.innerHTML = `<table>${table}</table>${themeExclusionStr}`;
|
||||
}
|
||||
|
||||
private updateSearchedExtensionTable(extensions: IssueReporterExtensionData[]): void {
|
||||
const target = document.querySelector('.block-searchedExtensions .block-info');
|
||||
if (target) {
|
||||
if (!extensions.length) {
|
||||
target.innerHTML = 'Extensions: none';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!extensions.length) {
|
||||
target.innerHTML = 'Extensions: none';
|
||||
return;
|
||||
const table = this.getExtensionTableHtml(extensions);
|
||||
target.innerHTML = `<table>${table}</table>`;
|
||||
}
|
||||
|
||||
const table = this.getExtensionTableHtml(extensions);
|
||||
target.innerHTML = `<table>${table}</table>`;
|
||||
}
|
||||
|
||||
private getExtensionTableHtml(extensions: IssueReporterExtensionData[]): string {
|
||||
@@ -972,19 +990,28 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private addEventListener(elementId: string, eventType: string, handler: (event: Event) => void): void {
|
||||
private getElementById(elementId: string): HTMLElement | undefined {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element) {
|
||||
element.addEventListener(eventType, handler);
|
||||
return element;
|
||||
} else {
|
||||
const error = new Error(`${elementId} not found.`);
|
||||
this.logService.error(error);
|
||||
/* __GDPR__
|
||||
"issueReporterAddEventListenerError" : {
|
||||
"issueReporterGetElementError" : {
|
||||
"message" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('issueReporterAddEventListenerError', { message: error.message });
|
||||
this.telemetryService.publicLog('issueReporterGetElementError', { message: error.message });
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private addEventListener(elementId: string, eventType: string, handler: (event: Event) => void): void {
|
||||
const element = this.getElementById(elementId);
|
||||
if (element) {
|
||||
element.addEventListener(eventType, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { assign } from 'vs/base/common/objects';
|
||||
import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/common/issue';
|
||||
|
||||
export interface IssueReporterData {
|
||||
issueType?: IssueType;
|
||||
issueType: IssueType;
|
||||
issueDescription?: string;
|
||||
|
||||
versionInfo?: any;
|
||||
@@ -15,15 +15,15 @@ export interface IssueReporterData {
|
||||
processInfo?: any;
|
||||
workspaceInfo?: any;
|
||||
|
||||
includeSystemInfo?: boolean;
|
||||
includeWorkspaceInfo?: boolean;
|
||||
includeProcessInfo?: boolean;
|
||||
includeExtensions?: boolean;
|
||||
includeSearchedExtensions?: boolean;
|
||||
includeSettingsSearchDetails?: boolean;
|
||||
includeSystemInfo: boolean;
|
||||
includeWorkspaceInfo: boolean;
|
||||
includeProcessInfo: boolean;
|
||||
includeExtensions: boolean;
|
||||
includeSearchedExtensions: boolean;
|
||||
includeSettingsSearchDetails: boolean;
|
||||
|
||||
numberOfThemeExtesions?: number;
|
||||
allExtensions?: IssueReporterExtensionData[];
|
||||
allExtensions: IssueReporterExtensionData[];
|
||||
enabledNonThemeExtesions?: IssueReporterExtensionData[];
|
||||
extensionsDisabled?: boolean;
|
||||
fileOnExtension?: boolean;
|
||||
@@ -36,14 +36,16 @@ export interface IssueReporterData {
|
||||
export class IssueReporterModel {
|
||||
private _data: IssueReporterData;
|
||||
|
||||
constructor(initialData?: IssueReporterData) {
|
||||
constructor(initialData?: Partial<IssueReporterData>) {
|
||||
const defaultData = {
|
||||
issueType: IssueType.Bug,
|
||||
includeSystemInfo: true,
|
||||
includeWorkspaceInfo: true,
|
||||
includeProcessInfo: true,
|
||||
includeExtensions: true,
|
||||
includeSearchedExtensions: true,
|
||||
includeSettingsSearchDetails: true
|
||||
includeSettingsSearchDetails: true,
|
||||
allExtensions: []
|
||||
};
|
||||
|
||||
this._data = initialData ? assign(defaultData, initialData) : defaultData;
|
||||
@@ -53,7 +55,7 @@ export class IssueReporterModel {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
update(newData: IssueReporterData): void {
|
||||
update(newData: Partial<IssueReporterData>): void {
|
||||
assign(this._data, newData);
|
||||
}
|
||||
|
||||
@@ -103,7 +105,7 @@ ${this.getInfos()}
|
||||
let info = '';
|
||||
|
||||
if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) {
|
||||
if (this._data.includeSystemInfo) {
|
||||
if (this._data.includeSystemInfo && this._data.systemInfo) {
|
||||
info += this.generateSystemInfoMd();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,9 +64,9 @@ export default (): string => `
|
||||
<div class="block block-system">
|
||||
<input class="sendData" type="checkbox" id="includeSystemInfo" checked/>
|
||||
<label class="caption" for="includeSystemInfo">${escape(localize({
|
||||
key: 'sendSystemInfo',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the system information']
|
||||
}, "Include my system information ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
key: 'sendSystemInfo',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the system information']
|
||||
}, "Include my system information ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<div class="block-info hidden">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
@@ -74,9 +74,9 @@ export default (): string => `
|
||||
<div class="block block-process">
|
||||
<input class="sendData" type="checkbox" id="includeProcessInfo" checked/>
|
||||
<label class="caption" for="includeProcessInfo">${escape(localize({
|
||||
key: 'sendProcessInfo',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the process info']
|
||||
}, "Include my currently running processes ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
key: 'sendProcessInfo',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the process info']
|
||||
}, "Include my currently running processes ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<pre class="block-info hidden">
|
||||
<code>
|
||||
<!-- To be dynamically filled -->
|
||||
@@ -86,9 +86,9 @@ export default (): string => `
|
||||
<div class="block block-workspace">
|
||||
<input class="sendData" type="checkbox" id="includeWorkspaceInfo" checked/>
|
||||
<label class="caption" for="includeWorkspaceInfo">${escape(localize({
|
||||
key: 'sendWorkspaceInfo',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the workspace information']
|
||||
}, "Include my workspace metadata ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
key: 'sendWorkspaceInfo',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the workspace information']
|
||||
}, "Include my workspace metadata ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<pre id="systemInfo" class="block-info hidden">
|
||||
<code>
|
||||
<!-- To be dynamically filled -->
|
||||
@@ -98,9 +98,9 @@ export default (): string => `
|
||||
<div class="block block-extensions">
|
||||
<input class="sendData" type="checkbox" id="includeExtensions" checked/>
|
||||
<label class="caption" for="includeExtensions">${escape(localize({
|
||||
key: 'sendExtensions',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the enabled extensions list']
|
||||
}, "Include my enabled extensions ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
key: 'sendExtensions',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the enabled extensions list']
|
||||
}, "Include my enabled extensions ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<div id="systemInfo" class="block-info hidden">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
@@ -108,9 +108,9 @@ export default (): string => `
|
||||
<div class="block block-searchedExtensions">
|
||||
<input class="sendData" type="checkbox" id="includeSearchedExtensions" checked/>
|
||||
<label class="caption" for="includeSearchedExtensions">${escape(localize({
|
||||
key: 'sendSearchedExtensions',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the searched extensions']
|
||||
}, "Send searched extensions ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
key: 'sendSearchedExtensions',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the searched extensions']
|
||||
}, "Send searched extensions ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<div class="block-info hidden">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
@@ -118,9 +118,9 @@ export default (): string => `
|
||||
<div class="block block-settingsSearchResults">
|
||||
<input class="sendData" type="checkbox" id="includeSettingsSearchDetails" checked/>
|
||||
<label class="caption" for="includeSettingsSearchDetails">${escape(localize({
|
||||
key: 'sendSettingsSearchDetails',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the search details']
|
||||
}, "Send settings search details ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
key: 'sendSettingsSearchDetails',
|
||||
comment: ['{0} is either "show" or "hide" and is a button to toggle the visibililty of the search details']
|
||||
}, "Send settings search details ({0})")).replace('{0}', `<a href="#" class="showInfo">${escape(localize('show', "show"))}</a>`)}</label>
|
||||
<div class="block-info hidden">
|
||||
<!-- To be dynamically filled -->
|
||||
</div>
|
||||
|
||||
@@ -86,16 +86,32 @@ select {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif;
|
||||
textarea, input, select {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
color: #CCCCCC;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html:lang(zh-Hans) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif;
|
||||
}
|
||||
|
||||
html:lang(zh-Hant) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif;
|
||||
}
|
||||
|
||||
html:lang(ja) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Meiryo", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
|
||||
}
|
||||
|
||||
html:lang(ko) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
overflow-y: scroll;
|
||||
@@ -112,7 +128,6 @@ body {
|
||||
|
||||
.block .block-info {
|
||||
width: 100%;
|
||||
font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";
|
||||
font-size: 12px;
|
||||
overflow: auto;
|
||||
overflow-wrap: break-word;
|
||||
@@ -120,10 +135,6 @@ body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
pre code {
|
||||
font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";
|
||||
}
|
||||
|
||||
#issue-reporter {
|
||||
max-width: 85vw;
|
||||
margin-left: auto;
|
||||
|
||||
@@ -13,28 +13,30 @@ suite('IssueReporter', () => {
|
||||
test('sets defaults to include all data', () => {
|
||||
const issueReporterModel = new IssueReporterModel();
|
||||
assert.deepEqual(issueReporterModel.getData(), {
|
||||
allExtensions: [],
|
||||
includeSystemInfo: true,
|
||||
includeWorkspaceInfo: true,
|
||||
includeProcessInfo: true,
|
||||
includeExtensions: true,
|
||||
includeSearchedExtensions: true,
|
||||
includeSettingsSearchDetails: true
|
||||
includeSettingsSearchDetails: true,
|
||||
issueType: 0
|
||||
});
|
||||
});
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
test('serializes model skeleton when no data is provided', () => {
|
||||
const issueReporterModel = new IssueReporterModel();
|
||||
const issueReporterModel = new IssueReporterModel({});
|
||||
assert.equal(issueReporterModel.serialize(),
|
||||
`
|
||||
Issue Type: <b>Feature Request</b>
|
||||
Issue Type: <b>Bug</b>
|
||||
|
||||
undefined
|
||||
|
||||
Azure Data Studio version: undefined
|
||||
OS version: undefined
|
||||
|
||||
|
||||
Extensions: none
|
||||
<!-- generated by issue reporter -->`);
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,27 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
html,
|
||||
html {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
html:lang(zh-Hans) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif;
|
||||
}
|
||||
|
||||
html:lang(zh-Hant) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif;
|
||||
}
|
||||
|
||||
html:lang(ja) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Meiryo", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
|
||||
}
|
||||
|
||||
html:lang(ko) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -12,8 +32,6 @@ body {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-size: 13px;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
@@ -38,7 +56,7 @@ table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
th {
|
||||
th[scope='col'] {
|
||||
vertical-align: bottom;
|
||||
border-bottom: 1px solid #cccccc;
|
||||
padding: .5rem;
|
||||
@@ -62,6 +80,9 @@ td {
|
||||
.data {
|
||||
white-space: pre;
|
||||
padding-left: .5rem;
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
tbody > tr:hover {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</head>
|
||||
|
||||
<body aria-label="">
|
||||
<table id="process-list" aria-live="polite"></table>
|
||||
<table id="process-list"></table>
|
||||
</body>
|
||||
|
||||
<!-- Startup via processExplorer.js -->
|
||||
|
||||
@@ -96,7 +96,7 @@ function attachTo(item: ProcessItem) {
|
||||
config.port = parseInt(matches[2]);
|
||||
}
|
||||
|
||||
ipcRenderer.send('vscode:workbenchCommand', { id: 'workbench.action.debug.start', from: 'processExplorer', args: [config] });
|
||||
ipcRenderer.send('vscode:workbenchCommand', { id: 'debug.startFromConfig', from: 'processExplorer', args: [config] });
|
||||
}
|
||||
|
||||
function getProcessIdWithHighestProperty(processList, propertyName: string) {
|
||||
@@ -113,42 +113,56 @@ function getProcessIdWithHighestProperty(processList, propertyName: string) {
|
||||
}
|
||||
|
||||
function updateProcessInfo(processList): void {
|
||||
const target = document.getElementById('process-list');
|
||||
if (!target) {
|
||||
const container = document.getElementById('process-list');
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = '';
|
||||
const highestCPUProcess = getProcessIdWithHighestProperty(processList, 'cpu');
|
||||
const highestMemoryProcess = getProcessIdWithHighestProperty(processList, 'memory');
|
||||
|
||||
let tableHtml = `
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="cpu">${localize('cpu', "CPU %")}</th>
|
||||
<th scope="col" class="memory">${localize('memory', "Memory (MB)")}</th>
|
||||
<th scope="col" class="pid">${localize('pid', "pid")}</th>
|
||||
<th scope="col" class="nameLabel">${localize('name', "Name")}</th>
|
||||
</tr>
|
||||
</thead>`;
|
||||
const tableHead = document.createElement('thead');
|
||||
tableHead.innerHTML = `<tr>
|
||||
<th scope="col" class="cpu">${localize('cpu', "CPU %")}</th>
|
||||
<th scope="col" class="memory">${localize('memory', "Memory (MB)")}</th>
|
||||
<th scope="col" class="pid">${localize('pid', "pid")}</th>
|
||||
<th scope="col" class="nameLabel">${localize('name', "Name")}</th>
|
||||
</tr>`;
|
||||
|
||||
tableHtml += `<tbody>`;
|
||||
const tableBody = document.createElement('tbody');
|
||||
|
||||
processList.forEach(p => {
|
||||
const cpuClass = p.pid === highestCPUProcess ? 'highest' : '';
|
||||
const memoryClass = p.pid === highestMemoryProcess ? 'highest' : '';
|
||||
const row = document.createElement('tr');
|
||||
row.id = p.pid;
|
||||
|
||||
tableHtml += `
|
||||
<tr id=${p.pid}>
|
||||
<td class="centered ${cpuClass}">${p.cpu}</td>
|
||||
<td class="centered ${memoryClass}">${p.memory}</td>
|
||||
<td class="centered">${p.pid}</td>
|
||||
<td title="${p.name}" class="data">${p.formattedName}</td>
|
||||
</tr>`;
|
||||
const cpu = document.createElement('td');
|
||||
p.pid === highestCPUProcess
|
||||
? cpu.classList.add('centered', 'highest')
|
||||
: cpu.classList.add('centered');
|
||||
cpu.textContent = p.cpu;
|
||||
|
||||
const memory = document.createElement('td');
|
||||
p.pid === highestMemoryProcess
|
||||
? memory.classList.add('centered', 'highest')
|
||||
: memory.classList.add('centered');
|
||||
memory.textContent = p.memory;
|
||||
|
||||
const pid = document.createElement('td');
|
||||
pid.classList.add('centered');
|
||||
pid.textContent = p.pid;
|
||||
|
||||
const name = document.createElement('th');
|
||||
name.scope = 'row';
|
||||
name.classList.add('data');
|
||||
name.title = p.cmd;
|
||||
name.textContent = p.formattedName;
|
||||
|
||||
row.append(cpu, memory, pid, name);
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
|
||||
tableHtml += `</tbody>`;
|
||||
|
||||
target.innerHTML = tableHtml;
|
||||
container.append(tableHead, tableBody);
|
||||
}
|
||||
|
||||
function applyStyles(styles: ProcessExplorerStyles): void {
|
||||
@@ -171,7 +185,9 @@ function applyStyles(styles: ProcessExplorerStyles): void {
|
||||
if (document.head) {
|
||||
document.head.appendChild(styleTag);
|
||||
}
|
||||
document.body.style.color = styles.color;
|
||||
if (styles.color) {
|
||||
document.body.style.color = styles.color;
|
||||
}
|
||||
}
|
||||
|
||||
function applyZoom(zoomLevel: number): void {
|
||||
@@ -297,4 +313,4 @@ export function startup(data: ProcessExplorerData): void {
|
||||
applyZoom(webFrame.getZoomLevel() - 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", sans-serif, "Droid Sans Fallback";
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-size: 10pt;
|
||||
background-color: #F3F3F3;
|
||||
}
|
||||
@@ -58,7 +58,7 @@
|
||||
}
|
||||
|
||||
input {
|
||||
font-family: "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", sans-serif, "Droid Sans Fallback" !important;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -8,11 +8,13 @@ import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedPr
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner';
|
||||
import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner';
|
||||
|
||||
export function createSharedProcessContributions(service: IInstantiationService): IDisposable {
|
||||
return combinedDisposable([
|
||||
service.createInstance(NodeCachedDataCleaner),
|
||||
service.createInstance(LanguagePackCachedDataCleaner),
|
||||
service.createInstance(StorageDataCleaner)
|
||||
service.createInstance(StorageDataCleaner),
|
||||
service.createInstance(LogsDataCleaner)
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ export class LanguagePackCachedDataCleaner {
|
||||
|
||||
this._disposables.push({
|
||||
dispose() {
|
||||
if (handle !== void 0) {
|
||||
if (handle !== undefined) {
|
||||
clearTimeout(handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { join, dirname, basename } from 'path';
|
||||
import { readdir, rimraf } from 'vs/base/node/pfs';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class LogsDataCleaner extends Disposable {
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.cleanUpOldLogsSoon();
|
||||
}
|
||||
|
||||
private cleanUpOldLogsSoon(): void {
|
||||
let handle: any = setTimeout(() => {
|
||||
handle = undefined;
|
||||
|
||||
const currentLog = basename(this.environmentService.logsPath);
|
||||
const logsRoot = dirname(this.environmentService.logsPath);
|
||||
|
||||
readdir(logsRoot).then(children => {
|
||||
const allSessions = children.filter(name => /^\d{8}T\d{6}$/.test(name));
|
||||
const oldSessions = allSessions.sort().filter((d, i) => d !== currentLog);
|
||||
const toDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 9));
|
||||
|
||||
return Promise.all(toDelete.map(name => rimraf(join(logsRoot, name))));
|
||||
}).then(null, onUnexpectedError);
|
||||
}, 10 * 1000);
|
||||
|
||||
this._register(toDisposable(() => clearTimeout(handle)));
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export class NodeCachedDataCleaner {
|
||||
readdir(nodeCachedDataRootDir).then(entries => {
|
||||
|
||||
const now = Date.now();
|
||||
const deletes: Thenable<any>[] = [];
|
||||
const deletes: Promise<any>[] = [];
|
||||
|
||||
entries.forEach(entry => {
|
||||
// name check
|
||||
|
||||
@@ -16,7 +16,7 @@ export class StorageDataCleaner extends Disposable {
|
||||
private static NON_EMPTY_WORKSPACE_ID_LENGTH = 128 / 4;
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService private environmentService: IEnvironmentService
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -25,7 +25,7 @@ export class StorageDataCleaner extends Disposable {
|
||||
|
||||
private cleanUpStorageSoon(): void {
|
||||
let handle: any = setTimeout(() => {
|
||||
handle = void 0;
|
||||
handle = undefined;
|
||||
|
||||
// Leverage the backup workspace file to find out which empty workspace is currently in use to
|
||||
// determine which empty workspace storage can safely be deleted
|
||||
|
||||
@@ -43,7 +43,6 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { DownloadService } from 'vs/platform/download/node/downloadService';
|
||||
import { IDownloadService } from 'vs/platform/download/common/download';
|
||||
import { StaticRouter } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { DefaultURITransformer } from 'vs/base/common/uriIpc';
|
||||
|
||||
export interface ISharedProcessConfiguration {
|
||||
readonly machineId: string;
|
||||
@@ -124,7 +123,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
|
||||
}
|
||||
server.registerChannel('telemetryAppender', new TelemetryAppenderChannel(appInsightsAppender));
|
||||
|
||||
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
|
||||
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService, [false]));
|
||||
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
|
||||
services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService));
|
||||
|
||||
@@ -133,7 +132,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
|
||||
instantiationService2.invokeFunction(accessor => {
|
||||
|
||||
const extensionManagementService = accessor.get(IExtensionManagementService);
|
||||
const channel = new ExtensionManagementChannel(extensionManagementService, () => DefaultURITransformer);
|
||||
const channel = new ExtensionManagementChannel(extensionManagementService, () => null);
|
||||
server.registerChannel('extensions', channel);
|
||||
|
||||
// clean up deprecated extensions
|
||||
@@ -149,8 +148,8 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
|
||||
});
|
||||
}
|
||||
|
||||
function setupIPC(hook: string): Thenable<Server> {
|
||||
function setup(retry: boolean): Thenable<Server> {
|
||||
function setupIPC(hook: string): Promise<Server> {
|
||||
function setup(retry: boolean): Promise<Server> {
|
||||
return serve(hook).then(null, err => {
|
||||
if (!retry || platform.isWindows || err.code !== 'EADDRINUSE') {
|
||||
return Promise.reject(err);
|
||||
|
||||
@@ -50,7 +50,7 @@ bootstrapWindow.load([
|
||||
showPartsSplash(windowConfig);
|
||||
},
|
||||
beforeLoaderConfig: function (windowConfig, loaderConfig) {
|
||||
loaderConfig.recordStats = !!windowConfig.performance;
|
||||
loaderConfig.recordStats = !!windowConfig['prof-modules'];
|
||||
if (loaderConfig.nodeCachedData) {
|
||||
const onNodeCachedData = window['MonacoEnvironment'].onNodeCachedData = [];
|
||||
loaderConfig.nodeCachedData.onData = function () {
|
||||
@@ -72,15 +72,7 @@ function showPartsSplash(configuration) {
|
||||
|
||||
let data;
|
||||
try {
|
||||
if (!process.env['VSCODE_TEST_STORAGE_MIGRATION']) {
|
||||
// TODO@Ben remove me after a while
|
||||
perf.mark('willReadLocalStorage');
|
||||
let raw = window.localStorage.getItem('storage://global/parts-splash-data');
|
||||
perf.mark('didReadLocalStorage');
|
||||
data = JSON.parse(raw);
|
||||
} else {
|
||||
data = JSON.parse(configuration.partsSplashData);
|
||||
}
|
||||
data = JSON.parse(configuration.partsSplashData);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol } from 'electron';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor } from 'electron';
|
||||
import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform';
|
||||
import { WindowsManager } from 'vs/code/electron-main/windows';
|
||||
import { IWindowsService, OpenContext, ActiveWindowManager } from 'vs/platform/windows/common/windows';
|
||||
import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc';
|
||||
@@ -38,7 +38,6 @@ import pkg from 'vs/platform/node/package';
|
||||
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IHistoryMainService } from 'vs/platform/history/common/history';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
@@ -61,11 +60,10 @@ import { connectRemoteAgentManagement, RemoteAgentConnectionContext } from 'vs/p
|
||||
import { IMenubarService } from 'vs/platform/menubar/common/menubar';
|
||||
import { MenubarService } from 'vs/platform/menubar/electron-main/menubarService';
|
||||
import { MenubarChannel } from 'vs/platform/menubar/node/menubarIpc';
|
||||
import { ILabelService, RegisterFormatterEvent } from 'vs/platform/label/common/label';
|
||||
import { hasArgs } from 'vs/platform/environment/node/argv';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu';
|
||||
import { THEME_STORAGE_KEY, THEME_BG_STORAGE_KEY } from 'vs/code/electron-main/theme';
|
||||
import { storeBackgroundColor } from 'vs/code/electron-main/theme';
|
||||
import { nativeSep, join } from 'vs/base/common/paths';
|
||||
import { homedir } from 'os';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -76,9 +74,12 @@ import { SnapUpdateService } from 'vs/platform/update/electron-main/updateServic
|
||||
import { IStorageMainService, StorageMainService } from 'vs/platform/storage/node/storageMainService';
|
||||
import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { CodeMenu } from 'sql/workbench/electron-browser/menus';
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
|
||||
import { IBackupMainService } from 'vs/platform/backup/common/backup';
|
||||
import { HistoryMainService } from 'vs/platform/history/electron-main/historyMainService';
|
||||
import { URLService } from 'vs/platform/url/common/urlService';
|
||||
import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
|
||||
|
||||
export class CodeApplication extends Disposable {
|
||||
|
||||
@@ -89,19 +90,17 @@ export class CodeApplication extends Disposable {
|
||||
private electronIpcServer: ElectronIPCServer;
|
||||
|
||||
private sharedProcess: SharedProcess;
|
||||
private sharedProcessClient: TPromise<Client>;
|
||||
private sharedProcessClient: Promise<Client>;
|
||||
|
||||
constructor(
|
||||
private mainIpcServer: Server,
|
||||
private userEnv: platform.IProcessEnvironment,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@ILogService private logService: ILogService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@ILifecycleService private lifecycleService: ILifecycleService,
|
||||
@IConfigurationService private configurationService: ConfigurationService,
|
||||
@IStateService private stateService: IStateService,
|
||||
@IHistoryMainService private historyMainService: IHistoryMainService,
|
||||
@ILabelService private labelService: ILabelService
|
||||
private userEnv: IProcessEnvironment,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
||||
@IConfigurationService private readonly configurationService: ConfigurationService,
|
||||
@IStateService private readonly stateService: IStateService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -144,12 +143,27 @@ export class CodeApplication extends Disposable {
|
||||
app.on('web-contents-created', (event: any, contents) => {
|
||||
contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => {
|
||||
|
||||
const isValidWebviewSource = (source: string): boolean => {
|
||||
if (!source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const srcUri = URI.parse(source).fsPath.toLowerCase();
|
||||
const rootUri = URI.file(this.environmentService.appRoot).fsPath.toLowerCase();
|
||||
|
||||
return startsWith(srcUri, rootUri + nativeSep);
|
||||
};
|
||||
|
||||
// Ensure defaults
|
||||
delete webPreferences.preload;
|
||||
webPreferences.nodeIntegration = false;
|
||||
|
||||
// Verify URLs being loaded
|
||||
if (this.isValidWebviewSource(params.src) && this.isValidWebviewSource(webPreferences.preloadURL)) {
|
||||
if (isValidWebviewSource(params.src) && isValidWebviewSource(webPreferences.preloadURL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -174,85 +188,6 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
});
|
||||
|
||||
const connectionPool: Map<string, ActiveConnection> = new Map<string, ActiveConnection>();
|
||||
|
||||
class ActiveConnection {
|
||||
private _authority: string;
|
||||
private _client: TPromise<Client<RemoteAgentConnectionContext>>;
|
||||
private _disposeRunner: RunOnceScheduler;
|
||||
|
||||
constructor(authority: string, host: string, port: number) {
|
||||
this._authority = authority;
|
||||
this._client = connectRemoteAgentManagement(authority, host, port, `main`);
|
||||
this._disposeRunner = new RunOnceScheduler(() => this._dispose(), 5000);
|
||||
}
|
||||
|
||||
private _dispose(): void {
|
||||
this._disposeRunner.dispose();
|
||||
connectionPool.delete(this._authority);
|
||||
this._client.then((connection) => {
|
||||
connection.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
public getClient(): TPromise<Client<RemoteAgentConnectionContext>> {
|
||||
this._disposeRunner.schedule();
|
||||
return this._client;
|
||||
}
|
||||
}
|
||||
|
||||
const resolvedAuthorities = new Map<string, ResolvedAuthority>();
|
||||
ipc.on('vscode:remoteAuthorityResolved', (event: any, data: ResolvedAuthority) => {
|
||||
resolvedAuthorities.set(data.authority, data);
|
||||
});
|
||||
const resolveAuthority = (authority: string): ResolvedAuthority | null => {
|
||||
if (authority.indexOf('+') >= 0) {
|
||||
if (resolvedAuthorities.has(authority)) {
|
||||
return resolvedAuthorities.get(authority);
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
const [host, strPort] = authority.split(':');
|
||||
const port = parseInt(strPort, 10);
|
||||
return { authority, host, port, syncExtensions: false };
|
||||
}
|
||||
};
|
||||
|
||||
protocol.registerBufferProtocol(REMOTE_HOST_SCHEME, async (request, callback) => {
|
||||
if (request.method !== 'GET') {
|
||||
return callback(null);
|
||||
}
|
||||
const uri = URI.parse(request.url);
|
||||
|
||||
let activeConnection: ActiveConnection = null;
|
||||
if (connectionPool.has(uri.authority)) {
|
||||
activeConnection = connectionPool.get(uri.authority);
|
||||
} else {
|
||||
let resolvedAuthority = resolveAuthority(uri.authority);
|
||||
if (!resolvedAuthority) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
activeConnection = new ActiveConnection(uri.authority, resolvedAuthority.host, resolvedAuthority.port);
|
||||
connectionPool.set(uri.authority, activeConnection);
|
||||
}
|
||||
try {
|
||||
const rawClient = await activeConnection.getClient();
|
||||
if (connectionPool.has(uri.authority)) { // not disposed in the meantime
|
||||
const channel = rawClient.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME);
|
||||
|
||||
// TODO@alex don't use call directly, wrap it around a `RemoteExtensionsFileSystemProvider`
|
||||
const fileContents = await channel.call<Uint8Array>('readFile', [uri]);
|
||||
callback(Buffer.from(fileContents));
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
} catch (err) {
|
||||
errors.onUnexpectedError(err);
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
|
||||
let macOpenFileURIs: URI[] = [];
|
||||
let runningTimeout: any = null;
|
||||
app.on('open-file', (event: Event, path: string) => {
|
||||
@@ -321,44 +256,18 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
});
|
||||
|
||||
ipc.on('vscode:labelRegisterFormatter', (event: any, data: RegisterFormatterEvent) => {
|
||||
this.labelService.registerFormatter(data.selector, data.formatter);
|
||||
});
|
||||
ipc.on('vscode:toggleDevTools', (event: Event) => event.sender.toggleDevTools());
|
||||
ipc.on('vscode:openDevTools', (event: Event) => event.sender.openDevTools());
|
||||
|
||||
ipc.on('vscode:toggleDevTools', (event: Event) => {
|
||||
event.sender.toggleDevTools();
|
||||
});
|
||||
ipc.on('vscode:reloadWindow', (event: Event) => event.sender.reload());
|
||||
|
||||
ipc.on('vscode:openDevTools', (event: Event) => {
|
||||
event.sender.openDevTools();
|
||||
});
|
||||
|
||||
ipc.on('vscode:reloadWindow', (event: Event) => {
|
||||
event.sender.reload();
|
||||
});
|
||||
|
||||
// Keyboard layout changes
|
||||
KeyboardLayoutMonitor.INSTANCE.onDidChangeKeyboardLayout(() => {
|
||||
powerMonitor.on('resume', () => { // After waking up from sleep
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false);
|
||||
this.windowsMainService.sendToAll('vscode:osResume', undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private isValidWebviewSource(source: string): boolean {
|
||||
if (!source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const srcUri: any = URI.parse(source).fsPath.toLowerCase();
|
||||
const rootUri = URI.file(this.environmentService.appRoot).fsPath.toLowerCase();
|
||||
return srcUri.startsWith(rootUri + nativeSep);
|
||||
}
|
||||
|
||||
private onUnexpectedError(err: Error): void {
|
||||
if (err) {
|
||||
|
||||
@@ -384,14 +293,11 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
// Theme changes
|
||||
if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
|
||||
let data = JSON.parse(payload);
|
||||
|
||||
this.stateService.setItem(THEME_STORAGE_KEY, data.baseTheme);
|
||||
this.stateService.setItem(THEME_BG_STORAGE_KEY, data.background);
|
||||
storeBackgroundColor(this.stateService, JSON.parse(payload));
|
||||
}
|
||||
}
|
||||
|
||||
startup(): TPromise<void> {
|
||||
startup(): Promise<void> {
|
||||
this.logService.debug('Starting VS Code');
|
||||
this.logService.debug(`from: ${this.environmentService.appRoot}`);
|
||||
this.logService.debug('args:', this.environmentService.args);
|
||||
@@ -400,7 +306,7 @@ export class CodeApplication extends Disposable {
|
||||
// This will help Windows to associate the running program with
|
||||
// any shortcut that is pinned to the taskbar and prevent showing
|
||||
// two icons in the taskbar for the same app.
|
||||
if (platform.isWindows && product.win32AppUserModelId) {
|
||||
if (isWindows && product.win32AppUserModelId) {
|
||||
app.setAppUserModelId(product.win32AppUserModelId);
|
||||
}
|
||||
|
||||
@@ -411,7 +317,7 @@ export class CodeApplication extends Disposable {
|
||||
// Explicitly opt out of the patch here before creating any windows.
|
||||
// See: https://github.com/Microsoft/vscode/issues/35361#issuecomment-399794085
|
||||
try {
|
||||
if (platform.isMacintosh && this.configurationService.getValue<boolean>('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) {
|
||||
if (isMacintosh && this.configurationService.getValue<boolean>('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) {
|
||||
systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true as any);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -421,9 +327,7 @@ export class CodeApplication extends Disposable {
|
||||
// Create Electron IPC Server
|
||||
this.electronIpcServer = new ElectronIPCServer();
|
||||
|
||||
// Resolve unique machine ID
|
||||
this.logService.trace('Resolving machine identifier...');
|
||||
return this.resolveMachineId().then(machineId => {
|
||||
const startupWithMachineId = (machineId: string) => {
|
||||
this.logService.trace(`Resolved machine identifier: ${machineId}`);
|
||||
|
||||
// Spawn shared process
|
||||
@@ -456,6 +360,28 @@ export class CodeApplication extends Disposable {
|
||||
this.stopTracingEventually(windows);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Resolve unique machine ID
|
||||
this.logService.trace('Resolving machine identifier...');
|
||||
const resolvedMachineId = this.resolveMachineId();
|
||||
if (typeof resolvedMachineId === 'string') {
|
||||
return startupWithMachineId(resolvedMachineId);
|
||||
} else {
|
||||
return resolvedMachineId.then(machineId => startupWithMachineId(machineId));
|
||||
}
|
||||
}
|
||||
|
||||
private resolveMachineId(): string | Promise<string> {
|
||||
const machineId = this.stateService.getItem<string>(CodeApplication.MACHINE_ID_KEY);
|
||||
if (machineId) {
|
||||
return machineId;
|
||||
}
|
||||
|
||||
return getMachineId().then(machineId => {
|
||||
this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId);
|
||||
|
||||
return machineId;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -488,35 +414,20 @@ export class CodeApplication extends Disposable {
|
||||
const timeoutHandle = setTimeout(() => stopRecording(true), 30000);
|
||||
|
||||
// Wait for all windows to get ready and stop tracing then
|
||||
TPromise.join(windows.map(window => window.ready())).then(() => {
|
||||
Promise.all(windows.map(window => window.ready())).then(() => {
|
||||
clearTimeout(timeoutHandle);
|
||||
stopRecording(false);
|
||||
});
|
||||
}
|
||||
|
||||
private resolveMachineId(): TPromise<string> {
|
||||
const machineId = this.stateService.getItem<string>(CodeApplication.MACHINE_ID_KEY);
|
||||
if (machineId) {
|
||||
return TPromise.wrap(machineId);
|
||||
}
|
||||
|
||||
return getMachineId().then(machineId => {
|
||||
|
||||
// Remember in global storage
|
||||
this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId);
|
||||
|
||||
return machineId;
|
||||
});
|
||||
}
|
||||
|
||||
private initServices(machineId: string): Thenable<IInstantiationService> {
|
||||
private initServices(machineId: string): Promise<IInstantiationService> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
|
||||
} else if (process.platform === 'linux') {
|
||||
if (process.env.SNAP && process.env.SNAP_REVISION) {
|
||||
services.set(IUpdateService, new SyncDescriptor(SnapUpdateService));
|
||||
services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION]));
|
||||
} else {
|
||||
services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
|
||||
}
|
||||
@@ -530,6 +441,10 @@ export class CodeApplication extends Disposable {
|
||||
services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv]));
|
||||
services.set(IMenubarService, new SyncDescriptor(MenubarService));
|
||||
services.set(IStorageMainService, new SyncDescriptor(StorageMainService));
|
||||
services.set(IBackupMainService, new SyncDescriptor(BackupMainService));
|
||||
services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
|
||||
services.set(IURLService, new SyncDescriptor(URLService));
|
||||
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
|
||||
|
||||
// Telemetry
|
||||
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
@@ -546,17 +461,20 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
const appInstantiationService = this.instantiationService.createChild(services);
|
||||
|
||||
return appInstantiationService.invokeFunction(accessor => this.initStorageService(accessor)).then(() => appInstantiationService);
|
||||
return appInstantiationService.invokeFunction(accessor => Promise.all([
|
||||
this.initStorageService(accessor),
|
||||
this.initBackupService(accessor)
|
||||
])).then(() => appInstantiationService);
|
||||
}
|
||||
|
||||
private initStorageService(accessor: ServicesAccessor): Thenable<void> {
|
||||
private initStorageService(accessor: ServicesAccessor): Promise<void> {
|
||||
const storageMainService = accessor.get(IStorageMainService) as StorageMainService;
|
||||
|
||||
// Ensure to close storage on shutdown
|
||||
this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close()));
|
||||
|
||||
// Initialize storage service
|
||||
return storageMainService.initialize().then(void 0, error => {
|
||||
return storageMainService.initialize().then(undefined, error => {
|
||||
errors.onUnexpectedError(error);
|
||||
this.logService.error(error);
|
||||
}).then(() => {
|
||||
@@ -586,6 +504,12 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private initBackupService(accessor: ServicesAccessor): Promise<void> {
|
||||
const backupMainService = accessor.get(IBackupMainService) as BackupMainService;
|
||||
|
||||
return backupMainService.initialize();
|
||||
}
|
||||
|
||||
private openFirstWindow(accessor: ServicesAccessor): ICodeWindow[] {
|
||||
const appInstantiationService = accessor.get(IInstantiationService);
|
||||
|
||||
@@ -635,8 +559,6 @@ export class CodeApplication extends Disposable {
|
||||
// Propagate to clients
|
||||
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); // TODO@Joao: unfold this
|
||||
|
||||
const args = this.environmentService.args;
|
||||
|
||||
// Create a URL handler which forwards to the last active window
|
||||
const activeWindowManager = new ActiveWindowManager(windowsService);
|
||||
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
|
||||
@@ -645,11 +567,11 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
// On Mac, Code can be running without any open windows, so we must create a window to handle urls,
|
||||
// if there is none
|
||||
if (platform.isMacintosh) {
|
||||
if (isMacintosh) {
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
|
||||
urlService.registerHandler({
|
||||
handleURL(uri: URI): TPromise<boolean> {
|
||||
handleURL(uri: URI): Promise<boolean> {
|
||||
if (windowsMainService.getWindowCount() === 0) {
|
||||
const cli = { ...environmentService.args, goto: true };
|
||||
const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true });
|
||||
@@ -657,7 +579,7 @@ export class CodeApplication extends Disposable {
|
||||
return window.ready().then(() => urlService.open(uri));
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -666,6 +588,7 @@ export class CodeApplication extends Disposable {
|
||||
urlService.registerHandler(multiplexURLHandler);
|
||||
|
||||
// Watch Electron URLs and forward them to the UrlService
|
||||
const args = this.environmentService.args;
|
||||
const urls = args['open-url'] ? args._urls : [];
|
||||
const urlListener = new ElectronURLListener(urls, urlService, this.windowsMainService);
|
||||
this._register(urlListener);
|
||||
@@ -692,9 +615,10 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
private afterWindowOpen(accessor: ServicesAccessor): void {
|
||||
const windowsMainService = accessor.get(IWindowsMainService);
|
||||
const historyMainService = accessor.get(IHistoryMainService);
|
||||
|
||||
let windowsMutex: Mutex | null = null;
|
||||
if (platform.isWindows) {
|
||||
if (isWindows) {
|
||||
|
||||
// Setup Windows mutex
|
||||
try {
|
||||
@@ -716,7 +640,7 @@ export class CodeApplication extends Disposable {
|
||||
// Ensure Windows foreground love module
|
||||
try {
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
<any>require.__$__nodeRequire('windows-foreground-love');
|
||||
require.__$__nodeRequire('windows-foreground-love');
|
||||
} catch (e) {
|
||||
if (!this.environmentService.isBuilt) {
|
||||
windowsMainService.showMessageBox({
|
||||
@@ -730,21 +654,105 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}} - Use static menu for now
|
||||
// Install Menu
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
if (platform.isMacintosh || configurationService.getValue<string>('window.titleBarStyle') !== 'custom') {
|
||||
instantiationService.createInstance(CodeMenu);
|
||||
}
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
// Remote Authorities
|
||||
this.handleRemoteAuthorities();
|
||||
|
||||
// Keyboard layout changes
|
||||
KeyboardLayoutMonitor.INSTANCE.onDidChangeKeyboardLayout(() => {
|
||||
this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false);
|
||||
});
|
||||
|
||||
// Jump List
|
||||
this.historyMainService.updateWindowsJumpList();
|
||||
this.historyMainService.onRecentlyOpenedChange(() => this.historyMainService.updateWindowsJumpList());
|
||||
historyMainService.updateWindowsJumpList();
|
||||
historyMainService.onRecentlyOpenedChange(() => historyMainService.updateWindowsJumpList());
|
||||
|
||||
// Start shared process after a while
|
||||
const sharedProcessSpawn = this._register(new RunOnceScheduler(() => getShellEnvironment().then(userEnv => this.sharedProcess.spawn(userEnv)), 3000));
|
||||
sharedProcessSpawn.schedule();
|
||||
}
|
||||
|
||||
private handleRemoteAuthorities(): void {
|
||||
const connectionPool: Map<string, ActiveConnection> = new Map<string, ActiveConnection>();
|
||||
|
||||
const isBuilt = this.environmentService.isBuilt;
|
||||
|
||||
class ActiveConnection {
|
||||
private _authority: string;
|
||||
private _client: Promise<Client<RemoteAgentConnectionContext>>;
|
||||
private _disposeRunner: RunOnceScheduler;
|
||||
|
||||
constructor(authority: string, host: string, port: number) {
|
||||
this._authority = authority;
|
||||
this._client = connectRemoteAgentManagement(authority, host, port, `main`, isBuilt);
|
||||
this._disposeRunner = new RunOnceScheduler(() => this._dispose(), 5000);
|
||||
}
|
||||
|
||||
private _dispose(): void {
|
||||
this._disposeRunner.dispose();
|
||||
connectionPool.delete(this._authority);
|
||||
this._client.then((connection) => {
|
||||
connection.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
public getClient(): Promise<Client<RemoteAgentConnectionContext>> {
|
||||
this._disposeRunner.schedule();
|
||||
return this._client;
|
||||
}
|
||||
}
|
||||
|
||||
const resolvedAuthorities = new Map<string, ResolvedAuthority>();
|
||||
ipc.on('vscode:remoteAuthorityResolved', (event: any, data: ResolvedAuthority) => {
|
||||
resolvedAuthorities.set(data.authority, data);
|
||||
});
|
||||
|
||||
const resolveAuthority = (authority: string): ResolvedAuthority | null => {
|
||||
if (authority.indexOf('+') >= 0) {
|
||||
if (resolvedAuthorities.has(authority)) {
|
||||
return resolvedAuthorities.get(authority);
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
const [host, strPort] = authority.split(':');
|
||||
const port = parseInt(strPort, 10);
|
||||
return { authority, host, port, syncExtensions: false };
|
||||
}
|
||||
};
|
||||
|
||||
protocol.registerBufferProtocol(REMOTE_HOST_SCHEME, async (request, callback) => {
|
||||
if (request.method !== 'GET') {
|
||||
return callback(null);
|
||||
}
|
||||
const uri = URI.parse(request.url);
|
||||
|
||||
let activeConnection: ActiveConnection = null;
|
||||
if (connectionPool.has(uri.authority)) {
|
||||
activeConnection = connectionPool.get(uri.authority);
|
||||
} else {
|
||||
let resolvedAuthority = resolveAuthority(uri.authority);
|
||||
if (!resolvedAuthority) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
activeConnection = new ActiveConnection(uri.authority, resolvedAuthority.host, resolvedAuthority.port);
|
||||
connectionPool.set(uri.authority, activeConnection);
|
||||
}
|
||||
try {
|
||||
const rawClient = await activeConnection.getClient();
|
||||
if (connectionPool.has(uri.authority)) { // not disposed in the meantime
|
||||
const channel = rawClient.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME);
|
||||
|
||||
// TODO@alex don't use call directly, wrap it around a `RemoteExtensionsFileSystemProvider`
|
||||
const fileContents = await channel.call<Uint8Array>('readFile', [uri]);
|
||||
callback(Buffer.from(fileContents));
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
} catch (err) {
|
||||
errors.onUnexpectedError(err);
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
import { fromNodeEventEmitter } from 'vs/base/common/event';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { BrowserWindow, app } from 'electron';
|
||||
|
||||
type LoginEvent = {
|
||||
@@ -30,9 +30,9 @@ export class ProxyAuthHandler {
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
@IWindowsMainService private windowsMainService: IWindowsMainService
|
||||
@IWindowsMainService private readonly windowsMainService: IWindowsMainService
|
||||
) {
|
||||
const onLogin = fromNodeEventEmitter<LoginEvent>(app, 'login', (event, webContents, req, authInfo, cb) => ({ event, webContents, req, authInfo, cb }));
|
||||
const onLogin = Event.fromNodeEventEmitter<LoginEvent>(app, 'login', (event, webContents, req, authInfo, cb) => ({ event, webContents, req, authInfo, cb }));
|
||||
onLogin(this.onLogin, this, this.disposables);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,8 @@ import { app, dialog } from 'electron';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import product from 'vs/platform/node/product';
|
||||
import * as path from 'path';
|
||||
import { parseMainProcessArgv } from 'vs/platform/environment/node/argv';
|
||||
import { mkdirp, readdir, rimraf } from 'vs/base/node/pfs';
|
||||
import { parseMainProcessArgv } from 'vs/platform/environment/node/argvHelper';
|
||||
import { mkdirp } from 'vs/base/node/pfs';
|
||||
import { validatePaths } from 'vs/code/node/paths';
|
||||
import { LifecycleService, ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
@@ -22,22 +21,14 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log';
|
||||
import { StateService } from 'vs/platform/state/node/stateService';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { IBackupMainService } from 'vs/platform/backup/common/backup';
|
||||
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
|
||||
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { RequestService } from 'vs/platform/request/electron-main/requestService';
|
||||
import { IURLService } from 'vs/platform/url/common/url';
|
||||
import { URLService } from 'vs/platform/url/common/urlService';
|
||||
import * as fs from 'fs';
|
||||
import { CodeApplication } from 'vs/code/electron-main/app';
|
||||
import { HistoryMainService } from 'vs/platform/history/electron-main/historyMainService';
|
||||
import { IHistoryMainService } from 'vs/platform/history/common/history';
|
||||
import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
|
||||
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { localize } from 'vs/nls';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
@@ -45,75 +36,19 @@ import { IDiagnosticsService, DiagnosticsService } from 'vs/platform/diagnostics
|
||||
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
|
||||
import { uploadLogs } from 'vs/code/electron-main/logUploader';
|
||||
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { CommandLineDialogService } from 'vs/platform/dialogs/node/dialogService';
|
||||
import { ILabelService, LabelService } from 'vs/platform/label/common/label';
|
||||
import { createWaitMarkerFile } from 'vs/code/node/wait';
|
||||
|
||||
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
const environmentService = new EnvironmentService(args, process.execPath);
|
||||
|
||||
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
|
||||
process.once('exit', () => logService.dispose());
|
||||
setTimeout(() => cleanupOlderLogs(environmentService).then(null, err => console.error(err)), 10000);
|
||||
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
services.set(ILabelService, new LabelService(environmentService, void 0, void 0));
|
||||
services.set(ILogService, logService);
|
||||
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
|
||||
services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
|
||||
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
|
||||
services.set(IStateService, new SyncDescriptor(StateService));
|
||||
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
services.set(IURLService, new SyncDescriptor(URLService));
|
||||
services.set(IBackupMainService, new SyncDescriptor(BackupMainService));
|
||||
services.set(IDialogService, new SyncDescriptor(CommandLineDialogService));
|
||||
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
|
||||
|
||||
return new InstantiationService(services, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up older logs, while keeping the 10 most recent ones.
|
||||
*/
|
||||
async function cleanupOlderLogs(environmentService: EnvironmentService): Promise<void> {
|
||||
const currentLog = path.basename(environmentService.logsPath);
|
||||
const logsRoot = path.dirname(environmentService.logsPath);
|
||||
const children = await readdir(logsRoot);
|
||||
const allSessions = children.filter(name => /^\d{8}T\d{6}$/.test(name));
|
||||
const oldSessions = allSessions.sort().filter((d, i) => d !== currentLog);
|
||||
const toDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 9));
|
||||
|
||||
await Promise.all(toDelete.map(name => rimraf(path.join(logsRoot, name))));
|
||||
}
|
||||
|
||||
function createPaths(environmentService: IEnvironmentService): Thenable<any> {
|
||||
const paths = [
|
||||
environmentService.extensionsPath,
|
||||
environmentService.nodeCachedDataDir,
|
||||
environmentService.logsPath,
|
||||
environmentService.globalStorageHome,
|
||||
environmentService.workspaceStorageHome
|
||||
];
|
||||
|
||||
return Promise.all(paths.map(path => path && mkdirp(path)));
|
||||
}
|
||||
|
||||
class ExpectedError extends Error {
|
||||
public readonly isExpected = true;
|
||||
readonly isExpected = true;
|
||||
}
|
||||
|
||||
function setupIPC(accessor: ServicesAccessor): Thenable<Server> {
|
||||
function setupIPC(accessor: ServicesAccessor): Promise<Server> {
|
||||
const logService = accessor.get(ILogService);
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
const requestService = accessor.get(IRequestService);
|
||||
const diagnosticsService = accessor.get(IDiagnosticsService);
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
|
||||
function allowSetForegroundWindow(service: LaunchChannelClient): Thenable<void> {
|
||||
let promise: Thenable<void> = Promise.resolve();
|
||||
function allowSetForegroundWindow(service: LaunchChannelClient): Promise<void> {
|
||||
let promise: Promise<void> = Promise.resolve();
|
||||
if (platform.isWindows) {
|
||||
promise = service.getMainProcessId()
|
||||
.then(processId => {
|
||||
@@ -131,7 +66,7 @@ function setupIPC(accessor: ServicesAccessor): Thenable<Server> {
|
||||
return promise;
|
||||
}
|
||||
|
||||
function setup(retry: boolean): Thenable<Server> {
|
||||
function setup(retry: boolean): Promise<Server> {
|
||||
return serve(environmentService.mainIPCHandle).then(server => {
|
||||
|
||||
// Print --status usage info
|
||||
@@ -157,7 +92,15 @@ function setupIPC(accessor: ServicesAccessor): Thenable<Server> {
|
||||
|
||||
return server;
|
||||
}, err => {
|
||||
|
||||
// Handle unexpected errors (the only expected error is EADDRINUSE that
|
||||
// indicates a second instance of Code is running)
|
||||
if (err.code !== 'EADDRINUSE') {
|
||||
|
||||
// Show a dialog for errors that can be resolved by the user
|
||||
handleStartupDataDirError(environmentService, err);
|
||||
|
||||
// Any other runtime error is just printed to the console
|
||||
return Promise.reject<Server>(err);
|
||||
}
|
||||
|
||||
@@ -198,14 +141,18 @@ function setupIPC(accessor: ServicesAccessor): Thenable<Server> {
|
||||
// Process Info
|
||||
if (environmentService.args.status) {
|
||||
return service.getMainProcessInfo().then(info => {
|
||||
return diagnosticsService.printDiagnostics(info).then(() => Promise.reject(new ExpectedError()));
|
||||
return instantiationService.invokeFunction(accessor => {
|
||||
return accessor.get(IDiagnosticsService).printDiagnostics(info).then(() => Promise.reject(new ExpectedError()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Log uploader
|
||||
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
|
||||
return uploadLogs(service, requestService, environmentService)
|
||||
.then(() => Promise.reject(new ExpectedError()));
|
||||
return instantiationService.invokeFunction(accessor => {
|
||||
return uploadLogs(service, accessor.get(IRequestService), environmentService)
|
||||
.then(() => Promise.reject(new ExpectedError()));
|
||||
});
|
||||
}
|
||||
|
||||
logService.trace('Sending env to running instance...');
|
||||
@@ -265,6 +212,15 @@ function showStartupWarningDialog(message: string, detail: string): void {
|
||||
});
|
||||
}
|
||||
|
||||
function handleStartupDataDirError(environmentService: IEnvironmentService, error): void {
|
||||
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
||||
showStartupWarningDialog(
|
||||
localize('startupDataDirError', "Unable to write program user data."),
|
||||
localize('startupDataDirErrorDetail', "Please make sure the directory {0} is writeable.", environmentService.userDataPath)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void {
|
||||
const logService = accessor.get(ILogService);
|
||||
const lifecycleService = accessor.get(ILifecycleService);
|
||||
@@ -316,14 +272,20 @@ function startup(args: ParsedArgs): void {
|
||||
const instantiationService = createServices(args, bufferLogService);
|
||||
instantiationService.invokeFunction(accessor => {
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
const stateService = accessor.get(IStateService);
|
||||
|
||||
// Patch `process.env` with the instance's environment
|
||||
const instanceEnvironment = patchEnvironment(environmentService);
|
||||
|
||||
// Startup
|
||||
return instantiationService
|
||||
.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
|
||||
.then(() => instantiationService.invokeFunction(setupIPC))
|
||||
return initServices(environmentService, stateService as StateService)
|
||||
.then(() => instantiationService.invokeFunction(setupIPC), error => {
|
||||
|
||||
// Show a dialog for errors that can be resolved by the user
|
||||
handleStartupDataDirError(environmentService, error);
|
||||
|
||||
return Promise.reject(error);
|
||||
})
|
||||
.then(mainIpcServer => {
|
||||
bufferLogService.logger = createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath);
|
||||
|
||||
@@ -332,6 +294,43 @@ function startup(args: ParsedArgs): void {
|
||||
}).then(null, err => instantiationService.invokeFunction(quit, err));
|
||||
}
|
||||
|
||||
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
const environmentService = new EnvironmentService(args, process.execPath);
|
||||
|
||||
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
|
||||
process.once('exit', () => logService.dispose());
|
||||
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
services.set(ILogService, logService);
|
||||
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
|
||||
services.set(IStateService, new SyncDescriptor(StateService));
|
||||
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
|
||||
|
||||
return new InstantiationService(services, true);
|
||||
}
|
||||
|
||||
function initServices(environmentService: IEnvironmentService, stateService: StateService): Promise<any> {
|
||||
|
||||
// Ensure paths for environment service exist
|
||||
const environmentServiceInitialization = Promise.all([
|
||||
environmentService.extensionsPath,
|
||||
environmentService.nodeCachedDataDir,
|
||||
environmentService.logsPath,
|
||||
environmentService.globalStorageHome,
|
||||
environmentService.workspaceStorageHome,
|
||||
environmentService.backupHome
|
||||
].map(path => path && mkdirp(path)));
|
||||
|
||||
// State service
|
||||
const stateServiceInitialization = stateService.init();
|
||||
|
||||
return Promise.all([environmentServiceInitialization, stateServiceInitialization]);
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
|
||||
// Set the error handler early enough so that we are not getting the
|
||||
@@ -347,7 +346,7 @@ function main(): void {
|
||||
console.error(err.message);
|
||||
app.exit(1);
|
||||
|
||||
return void 0;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If we are started with --wait create a random temporary file
|
||||
|
||||
@@ -105,7 +105,7 @@ export class SharedProcess implements ISharedProcess {
|
||||
});
|
||||
|
||||
disposables.push(toDisposable(() => sender.send('handshake:goodbye')));
|
||||
ipcMain.once('handshake:im ready', () => c(void 0));
|
||||
ipcMain.once('handshake:im ready', () => c(undefined));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,12 +7,17 @@ import { isWindows, isMacintosh } from 'vs/base/common/platform';
|
||||
import { systemPreferences } from 'electron';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
|
||||
export const DEFAULT_BG_LIGHT = '#FFFFFF';
|
||||
export const DEFAULT_BG_DARK = '#1E1E1E';
|
||||
export const DEFAULT_BG_HC_BLACK = '#000000';
|
||||
const DEFAULT_BG_LIGHT = '#FFFFFF';
|
||||
const DEFAULT_BG_DARK = '#1E1E1E';
|
||||
const DEFAULT_BG_HC_BLACK = '#000000';
|
||||
|
||||
export const THEME_STORAGE_KEY = 'theme';
|
||||
export const THEME_BG_STORAGE_KEY = 'themeBackground';
|
||||
const THEME_STORAGE_KEY = 'theme';
|
||||
const THEME_BG_STORAGE_KEY = 'themeBackground';
|
||||
|
||||
export function storeBackgroundColor(stateService: IStateService, data: { baseTheme: string, background: string }): void {
|
||||
stateService.setItem(THEME_STORAGE_KEY, data.baseTheme);
|
||||
stateService.setItem(THEME_BG_STORAGE_KEY, data.background);
|
||||
}
|
||||
|
||||
export function getBackgroundColor(stateService: IStateService): string {
|
||||
if (isWindows && systemPreferences.isInvertedColorScheme()) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import * as perf from 'vs/base/common/performance';
|
||||
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService';
|
||||
import { getBackgroundColor } from 'vs/code/electron-main/theme';
|
||||
import { IStorageMainService } from 'vs/platform/storage/node/storageMainService';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
|
||||
export interface IWindowCreationOptions {
|
||||
state: IWindowState;
|
||||
@@ -74,19 +75,19 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
private currentConfig: IWindowConfiguration;
|
||||
private pendingLoadConfig: IWindowConfiguration;
|
||||
|
||||
private marketplaceHeadersPromise: Thenable<object>;
|
||||
private marketplaceHeadersPromise: Promise<object>;
|
||||
|
||||
private touchBarGroups: Electron.TouchBarSegmentedControl[];
|
||||
|
||||
constructor(
|
||||
config: IWindowCreationOptions,
|
||||
@ILogService private logService: ILogService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IStateService private stateService: IStateService,
|
||||
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
|
||||
@IBackupMainService private backupMainService: IBackupMainService,
|
||||
@IStorageMainService private storageMainService: IStorageMainService
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
|
||||
@IBackupMainService private readonly backupMainService: IBackupMainService,
|
||||
@IStorageMainService private readonly storageMainService: IStorageMainService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -256,19 +257,19 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
|
||||
get backupPath(): string {
|
||||
return this.currentConfig ? this.currentConfig.backupPath : void 0;
|
||||
return this.currentConfig ? this.currentConfig.backupPath : undefined;
|
||||
}
|
||||
|
||||
get openedWorkspace(): IWorkspaceIdentifier {
|
||||
return this.currentConfig ? this.currentConfig.workspace : void 0;
|
||||
return this.currentConfig ? this.currentConfig.workspace : undefined;
|
||||
}
|
||||
|
||||
get openedFolderUri(): URI {
|
||||
return this.currentConfig ? this.currentConfig.folderUri : void 0;
|
||||
return this.currentConfig ? this.currentConfig.folderUri : undefined;
|
||||
}
|
||||
|
||||
get remoteAuthority(): string {
|
||||
return this.currentConfig ? this.currentConfig.remoteAuthority : void 0;
|
||||
return this.currentConfig ? this.currentConfig.remoteAuthority : undefined;
|
||||
}
|
||||
|
||||
setReady(): void {
|
||||
@@ -276,11 +277,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
// inform all waiting promises that we are ready now
|
||||
while (this.whenReadyCallbacks.length) {
|
||||
this.whenReadyCallbacks.pop()(this);
|
||||
this.whenReadyCallbacks.pop()!(this);
|
||||
}
|
||||
}
|
||||
|
||||
ready(): Thenable<ICodeWindow> {
|
||||
ready(): Promise<ICodeWindow> {
|
||||
return new Promise<ICodeWindow>(resolve => {
|
||||
if (this.isReady) {
|
||||
return resolve(this);
|
||||
@@ -363,17 +364,31 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
this._lastFocusTime = Date.now();
|
||||
});
|
||||
|
||||
// Simple fullscreen doesn't resize automatically when the resolution changes
|
||||
// Simple fullscreen doesn't resize automatically when the resolution changes so as a workaround
|
||||
// we need to detect when display metrics change or displays are added/removed and toggle the
|
||||
// fullscreen manually.
|
||||
if (isMacintosh) {
|
||||
const displayMetricsChangedListener = () => {
|
||||
if (this.isFullScreen() && !this.useNativeFullScreen()) {
|
||||
const simpleFullScreenScheduler = this._register(new RunOnceScheduler(() => {
|
||||
if (!this._win) {
|
||||
return; // disposed
|
||||
}
|
||||
|
||||
if (!this.useNativeFullScreen() && this.isFullScreen()) {
|
||||
this.setFullScreen(false);
|
||||
this.setFullScreen(true);
|
||||
}
|
||||
};
|
||||
}, 100));
|
||||
|
||||
screen.addListener('display-metrics-changed', displayMetricsChangedListener);
|
||||
this._register(toDisposable(() => screen.removeListener('display-metrics-changed', displayMetricsChangedListener)));
|
||||
const displayChangedListener = () => simpleFullScreenScheduler.schedule();
|
||||
|
||||
screen.on('display-metrics-changed', displayChangedListener);
|
||||
this._register(toDisposable(() => screen.removeListener('display-metrics-changed', displayChangedListener)));
|
||||
|
||||
screen.on('display-added', displayChangedListener);
|
||||
this._register(toDisposable(() => screen.removeListener('display-added', displayChangedListener)));
|
||||
|
||||
screen.on('display-removed', displayChangedListener);
|
||||
this._register(toDisposable(() => screen.removeListener('display-removed', displayChangedListener)));
|
||||
}
|
||||
|
||||
// Window (Un)Maximize
|
||||
@@ -412,34 +427,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
// Handle Workspace events
|
||||
this._register(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
|
||||
|
||||
// TODO@Ben workaround for https://github.com/Microsoft/vscode/issues/13612
|
||||
// It looks like smooth scrolling disappears as soon as the window is minimized
|
||||
// and maximized again. Touching some window properties "fixes" it, like toggling
|
||||
// the visibility of the menu.
|
||||
if (isWindows) {
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
if (windowConfig && windowConfig.smoothScrollingWorkaround === true) {
|
||||
let minimized = false;
|
||||
|
||||
const restoreSmoothScrolling = () => {
|
||||
if (minimized) {
|
||||
const visibility = this.getMenuBarVisibility();
|
||||
const temporaryVisibility: MenuBarVisibility = (visibility === 'hidden' || visibility === 'toggle') ? 'default' : 'hidden';
|
||||
setTimeout(() => {
|
||||
this.doSetMenuBarVisibility(temporaryVisibility);
|
||||
this.doSetMenuBarVisibility(visibility);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
minimized = false;
|
||||
};
|
||||
|
||||
this._win.on('minimize', () => minimized = true);
|
||||
this._win.on('restore', () => restoreSmoothScrolling());
|
||||
this._win.on('maximize', () => restoreSmoothScrolling());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onUntitledWorkspaceDeleted(workspace: IWorkspaceIdentifier): void {
|
||||
@@ -447,7 +434,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
// Make sure to update our workspace config if we detect that it
|
||||
// was deleted
|
||||
if (this.openedWorkspace && this.openedWorkspace.id === workspace.id) {
|
||||
this.currentConfig.workspace = void 0;
|
||||
this.currentConfig.workspace = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,12 +533,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
}
|
||||
|
||||
reload(configuration?: IWindowConfiguration, cli?: ParsedArgs): void {
|
||||
reload(configurationIn?: IWindowConfiguration, cli?: ParsedArgs): void {
|
||||
|
||||
// If config is not provided, copy our current one
|
||||
if (!configuration) {
|
||||
configuration = objects.mixin({}, this.currentConfig);
|
||||
}
|
||||
const configuration = configurationIn ? configurationIn : objects.mixin({}, this.currentConfig);
|
||||
|
||||
// Delete some properties we do not want during reload
|
||||
delete configuration.filesToOpen;
|
||||
@@ -608,13 +593,13 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
windowConfiguration.perfEntries = perf.exportEntries();
|
||||
|
||||
// Parts splash
|
||||
windowConfiguration.partsSplashData = this.storageMainService.get('parts-splash-data', void 0);
|
||||
windowConfiguration.partsSplashData = this.storageMainService.get('parts-splash-data', undefined);
|
||||
|
||||
// Config (combination of process.argv and window configuration)
|
||||
const environment = parseArgs(process.argv);
|
||||
const config = objects.assign(environment, windowConfiguration);
|
||||
for (let key in config) {
|
||||
if (config[key] === void 0 || config[key] === null || config[key] === '' || config[key] === false) {
|
||||
if (config[key] === undefined || config[key] === null || config[key] === '' || config[key] === false) {
|
||||
delete config[key]; // only send over properties that have a true value
|
||||
}
|
||||
}
|
||||
@@ -654,7 +639,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
const res = {
|
||||
mode: WindowMode.Fullscreen,
|
||||
display: display ? display.id : void 0,
|
||||
display: display ? display.id : undefined,
|
||||
|
||||
// Still carry over window dimensions from previous sessions
|
||||
// if we can compute it in fullscreen state.
|
||||
@@ -716,7 +701,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
return state;
|
||||
}
|
||||
|
||||
private validateWindowState(state: IWindowState): IWindowState {
|
||||
private validateWindowState(state: IWindowState): IWindowState | null {
|
||||
if (!state) {
|
||||
return null;
|
||||
}
|
||||
@@ -1035,17 +1020,17 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] {
|
||||
const segments: ITouchBarSegment[] = items.map(item => {
|
||||
let icon: Electron.NativeImage;
|
||||
let icon: Electron.NativeImage | undefined;
|
||||
if (item.iconLocation && item.iconLocation.dark.scheme === 'file') {
|
||||
icon = nativeImage.createFromPath(URI.revive(item.iconLocation.dark).fsPath);
|
||||
if (icon.isEmpty()) {
|
||||
icon = void 0;
|
||||
icon = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
label: !icon ? item.title as string : void 0,
|
||||
label: !icon ? item.title as string : undefined,
|
||||
icon
|
||||
};
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
|
||||
import { hasArgs, asArray } from 'vs/platform/environment/node/argv';
|
||||
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, app } from 'electron';
|
||||
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences } from 'electron';
|
||||
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/paths';
|
||||
import { ILifecycleService, UnloadReason, IWindowUnloadEvent, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -26,8 +26,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IHistoryMainService } from 'vs/platform/history/common/history';
|
||||
import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
@@ -35,7 +34,7 @@ import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Queue, timeout } from 'vs/base/common/async';
|
||||
import { exists } from 'vs/base/node/pfs';
|
||||
import { getComparisonKey, isEqual, normalizePath } from 'vs/base/common/resources';
|
||||
import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename } from 'vs/base/common/resources';
|
||||
import { endsWith } from 'vs/base/common/strings';
|
||||
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
|
||||
|
||||
@@ -146,27 +145,21 @@ export class WindowsManager implements IWindowsMainService {
|
||||
private _onWindowLoad = new Emitter<number>();
|
||||
onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;
|
||||
|
||||
private _onActiveWindowChanged = new Emitter<ICodeWindow>();
|
||||
onActiveWindowChanged: CommonEvent<ICodeWindow> = this._onActiveWindowChanged.event;
|
||||
|
||||
private _onWindowReload = new Emitter<number>();
|
||||
onWindowReload: CommonEvent<number> = this._onWindowReload.event;
|
||||
|
||||
private _onWindowsCountChanged = new Emitter<IWindowsCountChangedEvent>();
|
||||
onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;
|
||||
|
||||
constructor(
|
||||
private readonly machineId: string,
|
||||
@ILogService private logService: ILogService,
|
||||
@IStateService private stateService: IStateService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@ILifecycleService private lifecycleService: ILifecycleService,
|
||||
@IBackupMainService private backupMainService: IBackupMainService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IHistoryMainService private historyMainService: IHistoryMainService,
|
||||
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
||||
@IBackupMainService private readonly backupMainService: IBackupMainService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IHistoryMainService private readonly historyMainService: IHistoryMainService,
|
||||
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
this.windowsState = this.getWindowsState();
|
||||
if (!Array.isArray(this.windowsState.openedWindows)) {
|
||||
@@ -174,7 +167,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
this.dialogs = new Dialogs(environmentService, telemetryService, stateService, this);
|
||||
this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, this);
|
||||
this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, historyMainService, this);
|
||||
}
|
||||
|
||||
private getWindowsState(): IWindowsState {
|
||||
@@ -209,13 +202,6 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// React to windows focus changes
|
||||
app.on('browser-window-focus', () => {
|
||||
setTimeout(() => {
|
||||
this._onActiveWindowChanged.fire(this.getLastActiveWindow());
|
||||
});
|
||||
});
|
||||
|
||||
// React to workbench ready events from windows
|
||||
ipc.on('vscode:workbenchReady', (event: any, windowId: number) => {
|
||||
this.logService.trace('IPC#vscode-workbenchReady');
|
||||
@@ -249,7 +235,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
// clear last closed window state when a new window opens. this helps on macOS where
|
||||
// otherwise closing the last window, opening a new window and then quitting would
|
||||
// use the state of the previously closed window when restarting.
|
||||
this.lastClosedWindowState = void 0;
|
||||
this.lastClosedWindowState = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -387,7 +373,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
// collect all file inputs
|
||||
let fileInputs: IFileInputs = void 0;
|
||||
let fileInputs: IFileInputs = undefined;
|
||||
for (const path of pathsToOpen) {
|
||||
if (path.fileUri) {
|
||||
if (!fileInputs) {
|
||||
@@ -492,8 +478,8 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
// Remember in recent document list (unless this opens for extension development)
|
||||
// Also do not add paths when files are opened for diffing, only if opened individually
|
||||
if (!usedWindows.some(w => w.isExtensionDevelopmentHost) && !openConfig.cli.diff) {
|
||||
const recentlyOpenedWorkspaces: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)[] = [];
|
||||
if (!usedWindows.some(w => w.isExtensionDevelopmentHost) && !openConfig.diffMode) {
|
||||
const recentlyOpenedWorkspaces: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier> = [];
|
||||
const recentlyOpenedFiles: URI[] = [];
|
||||
|
||||
pathsToOpen.forEach(win => {
|
||||
@@ -513,7 +499,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
// used for the edit operation is closed or loaded to a different folder so that the waiting
|
||||
// process can continue. We do this by deleting the waitMarkerFilePath.
|
||||
if (openConfig.context === OpenContext.CLI && openConfig.cli.wait && openConfig.cli.waitMarkerFilePath && usedWindows.length === 1 && usedWindows[0]) {
|
||||
this.waitForWindowCloseOrLoad(usedWindows[0].id).then(() => fs.unlink(openConfig.cli.waitMarkerFilePath, error => void 0));
|
||||
this.waitForWindowCloseOrLoad(usedWindows[0].id).then(() => fs.unlink(openConfig.cli.waitMarkerFilePath, error => undefined));
|
||||
}
|
||||
|
||||
return usedWindows;
|
||||
@@ -552,9 +538,6 @@ export class WindowsManager implements IWindowsMainService {
|
||||
if (lastActiveWindow) {
|
||||
usedWindows.push(this.doAddFoldersToExistingWindow(lastActiveWindow, foldersToAdd));
|
||||
}
|
||||
|
||||
// Reset because we handled them
|
||||
foldersToAdd = [];
|
||||
}
|
||||
|
||||
// Handle files to open/diff or to create when we dont open a folder and we do not restore any folder/untitled from hot-exit
|
||||
@@ -595,7 +578,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, fileInputs));
|
||||
|
||||
// Reset these because we handled them
|
||||
fileInputs = void 0;
|
||||
fileInputs = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,7 +595,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}));
|
||||
|
||||
// Reset these because we handled them
|
||||
fileInputs = void 0;
|
||||
fileInputs = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,14 +607,14 @@ export class WindowsManager implements IWindowsMainService {
|
||||
const windowsOnWorkspace = arrays.coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, workspaceToOpen)));
|
||||
if (windowsOnWorkspace.length > 0) {
|
||||
const windowOnWorkspace = windowsOnWorkspace[0];
|
||||
const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === windowOnWorkspace.remoteAuthority) ? fileInputs : void 0;
|
||||
const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === windowOnWorkspace.remoteAuthority) ? fileInputs : undefined;
|
||||
|
||||
// Do open files
|
||||
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, fileInputsForWindow));
|
||||
|
||||
// Reset these because we handled them
|
||||
if (fileInputsForWindow) {
|
||||
fileInputs = void 0;
|
||||
fileInputs = undefined;
|
||||
}
|
||||
|
||||
openFolderInNewWindow = true; // any other folders to open must open in new window then
|
||||
@@ -643,14 +626,14 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return; // ignore folders that are already open
|
||||
}
|
||||
|
||||
const fileInputsForWindow = (fileInputs && !fileInputs.remoteAuthority) ? fileInputs : void 0;
|
||||
const fileInputsForWindow = (fileInputs && !fileInputs.remoteAuthority) ? fileInputs : undefined;
|
||||
|
||||
// Do open folder
|
||||
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspace: workspaceToOpen }, openFolderInNewWindow, fileInputsForWindow));
|
||||
|
||||
// Reset these because we handled them
|
||||
if (fileInputsForWindow) {
|
||||
fileInputs = void 0;
|
||||
fileInputs = undefined;
|
||||
}
|
||||
|
||||
openFolderInNewWindow = true; // any other folders to open must open in new window then
|
||||
@@ -666,14 +649,14 @@ export class WindowsManager implements IWindowsMainService {
|
||||
const windowsOnFolderPath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, folderToOpen)));
|
||||
if (windowsOnFolderPath.length > 0) {
|
||||
const windowOnFolderPath = windowsOnFolderPath[0];
|
||||
const fileInputsForWindow = fileInputs && fileInputs.remoteAuthority === windowOnFolderPath.remoteAuthority ? fileInputs : void 0;
|
||||
const fileInputsForWindow = fileInputs && fileInputs.remoteAuthority === windowOnFolderPath.remoteAuthority ? fileInputs : undefined;
|
||||
|
||||
// Do open files
|
||||
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, fileInputsForWindow));
|
||||
|
||||
// Reset these because we handled them
|
||||
if (fileInputsForWindow) {
|
||||
fileInputs = void 0;
|
||||
fileInputs = undefined;
|
||||
}
|
||||
|
||||
openFolderInNewWindow = true; // any other folders to open must open in new window then
|
||||
@@ -687,14 +670,14 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}
|
||||
|
||||
const remoteAuthority = getRemoteAuthority(folderToOpen);
|
||||
const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : void 0;
|
||||
const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
|
||||
|
||||
// Do open folder
|
||||
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { folderUri: folderToOpen, remoteAuthority }, openFolderInNewWindow, fileInputsForWindow));
|
||||
|
||||
// Reset these because we handled them
|
||||
if (fileInputsForWindow) {
|
||||
fileInputs = void 0;
|
||||
fileInputs = undefined;
|
||||
}
|
||||
|
||||
openFolderInNewWindow = true; // any other folders to open must open in new window then
|
||||
@@ -705,7 +688,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
if (emptyToRestore.length > 0) {
|
||||
emptyToRestore.forEach(emptyWindowBackupInfo => {
|
||||
const remoteAuthority = emptyWindowBackupInfo.remoteAuthority;
|
||||
const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : void 0;
|
||||
const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
|
||||
|
||||
usedWindows.push(this.openInBrowserWindow({
|
||||
userEnv: openConfig.userEnv,
|
||||
@@ -720,7 +703,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
// Reset these because we handled them
|
||||
if (fileInputsForWindow) {
|
||||
fileInputs = void 0;
|
||||
fileInputs = undefined;
|
||||
}
|
||||
|
||||
openFolderInNewWindow = true; // any other folders to open must open in new window then
|
||||
@@ -732,7 +715,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
if (fileInputs && !emptyToOpen) {
|
||||
emptyToOpen++;
|
||||
}
|
||||
const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || void 0);
|
||||
const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined);
|
||||
for (let i = 0; i < emptyToOpen; i++) {
|
||||
usedWindows.push(this.openInBrowserWindow({
|
||||
userEnv: openConfig.userEnv,
|
||||
@@ -745,7 +728,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
}));
|
||||
|
||||
// Reset these because we handled them
|
||||
fileInputs = void 0;
|
||||
fileInputs = undefined;
|
||||
openFolderInNewWindow = true; // any other window to open must open in new window then
|
||||
}
|
||||
}
|
||||
@@ -834,8 +817,8 @@ export class WindowsManager implements IWindowsMainService {
|
||||
// folders should be added to the existing window.
|
||||
if (!openConfig.addMode && isCommandLineOrAPICall) {
|
||||
const foldersToOpen = windowsToOpen.filter(path => !!path.folderUri);
|
||||
if (foldersToOpen.length > 1) {
|
||||
const workspace = this.workspacesMainService.createWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.folderUri })));
|
||||
if (foldersToOpen.length > 1 && foldersToOpen.every(f => f.folderUri.scheme === Schemas.file)) {
|
||||
const workspace = this.workspacesMainService.createUntitledWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.folderUri })));
|
||||
|
||||
// Add workspace and remove folders thereby
|
||||
windowsToOpen.push({ workspace, remoteAuthority: foldersToOpen[0].remoteAuthority });
|
||||
@@ -886,7 +869,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
private doExtractPathsFromCLI(cli: ParsedArgs): IPath[] {
|
||||
const pathsToOpen: IPathToOpen[] = [];
|
||||
const parseOptions: IPathParseOptions = { ignoreFileNotFound: true, gotoLineMode: cli.goto, remoteAuthority: cli.remote || void 0 };
|
||||
const parseOptions: IPathParseOptions = { ignoreFileNotFound: true, gotoLineMode: cli.goto, remoteAuthority: cli.remote || undefined };
|
||||
|
||||
// folder uris
|
||||
const folderUris = asArray(cli['folder-uri']);
|
||||
@@ -1017,8 +1000,9 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
// normalize URI
|
||||
uri = normalizePath(uri);
|
||||
if (endsWith(uri.path, '/')) {
|
||||
uri = uri.with({ path: uri.path.substr(0, uri.path.length - 1) });
|
||||
const uriPath = uri.path;
|
||||
if (uriPath.length > 2 && endsWith(uriPath, '/')) {
|
||||
uri = uri.with({ path: uriPath.substr(0, uriPath.length - 1) });
|
||||
}
|
||||
if (isFile) {
|
||||
if (options && options.gotoLineMode) {
|
||||
@@ -1074,8 +1058,8 @@ export class WindowsManager implements IWindowsMainService {
|
||||
// File
|
||||
return {
|
||||
fileUri: URI.file(candidate),
|
||||
lineNumber: gotoLineMode ? parsedPath.line : void 0,
|
||||
columnNumber: gotoLineMode ? parsedPath.column : void 0,
|
||||
lineNumber: gotoLineMode ? parsedPath.line : undefined,
|
||||
columnNumber: gotoLineMode ? parsedPath.column : undefined,
|
||||
remoteAuthority
|
||||
};
|
||||
}
|
||||
@@ -1473,10 +1457,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
// Only reload when the window has not vetoed this
|
||||
this.lifecycleService.unload(win, UnloadReason.RELOAD).then(veto => {
|
||||
if (!veto) {
|
||||
win.reload(void 0, cli);
|
||||
|
||||
// Emit
|
||||
this._onWindowReload.fire(win.id);
|
||||
win.reload(undefined, cli);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1489,18 +1470,10 @@ export class WindowsManager implements IWindowsMainService {
|
||||
});
|
||||
}
|
||||
|
||||
saveAndEnterWorkspace(win: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
|
||||
return this.workspacesManager.saveAndEnterWorkspace(win, path).then(result => this.doEnterWorkspace(win, result));
|
||||
}
|
||||
|
||||
enterWorkspace(win: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
|
||||
enterWorkspace(win: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult> {
|
||||
return this.workspacesManager.enterWorkspace(win, path).then(result => this.doEnterWorkspace(win, result));
|
||||
}
|
||||
|
||||
createAndEnterWorkspace(win: ICodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
return this.workspacesManager.createAndEnterWorkspace(win, folders, path).then(result => this.doEnterWorkspace(win, result));
|
||||
}
|
||||
|
||||
private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
|
||||
|
||||
// Mark as recently opened
|
||||
@@ -1575,7 +1548,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
openNewWindow(context: OpenContext, options?: INewWindowOptions): ICodeWindow[] {
|
||||
let cli = this.environmentService.args;
|
||||
let remote = options && options.remoteAuthority || void 0;
|
||||
let remote = options && options.remoteAuthority || undefined;
|
||||
if (cli && (cli.remote !== remote)) {
|
||||
cli = { ...cli, remote };
|
||||
}
|
||||
@@ -1586,7 +1559,7 @@ export class WindowsManager implements IWindowsMainService {
|
||||
return this.open({ context, cli: this.environmentService.args, forceNewTabbedWindow: true, forceEmpty: true });
|
||||
}
|
||||
|
||||
waitForWindowCloseOrLoad(windowId: number): Thenable<void> {
|
||||
waitForWindowCloseOrLoad(windowId: number): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
function handler(id: number) {
|
||||
if (id === windowId) {
|
||||
@@ -1658,6 +1631,17 @@ export class WindowsManager implements IWindowsMainService {
|
||||
|
||||
// Unresponsive
|
||||
if (error === WindowError.UNRESPONSIVE) {
|
||||
if (window.isExtensionDevelopmentHost || window.isExtensionTestHost || (window.win && window.win.webContents && window.win.webContents.isDevToolsOpened())) {
|
||||
// TODO@Ben Workaround for https://github.com/Microsoft/vscode/issues/56994
|
||||
// In certain cases the window can report unresponsiveness because a breakpoint was hit
|
||||
// and the process is stopped executing. The most typical cases are:
|
||||
// - devtools are opened and debugging happens
|
||||
// - window is an extensions development host that is being debugged
|
||||
// - window is an extension test development host that is being debugged
|
||||
return;
|
||||
}
|
||||
|
||||
// Show Dialog
|
||||
this.dialogs.showMessageBox({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
@@ -1763,15 +1747,15 @@ export class WindowsManager implements IWindowsMainService {
|
||||
this.dialogs.pickAndOpen(internalOptions);
|
||||
}
|
||||
|
||||
showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): Thenable<IMessageBoxResult> {
|
||||
showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): Promise<IMessageBoxResult> {
|
||||
return this.dialogs.showMessageBox(options, win);
|
||||
}
|
||||
|
||||
showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): Thenable<string> {
|
||||
showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): Promise<string> {
|
||||
return this.dialogs.showSaveDialog(options, win);
|
||||
}
|
||||
|
||||
showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): Thenable<string[]> {
|
||||
showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): Promise<string[]> {
|
||||
return this.dialogs.showOpenDialog(options, win);
|
||||
}
|
||||
|
||||
@@ -1833,6 +1817,7 @@ class Dialogs {
|
||||
if (numberOfPaths) {
|
||||
this.windowsMainService.open({
|
||||
context: OpenContext.DIALOG,
|
||||
contextWindowId: options.windowId,
|
||||
cli: this.environmentService.args,
|
||||
urisToOpen: paths,
|
||||
forceNewWindow: options.forceNewWindow,
|
||||
@@ -1842,7 +1827,7 @@ class Dialogs {
|
||||
});
|
||||
}
|
||||
|
||||
private getFileOrFolderUris(options: IInternalNativeOpenDialogOptions): TPromise<URI[]> {
|
||||
private getFileOrFolderUris(options: IInternalNativeOpenDialogOptions): Promise<URI[]> {
|
||||
|
||||
// Ensure dialog options
|
||||
if (!options.dialogOptions) {
|
||||
@@ -1856,7 +1841,7 @@ class Dialogs {
|
||||
|
||||
// Ensure properties
|
||||
if (typeof options.pickFiles === 'boolean' || typeof options.pickFolders === 'boolean') {
|
||||
options.dialogOptions.properties = void 0; // let it override based on the booleans
|
||||
options.dialogOptions.properties = undefined; // let it override based on the booleans
|
||||
|
||||
if (options.pickFiles && options.pickFolders) {
|
||||
options.dialogOptions.properties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
|
||||
@@ -1883,7 +1868,7 @@ class Dialogs {
|
||||
return paths.map(path => URI.file(path));
|
||||
}
|
||||
|
||||
return void 0;
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1901,17 +1886,17 @@ class Dialogs {
|
||||
return windowDialogQueue;
|
||||
}
|
||||
|
||||
showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): Thenable<IMessageBoxResult> {
|
||||
showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): Promise<IMessageBoxResult> {
|
||||
return this.getDialogQueue(window).queue(() => {
|
||||
return new Promise(resolve => {
|
||||
dialog.showMessageBox(window ? window.win : void 0, options, (response: number, checkboxChecked: boolean) => {
|
||||
dialog.showMessageBox(window ? window.win : undefined, options, (response: number, checkboxChecked: boolean) => {
|
||||
resolve({ button: response, checkboxChecked });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
showSaveDialog(options: Electron.SaveDialogOptions, window?: ICodeWindow): Thenable<string> {
|
||||
showSaveDialog(options: Electron.SaveDialogOptions, window?: ICodeWindow): Promise<string> {
|
||||
|
||||
function normalizePath(path: string): string {
|
||||
if (path && isMacintosh) {
|
||||
@@ -1923,14 +1908,14 @@ class Dialogs {
|
||||
|
||||
return this.getDialogQueue(window).queue(() => {
|
||||
return new Promise(resolve => {
|
||||
dialog.showSaveDialog(window ? window.win : void 0, options, path => {
|
||||
dialog.showSaveDialog(window ? window.win : undefined, options, path => {
|
||||
resolve(normalizePath(path));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
showOpenDialog(options: Electron.OpenDialogOptions, window?: ICodeWindow): Thenable<string[]> {
|
||||
showOpenDialog(options: Electron.OpenDialogOptions, window?: ICodeWindow): Promise<string[]> {
|
||||
|
||||
function normalizePaths(paths: string[]): string[] {
|
||||
if (paths && paths.length > 0 && isMacintosh) {
|
||||
@@ -1948,14 +1933,14 @@ class Dialogs {
|
||||
if (options.defaultPath) {
|
||||
validatePathPromise = exists(options.defaultPath).then(exists => {
|
||||
if (!exists) {
|
||||
options.defaultPath = void 0;
|
||||
options.defaultPath = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show dialog and wrap as promise
|
||||
validatePathPromise.then(() => {
|
||||
dialog.showOpenDialog(window ? window.win : void 0, options, paths => {
|
||||
dialog.showOpenDialog(window ? window.win : undefined, options, paths => {
|
||||
resolve(normalizePaths(paths));
|
||||
});
|
||||
});
|
||||
@@ -1970,68 +1955,42 @@ class WorkspacesManager {
|
||||
private workspacesMainService: IWorkspacesMainService,
|
||||
private backupMainService: IBackupMainService,
|
||||
private environmentService: IEnvironmentService,
|
||||
private windowsMainService: IWindowsMainService
|
||||
private historyMainService: IHistoryMainService,
|
||||
private windowsMainService: IWindowsMainService,
|
||||
) {
|
||||
}
|
||||
|
||||
saveAndEnterWorkspace(window: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
|
||||
if (!window || !window.win || !window.isReady || !window.openedWorkspace || !path || !this.isValidTargetWorkspacePath(window, path)) {
|
||||
return TPromise.as(null); // return early if the window is not ready or disposed or does not have a workspace
|
||||
}
|
||||
|
||||
return this.doSaveAndOpenWorkspace(window, window.openedWorkspace, path);
|
||||
}
|
||||
|
||||
enterWorkspace(window: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
|
||||
enterWorkspace(window: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | null> {
|
||||
if (!window || !window.win || !window.isReady) {
|
||||
return TPromise.as(null); // return early if the window is not ready or disposed
|
||||
return Promise.resolve(null); // return early if the window is not ready or disposed
|
||||
}
|
||||
|
||||
return this.isValidTargetWorkspacePath(window, path).then(isValid => {
|
||||
if (!isValid) {
|
||||
return TPromise.as<IEnterWorkspaceResult>(null); // return early if the workspace is not valid
|
||||
return null; // return early if the workspace is not valid
|
||||
}
|
||||
|
||||
return this.workspacesMainService.resolveWorkspace(path).then(workspace => {
|
||||
return this.doOpenWorkspace(window, workspace);
|
||||
});
|
||||
const workspaceIdentifier = this.workspacesMainService.getWorkspaceIdentifier(path);
|
||||
return this.doOpenWorkspace(window, workspaceIdentifier);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
createAndEnterWorkspace(window: ICodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
if (!window || !window.win || !window.isReady) {
|
||||
return TPromise.as(null); // return early if the window is not ready or disposed
|
||||
}
|
||||
|
||||
return this.isValidTargetWorkspacePath(window, path).then(isValid => {
|
||||
if (!isValid) {
|
||||
return TPromise.as(null); // return early if the workspace is not valid
|
||||
}
|
||||
|
||||
return this.workspacesMainService.createWorkspace(folders).then(workspace => {
|
||||
return this.doSaveAndOpenWorkspace(window, workspace, path);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private isValidTargetWorkspacePath(window: ICodeWindow, path?: string): TPromise<boolean> {
|
||||
private isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise<boolean> {
|
||||
if (!path) {
|
||||
return TPromise.wrap(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
if (window.openedWorkspace && window.openedWorkspace.configPath === path) {
|
||||
return TPromise.wrap(false); // window is already opened on a workspace with that path
|
||||
if (window.openedWorkspace && isEqual(URI.file(window.openedWorkspace.configPath), path)) {
|
||||
return Promise.resolve(false); // window is already opened on a workspace with that path
|
||||
}
|
||||
|
||||
// Prevent overwriting a workspace that is currently opened in another window
|
||||
if (findWindowOnWorkspace(this.windowsMainService.getWindows(), { id: this.workspacesMainService.getWorkspaceId(path), configPath: path })) {
|
||||
if (findWindowOnWorkspace(this.windowsMainService.getWindows(), this.workspacesMainService.getWorkspaceIdentifier(path))) {
|
||||
const options: Electron.MessageBoxOptions = {
|
||||
title: product.nameLong,
|
||||
type: 'info',
|
||||
buttons: [localize('ok', "OK")],
|
||||
message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)),
|
||||
message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", resourcesBasename(path)),
|
||||
detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
|
||||
noLink: true
|
||||
};
|
||||
@@ -2039,18 +1998,7 @@ class WorkspacesManager {
|
||||
return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false);
|
||||
}
|
||||
|
||||
return TPromise.wrap(true); // OK
|
||||
}
|
||||
|
||||
private doSaveAndOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier, path?: string): TPromise<IEnterWorkspaceResult> {
|
||||
let savePromise: TPromise<IWorkspaceIdentifier>;
|
||||
if (path) {
|
||||
savePromise = this.workspacesMainService.saveWorkspace(workspace, path);
|
||||
} else {
|
||||
savePromise = TPromise.as(workspace);
|
||||
}
|
||||
|
||||
return savePromise.then(workspace => this.doOpenWorkspace(window, workspace));
|
||||
return Promise.resolve(true); // OK
|
||||
}
|
||||
|
||||
private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult {
|
||||
@@ -2062,8 +2010,13 @@ class WorkspacesManager {
|
||||
backupPath = this.backupMainService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
|
||||
}
|
||||
|
||||
// if the window was opened on an untitled workspace, delete it.
|
||||
if (window.openedWorkspace && this.workspacesMainService.isUntitledWorkspace(window.openedWorkspace)) {
|
||||
this.workspacesMainService.deleteUntitledWorkspaceSync(window.openedWorkspace);
|
||||
}
|
||||
|
||||
// Update window configuration properly based on transition to workspace
|
||||
window.config.folderUri = void 0;
|
||||
window.config.folderUri = undefined;
|
||||
window.config.workspace = workspace;
|
||||
window.config.backupPath = backupPath;
|
||||
|
||||
@@ -2074,7 +2027,7 @@ class WorkspacesManager {
|
||||
const window = this.windowsMainService.getWindowById(options.windowId) || this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
|
||||
|
||||
this.windowsMainService.pickFileAndOpen({
|
||||
windowId: window ? window.id : void 0,
|
||||
windowId: window ? window.id : undefined,
|
||||
dialogOptions: {
|
||||
buttonLabel: mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")),
|
||||
title: localize('openWorkspaceTitle', "Open Workspace"),
|
||||
@@ -2088,7 +2041,7 @@ class WorkspacesManager {
|
||||
});
|
||||
}
|
||||
|
||||
promptToSaveUntitledWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): TPromise<boolean> {
|
||||
promptToSaveUntitledWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): Promise<boolean> {
|
||||
enum ConfirmResult {
|
||||
SAVE,
|
||||
DONT_SAVE,
|
||||
@@ -2143,7 +2096,11 @@ class WorkspacesManager {
|
||||
defaultPath: this.getUntitledWorkspaceSaveDialogDefaultPath(workspace)
|
||||
}, window).then(target => {
|
||||
if (target) {
|
||||
return this.workspacesMainService.saveWorkspace(workspace, target).then(() => false, () => false);
|
||||
return this.workspacesMainService.saveWorkspaceAs(workspace, target).then(savedWorkspace => {
|
||||
this.historyMainService.addRecentlyOpened([savedWorkspace], []);
|
||||
this.workspacesMainService.deleteUntitledWorkspaceSync(workspace);
|
||||
return false;
|
||||
}, () => false);
|
||||
}
|
||||
|
||||
return true; // keep veto if no target was provided
|
||||
@@ -2156,7 +2113,7 @@ class WorkspacesManager {
|
||||
private getUntitledWorkspaceSaveDialogDefaultPath(workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string {
|
||||
if (workspace) {
|
||||
if (isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
return workspace.scheme === Schemas.file ? dirname(workspace.fsPath) : void 0;
|
||||
return workspace.scheme === Schemas.file ? dirname(workspace.fsPath) : undefined;
|
||||
}
|
||||
|
||||
const resolvedWorkspace = this.workspacesMainService.resolveWorkspaceSync(workspace.configPath);
|
||||
@@ -2169,6 +2126,6 @@ class WorkspacesManager {
|
||||
}
|
||||
}
|
||||
|
||||
return void 0;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { parseCLIProcessArgv, buildHelpMessage } from 'vs/platform/environment/node/argv';
|
||||
import { buildHelpMessage, buildVersionMessage } from 'vs/platform/environment/node/argv';
|
||||
import { ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import product from 'vs/platform/node/product';
|
||||
import pkg from 'vs/platform/node/package';
|
||||
@@ -20,6 +20,7 @@ import { writeFileAndFlushSync } from 'vs/base/node/extfs';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { ProfilingSession, Target } from 'v8-inspect-profiler';
|
||||
import { createWaitMarkerFile } from 'vs/code/node/wait';
|
||||
import { parseCLIProcessArgv } from 'vs/platform/environment/node/argvHelper';
|
||||
|
||||
function shouldSpawnCliProcess(argv: ParsedArgs): boolean {
|
||||
return !!argv['install-source']
|
||||
@@ -29,7 +30,7 @@ function shouldSpawnCliProcess(argv: ParsedArgs): boolean {
|
||||
}
|
||||
|
||||
interface IMainCli {
|
||||
main: (argv: ParsedArgs) => Thenable<void>;
|
||||
main: (argv: ParsedArgs) => Promise<void>;
|
||||
}
|
||||
|
||||
export async function main(argv: string[]): Promise<any> {
|
||||
@@ -44,12 +45,13 @@ export async function main(argv: string[]): Promise<any> {
|
||||
|
||||
// Help
|
||||
if (args.help) {
|
||||
console.log(buildHelpMessage(product.nameLong, product.applicationName, pkg.version));
|
||||
const executable = `${product.applicationName}${os.platform() === 'win32' ? '.exe' : ''}`;
|
||||
console.log(buildHelpMessage(product.nameLong, executable, pkg.version));
|
||||
}
|
||||
|
||||
// Version Info
|
||||
else if (args.version) {
|
||||
console.log(`${pkg.version}\n${product.commit}\n${process.arch}`);
|
||||
console.log(buildVersionMessage(pkg.version, product.commit));
|
||||
}
|
||||
|
||||
// Extensions Management
|
||||
@@ -121,7 +123,7 @@ export async function main(argv: string[]): Promise<any> {
|
||||
|
||||
delete env['ELECTRON_RUN_AS_NODE'];
|
||||
|
||||
const processCallbacks: ((child: ChildProcess) => Thenable<any>)[] = [];
|
||||
const processCallbacks: ((child: ChildProcess) => Promise<any>)[] = [];
|
||||
|
||||
const verbose = args.verbose || args.status || typeof args['upload-logs'] !== 'undefined';
|
||||
if (verbose) {
|
||||
@@ -206,14 +208,14 @@ export async function main(argv: string[]): Promise<any> {
|
||||
console.log(`Run with '${product.applicationName} -' to read from stdin (e.g. 'ps aux | grep code | ${product.applicationName} -').`);
|
||||
}
|
||||
|
||||
c(void 0);
|
||||
c(undefined);
|
||||
};
|
||||
|
||||
// wait for 1s maximum...
|
||||
setTimeout(() => {
|
||||
process.stdin.removeListener('data', dataListener);
|
||||
|
||||
c(void 0);
|
||||
c(undefined);
|
||||
}, 1000);
|
||||
|
||||
// ...but finish early if we detect data
|
||||
@@ -360,7 +362,7 @@ export async function main(argv: string[]): Promise<any> {
|
||||
return new Promise<void>(c => {
|
||||
|
||||
// Complete when process exits
|
||||
child.once('exit', () => c(void 0));
|
||||
child.once('exit', () => c(undefined));
|
||||
|
||||
// Complete when wait marker file is deleted
|
||||
whenDeleted(waitMarkerFilePath!).then(c, c);
|
||||
|
||||
@@ -9,7 +9,6 @@ import pkg from 'vs/platform/node/package';
|
||||
import * as path from 'path';
|
||||
import * as semver from 'semver';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { sequence } from 'vs/base/common/async';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
@@ -17,7 +16,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { IExtensionManagementService, IExtensionGalleryService, IExtensionManifest, IGalleryExtension, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionManagementService, IExtensionGalleryService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
|
||||
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
@@ -36,9 +35,10 @@ import { StateService } from 'vs/platform/state/node/stateService';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { areSameExtensions, getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { areSameExtensions, adoptToGalleryExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
|
||||
import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id);
|
||||
const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id);
|
||||
@@ -54,161 +54,164 @@ function getId(manifest: IExtensionManifest, withVersion?: boolean): string {
|
||||
|
||||
const EXTENSION_ID_REGEX = /^([^.]+\..+)@(\d+\.\d+\.\d+(-.*)?)$/;
|
||||
|
||||
export function getIdAndVersion(id: string): [string, string] {
|
||||
export function getIdAndVersion(id: string): [string, string | undefined] {
|
||||
const matches = EXTENSION_ID_REGEX.exec(id);
|
||||
if (matches && matches[1]) {
|
||||
return [adoptToGalleryExtensionId(matches[1]), matches[2]];
|
||||
}
|
||||
return [adoptToGalleryExtensionId(id), void 0];
|
||||
return [adoptToGalleryExtensionId(id), undefined];
|
||||
}
|
||||
|
||||
|
||||
type Task = { (): TPromise<void> };
|
||||
|
||||
class Main {
|
||||
export class Main {
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService
|
||||
) { }
|
||||
|
||||
run(argv: ParsedArgs): TPromise<any> {
|
||||
// TODO@joao - make this contributable
|
||||
|
||||
let returnPromise: TPromise<any>;
|
||||
async run(argv: ParsedArgs): Promise<any> {
|
||||
if (argv['install-source']) {
|
||||
returnPromise = this.setInstallSource(argv['install-source']);
|
||||
await this.setInstallSource(argv['install-source']);
|
||||
|
||||
} else if (argv['list-extensions']) {
|
||||
returnPromise = this.listExtensions(argv['show-versions']);
|
||||
await this.listExtensions(!!argv['show-versions']);
|
||||
|
||||
} else if (argv['install-extension']) {
|
||||
const arg = argv['install-extension'];
|
||||
const args: string[] = typeof arg === 'string' ? [arg] : arg;
|
||||
returnPromise = this.installExtension(args, argv['force']);
|
||||
await this.installExtensions(args, argv['force']);
|
||||
|
||||
} else if (argv['uninstall-extension']) {
|
||||
const arg = argv['uninstall-extension'];
|
||||
const ids: string[] = typeof arg === 'string' ? [arg] : arg;
|
||||
returnPromise = this.uninstallExtension(ids);
|
||||
await this.uninstallExtension(ids);
|
||||
}
|
||||
return returnPromise || TPromise.as(null);
|
||||
}
|
||||
|
||||
private setInstallSource(installSource: string): TPromise<any> {
|
||||
private setInstallSource(installSource: string): Promise<any> {
|
||||
return writeFile(this.environmentService.installSourcePath, installSource.slice(0, 30));
|
||||
}
|
||||
|
||||
private listExtensions(showVersions: boolean): TPromise<any> {
|
||||
return this.extensionManagementService.getInstalled(LocalExtensionType.User).then(extensions => {
|
||||
extensions.forEach(e => console.log(getId(e.manifest, showVersions)));
|
||||
});
|
||||
private async listExtensions(showVersions: boolean): Promise<any> {
|
||||
const extensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
|
||||
extensions.forEach(e => console.log(getId(e.manifest, showVersions)));
|
||||
}
|
||||
|
||||
private installExtension(extensions: string[], force: boolean): TPromise<any> {
|
||||
const vsixTasks: Task[] = extensions
|
||||
.filter(e => /\.vsix$/i.test(e))
|
||||
.map(id => () => {
|
||||
const extension = path.isAbsolute(id) ? id : path.join(process.cwd(), id);
|
||||
return this.validate(extension, force)
|
||||
.then(valid => {
|
||||
if (valid) {
|
||||
return this.extensionManagementService.install(URI.file(extension)).then(() => {
|
||||
console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed!", getBaseLabel(extension)));
|
||||
}, error => {
|
||||
if (isPromiseCanceledError(error)) {
|
||||
console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", getBaseLabel(extension)));
|
||||
return null;
|
||||
} else {
|
||||
return TPromise.wrapError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
return null;
|
||||
});
|
||||
});
|
||||
private async installExtensions(extensions: string[], force: boolean): Promise<void> {
|
||||
let failed: string[] = [];
|
||||
for (const extension of extensions) {
|
||||
try {
|
||||
await this.installExtension(extension, force);
|
||||
} catch (err) {
|
||||
console.error(err.message || err.stack || err);
|
||||
failed.push(extension);
|
||||
}
|
||||
}
|
||||
return failed.length ? Promise.reject(localize('installation failed', "Failed Installing Extensions: {0}", failed.join(', '))) : Promise.resolve();
|
||||
}
|
||||
|
||||
const galleryTasks: Task[] = extensions
|
||||
.filter(e => !/\.vsix$/i.test(e))
|
||||
.map(e => () => {
|
||||
const [id, version] = getIdAndVersion(e);
|
||||
return this.extensionManagementService.getInstalled(LocalExtensionType.User)
|
||||
.then(installed => this.extensionGalleryService.getExtension({ id }, version)
|
||||
.then<IGalleryExtension>(null, err => {
|
||||
if (err.responseText) {
|
||||
try {
|
||||
const response = JSON.parse(err.responseText);
|
||||
return TPromise.wrapError(response.message);
|
||||
} catch (e) {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
return TPromise.wrapError(err);
|
||||
})
|
||||
.then(extension => {
|
||||
if (!extension) {
|
||||
return TPromise.wrapError(new Error(`${notFound(version ? `${id}@${version}` : id)}\n${useId}`));
|
||||
}
|
||||
private installExtension(extension: string, force: boolean): Promise<any> {
|
||||
if (/\.vsix$/i.test(extension)) {
|
||||
extension = path.isAbsolute(extension) ? extension : path.join(process.cwd(), extension);
|
||||
|
||||
const [installedExtension] = installed.filter(e => areSameExtensions({ id: getGalleryExtensionIdFromLocal(e) }, { id }));
|
||||
if (installedExtension) {
|
||||
if (extension.version !== installedExtension.manifest.version) {
|
||||
if (version || force) {
|
||||
console.log(localize('updateMessage', "Updating the Extension '{0}' to the version {1}", id, extension.version));
|
||||
return this.installFromGallery(id, extension);
|
||||
} else {
|
||||
console.log(localize('forceUpdate', "Extension '{0}' v{1} is already installed, but a newer version {2} is available in the marketplace. Use '--force' option to update to newer version.", id, installedExtension.manifest.version, extension.version));
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
} else {
|
||||
console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id));
|
||||
return TPromise.as(null);
|
||||
}
|
||||
return this.validate(extension, force)
|
||||
.then(valid => {
|
||||
if (valid) {
|
||||
return this.extensionManagementService.install(URI.file(extension)).then(() => {
|
||||
console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed!", getBaseLabel(extension)));
|
||||
}, error => {
|
||||
if (isPromiseCanceledError(error)) {
|
||||
console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", getBaseLabel(extension)));
|
||||
return null;
|
||||
} else {
|
||||
console.log(localize('foundExtension', "Found '{0}' in the marketplace.", id));
|
||||
return this.installFromGallery(id, extension);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
return sequence([...vsixTasks, ...galleryTasks]);
|
||||
}
|
||||
|
||||
private validate(vsix: string, force: boolean): Thenable<boolean> {
|
||||
return getManifest(vsix)
|
||||
.then(manifest => {
|
||||
if (manifest) {
|
||||
const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
|
||||
return this.extensionManagementService.getInstalled(LocalExtensionType.User)
|
||||
.then(installedExtensions => {
|
||||
const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, { id: getGalleryExtensionIdFromLocal(local) }) && semver.gt(local.manifest.version, manifest.version))[0];
|
||||
if (newer && !force) {
|
||||
console.log(localize('forceDowngrade', "A newer version of this extension '{0}' v{1} is already installed. Use '--force' option to downgrade to older version.", newer.galleryIdentifier.id, newer.manifest.version, manifest.version));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
return Promise.reject(new Error('Invalid vsix'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private installFromGallery(id: string, extension: IGalleryExtension): TPromise<void> {
|
||||
console.log(localize('installing', "Installing..."));
|
||||
return this.extensionManagementService.installFromGallery(extension)
|
||||
.then(
|
||||
() => console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed!", id, extension.version)),
|
||||
error => {
|
||||
if (isPromiseCanceledError(error)) {
|
||||
console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", id));
|
||||
return null;
|
||||
} else {
|
||||
return TPromise.wrapError(error);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
const [id, version] = getIdAndVersion(extension);
|
||||
return this.extensionManagementService.getInstalled(ExtensionType.User)
|
||||
.then(installed => this.extensionGalleryService.getCompatibleExtension({ id }, version)
|
||||
.then<IGalleryExtension>(null, err => {
|
||||
if (err.responseText) {
|
||||
try {
|
||||
const response = JSON.parse(err.responseText);
|
||||
return Promise.reject(response.message);
|
||||
} catch (e) {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
return Promise.reject(err);
|
||||
})
|
||||
.then(extension => {
|
||||
if (!extension) {
|
||||
return Promise.reject(new Error(`${notFound(version ? `${id}@${version}` : id)}\n${useId}`));
|
||||
}
|
||||
|
||||
const [installedExtension] = installed.filter(e => areSameExtensions(e.identifier, { id }));
|
||||
if (installedExtension) {
|
||||
if (extension.version !== installedExtension.manifest.version) {
|
||||
if (version || force) {
|
||||
console.log(localize('updateMessage', "Updating the Extension '{0}' to the version {1}", id, extension.version));
|
||||
return this.installFromGallery(id, extension);
|
||||
} else {
|
||||
console.log(localize('forceUpdate', "Extension '{0}' v{1} is already installed, but a newer version {2} is available in the marketplace. Use '--force' option to update to newer version.", id, installedExtension.manifest.version, extension.version));
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
} else {
|
||||
console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id));
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
} else {
|
||||
console.log(localize('foundExtension', "Found '{0}' in the marketplace.", id));
|
||||
return this.installFromGallery(id, extension);
|
||||
}
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
private uninstallExtension(extensions: string[]): TPromise<any> {
|
||||
|
||||
|
||||
private async validate(vsix: string, force: boolean): Promise<boolean> {
|
||||
const manifest = await getManifest(vsix);
|
||||
|
||||
if (!manifest) {
|
||||
throw new Error('Invalid vsix');
|
||||
}
|
||||
|
||||
const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
|
||||
const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, local.identifier) && semver.gt(local.manifest.version, manifest.version))[0];
|
||||
|
||||
if (newer && !force) {
|
||||
console.log(localize('forceDowngrade', "A newer version of this extension '{0}' v{1} is already installed. Use '--force' option to downgrade to older version.", newer.identifier.id, newer.manifest.version, manifest.version));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async installFromGallery(id: string, extension: IGalleryExtension): Promise<void> {
|
||||
console.log(localize('installing', "Installing..."));
|
||||
|
||||
try {
|
||||
await this.extensionManagementService.installFromGallery(extension);
|
||||
console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed!", id, extension.version));
|
||||
} catch (error) {
|
||||
if (isPromiseCanceledError(error)) {
|
||||
console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", id));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private uninstallExtension(extensions: string[]): Promise<any> {
|
||||
async function getExtensionId(extensionDescription: string): Promise<string> {
|
||||
if (!/\.vsix$/i.test(extensionDescription)) {
|
||||
return extensionDescription;
|
||||
@@ -221,11 +224,11 @@ class Main {
|
||||
|
||||
return sequence(extensions.map(extension => () => {
|
||||
return getExtensionId(extension).then(id => {
|
||||
return this.extensionManagementService.getInstalled(LocalExtensionType.User).then(installed => {
|
||||
const [extension] = installed.filter(e => areSameExtensions({ id: getGalleryExtensionIdFromLocal(e) }, { id }));
|
||||
return this.extensionManagementService.getInstalled(ExtensionType.User).then(installed => {
|
||||
const [extension] = installed.filter(e => areSameExtensions(e.identifier, { id }));
|
||||
|
||||
if (!extension) {
|
||||
return TPromise.wrapError(new Error(`${notInstalled(id)}\n${useId}`));
|
||||
return Promise.reject(new Error(`${notInstalled(id)}\n${useId}`));
|
||||
}
|
||||
|
||||
console.log(localize('uninstalling', "Uninstalling {0}...", id));
|
||||
@@ -240,7 +243,7 @@ class Main {
|
||||
|
||||
const eventPrefix = 'monacoworkbench';
|
||||
|
||||
export function main(argv: ParsedArgs): TPromise<void> {
|
||||
export function main(argv: ParsedArgs): Promise<void> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
const environmentService = new EnvironmentService(argv, process.execPath);
|
||||
@@ -259,13 +262,13 @@ export function main(argv: ParsedArgs): TPromise<void> {
|
||||
const envService = accessor.get(IEnvironmentService);
|
||||
const stateService = accessor.get(IStateService);
|
||||
|
||||
return TPromise.join([envService.appSettingsHome, envService.extensionsPath].map(p => mkdirp(p))).then(() => {
|
||||
return Promise.all([envService.appSettingsHome, envService.extensionsPath].map(p => mkdirp(p))).then(() => {
|
||||
const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = envService;
|
||||
|
||||
const services = new ServiceCollection();
|
||||
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
|
||||
services.set(IRequestService, new SyncDescriptor(RequestService));
|
||||
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
|
||||
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService, [false]));
|
||||
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
|
||||
|
||||
const appenders: AppInsightsAppender[] = [];
|
||||
|
||||
@@ -119,8 +119,8 @@ export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn
|
||||
|
||||
return {
|
||||
path: path,
|
||||
line: line !== null ? line : void 0,
|
||||
column: column !== null ? column : line !== null ? 1 : void 0 // if we have a line, make sure column is also set
|
||||
line: line !== null ? line : undefined,
|
||||
column: column !== null ? column : line !== null ? 1 : undefined // if we have a line, make sure column is also set
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,6 @@ export function createWaitMarkerFile(verbose?: boolean): Promise<string> {
|
||||
console.error(`Failed to create marker file for --wait: ${error}`);
|
||||
}
|
||||
|
||||
return Promise.resolve(void 0);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export interface IBestWindowOrFolderOptions<W extends ISimpleWindow> {
|
||||
fileUri?: URI;
|
||||
userHome?: string;
|
||||
codeSettingsFolder?: string;
|
||||
workspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace;
|
||||
workspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null;
|
||||
}
|
||||
|
||||
export function findBestWindowOrFolderForFile<W extends ISimpleWindow>({ windows, newWindow, reuseWindow, context, fileUri, workspaceResolver }: IBestWindowOrFolderOptions<W>): W | null {
|
||||
@@ -39,12 +39,11 @@ export function findBestWindowOrFolderForFile<W extends ISimpleWindow>({ windows
|
||||
return !newWindow ? getLastActiveWindow(windows) : null;
|
||||
}
|
||||
|
||||
function findWindowOnFilePath<W extends ISimpleWindow>(windows: W[], fileUri: URI, workspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace): W | null {
|
||||
function findWindowOnFilePath<W extends ISimpleWindow>(windows: W[], fileUri: URI, workspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null): W | null {
|
||||
|
||||
// First check for windows with workspaces that have a parent folder of the provided path opened
|
||||
const workspaceWindows = windows.filter(window => !!window.openedWorkspace);
|
||||
for (let i = 0; i < workspaceWindows.length; i++) {
|
||||
const window = workspaceWindows[i];
|
||||
for (const window of workspaceWindows) {
|
||||
const resolvedWorkspace = workspaceResolver(window.openedWorkspace!);
|
||||
if (resolvedWorkspace && resolvedWorkspace.folders.some(folder => isEqualOrParent(fileUri, folder.uri))) {
|
||||
return window;
|
||||
|
||||
@@ -3,37 +3,55 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { formatOptions } from 'vs/platform/environment/node/argv';
|
||||
import { formatOptions, Option } from 'vs/platform/environment/node/argv';
|
||||
|
||||
suite('formatOptions', () => {
|
||||
|
||||
function o(id: string, description: string): Option {
|
||||
return {
|
||||
id, description, type: 'string'
|
||||
};
|
||||
}
|
||||
|
||||
test('Text should display small columns correctly', () => {
|
||||
assert.equal(formatOptions({ 'foo': 'bar' }, 80), ' foo bar');
|
||||
assert.equal(
|
||||
formatOptions({
|
||||
'f': 'bar',
|
||||
'fo': 'ba',
|
||||
'foo': 'b'
|
||||
}, 80),
|
||||
' f bar\n' +
|
||||
' fo ba\n' +
|
||||
' foo b');
|
||||
assert.deepEqual(
|
||||
formatOptions([
|
||||
o('foo', 'bar')
|
||||
], 80),
|
||||
[' --foo bar']
|
||||
);
|
||||
assert.deepEqual(
|
||||
formatOptions([
|
||||
o('f', 'bar'),
|
||||
o('fo', 'ba'),
|
||||
o('foo', 'b')
|
||||
], 80),
|
||||
[
|
||||
' --f bar',
|
||||
' --fo ba',
|
||||
' --foo b'
|
||||
]);
|
||||
});
|
||||
|
||||
test('Text should wrap', () => {
|
||||
assert.equal(
|
||||
formatOptions({
|
||||
'foo': (<any>'bar ').repeat(9)
|
||||
}, 40),
|
||||
' foo bar bar bar bar bar bar bar bar\n' +
|
||||
' bar');
|
||||
assert.deepEqual(
|
||||
formatOptions([
|
||||
o('foo', (<any>'bar ').repeat(9))
|
||||
], 40),
|
||||
[
|
||||
' --foo bar bar bar bar bar bar bar bar',
|
||||
' bar'
|
||||
]);
|
||||
});
|
||||
|
||||
test('Text should revert to the condensed view when the terminal is too narrow', () => {
|
||||
assert.equal(
|
||||
formatOptions({
|
||||
'foo': (<any>'bar ').repeat(9)
|
||||
}, 30),
|
||||
' foo\n' +
|
||||
' bar bar bar bar bar bar bar bar bar ');
|
||||
assert.deepEqual(
|
||||
formatOptions([
|
||||
o('foo', (<any>'bar ').repeat(9))
|
||||
], 30),
|
||||
[
|
||||
' --foo',
|
||||
' bar bar bar bar bar bar bar bar bar '
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,13 +25,13 @@ function options(custom?: Partial<IBestWindowOrFolderOptions<ISimpleWindow>>): I
|
||||
reuseWindow: false,
|
||||
context: OpenContext.CLI,
|
||||
codeSettingsFolder: '_vscode',
|
||||
workspaceResolver: workspace => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: toWorkspaceFolders([{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }]) } : null; },
|
||||
workspaceResolver: workspace => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: toWorkspaceFolders([{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }]) } : null!; },
|
||||
...custom
|
||||
};
|
||||
}
|
||||
|
||||
const vscodeFolderWindow: ISimpleWindow = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder')) };
|
||||
const lastActiveWindow: ISimpleWindow = { lastFocusTime: 3, openedFolderUri: null };
|
||||
const lastActiveWindow: ISimpleWindow = { lastFocusTime: 3, openedFolderUri: undefined };
|
||||
const noVscodeFolderWindow: ISimpleWindow = { lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) };
|
||||
const windows: ISimpleWindow[] = [
|
||||
vscodeFolderWindow,
|
||||
|
||||
Reference in New Issue
Block a user