diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 6b43542afb..61ab75c22a 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -77,7 +77,6 @@ const vscodeResources = [ 'out-build/vs/code/electron-browser/workbench/**', 'out-build/vs/code/electron-browser/sharedProcess/sharedProcess.js', 'out-build/vs/code/electron-browser/issue/issueReporter.js', - 'out-build/vs/code/electron-browser/processExplorer/processExplorer.js', 'out-build/sql/workbench/electron-browser/splashscreen/*', // {{SQL CARBON EDIT}} STart 'out-build/sql/**/*.{svg,png,cur,html}', 'out-build/sql/base/browser/ui/table/media/*.{gif,png,svg}', diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index 477d530a0e..3cadb80fd3 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -54,7 +54,7 @@ "url": "vscode://schemas/keybindings" }, { - "fileMatch": "vscode://defaultsettings/*/*.json", + "fileMatch": "vscode://defaultsettings/defaultSettings.json", "url": "vscode://schemas/settings/default" }, { diff --git a/extensions/markdown-language-features/media/index.js b/extensions/markdown-language-features/media/index.js index 4075748e35..0433f33c89 100644 --- a/extensions/markdown-language-features/media/index.js +++ b/extensions/markdown-language-features/media/index.js @@ -1 +1 @@ -!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=3)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let o=void 0;function i(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const n=t.getAttribute(e);if(n)return JSON.parse(n)}throw new Error(`Could not load data for ${e}`)}t.getData=i,t.getSettings=function(){if(o)return o;if(o=i("data-settings"))return o;throw new Error("Could not load settings")}},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0),i="code-line";function r(e){return t=0,n=o.getSettings().lineCount-1,i=e,Math.min(n,Math.max(t,i));var t,n,i}const c=(()=>{let e;return()=>{if(!e){e=[{element:document.body,line:0}];for(const t of document.getElementsByClassName(i)){const n=+t.getAttribute("data-line");isNaN(n)||("CODE"===t.tagName&&t.parentElement&&"PRE"===t.parentElement.tagName?e.push({element:t.parentElement,line:n}):e.push({element:t,line:n}))}}return e}})();function s(e){const t=Math.floor(e),n=c();let o=n[0]||null;for(const e of n){if(e.line===t)return{previous:e,next:void 0};if(e.line>t)return{previous:o,next:e};o=e}return{previous:o}}function a(e){const t=c(),n=e-window.scrollY;let o=-1,i=t.length-1;for(;o+1=n?i=e:o=e}const r=t[i],s=u(r);if(i>=1&&s.top>n){return{previous:t[o],next:r}}return i>1&&in?{previous:r,next:t[i+1]}:{previous:r}}function u({element:e}){const t=e.getBoundingClientRect(),n=e.querySelector(`.${i}`);if(n){const e=n.getBoundingClientRect(),o=Math.max(1,e.top-t.top);return{top:t.top,height:o}}return t}t.getElementsForSourceLine=s,t.getLineElementsAtPageOffset=a,t.scrollToRevealSourceLine=function(e){if(!o.getSettings().scrollPreviewWithEditor)return;if(e<=0)return void window.scroll(window.scrollX,0);const{previous:t,next:n}=s(e);if(!t)return;let i=0;const r=u(t),c=r.top;if(n&&n.line!==t.line){i=c+(e-t.line)/(n.line-t.line)*(n.element.getBoundingClientRect().top-c)}else{const t=e-Math.floor(e);i=c+r.height*t}window.scroll(window.scrollX,Math.max(1,window.scrollY+i))},t.getEditorLineNumberForPageOffset=function(e){const{previous:t,next:n}=a(e);if(t){const o=u(t),i=e-window.scrollY-o.top;if(n){const e=i/(u(n).top-o.top);return r(t.line+e*(n.line-t.line))}{const e=i/o.height;return r(t.line+e)}}return null},t.getLineElementForFragment=function(e){return c().find(t=>t.element.id===e)}},function(e,t,n){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});const o=n(7),i=n(8),r=n(9),c=n(2),s=n(0),a=n(10);let u=!0;const l=new o.ActiveLineMarker,f=s.getSettings(),d=acquireVsCodeApi(),m=d.getState(),p={..."object"==typeof m?m:{},...s.getData("data-state")};d.setState(p);const g=r.createPosterForVsCode(d);window.cspAlerter.setPoster(g),window.styleLoadingMonitor.setPoster(g),window.onload=()=>{v()},i.onceDocumentLoaded(()=>{const t=p.scrollProgress;"number"!=typeof t||f.fragment?f.scrollPreviewWithEditor&&e(()=>{if(f.fragment){p.fragment=void 0,d.setState(p);const e=c.getLineElementForFragment(f.fragment);e&&(u=!0,c.scrollToRevealSourceLine(e.line))}else isNaN(f.line)||(u=!0,c.scrollToRevealSourceLine(f.line))}):e(()=>{u=!0,window.scrollTo(0,t*document.body.clientHeight)})});const h=(()=>{const e=a(e=>{u=!0,c.scrollToRevealSourceLine(e)},50);return t=>{isNaN(t)||(p.line=t,e(t))}})();let v=a(()=>{const e=[];let t=document.getElementsByTagName("img");if(t){let n;for(n=0;n{u=!0,w(),v()},!0),window.addEventListener("message",e=>{if(e.data.source===f.source)switch(e.data.type){case"onDidChangeTextEditorSelection":l.onDidChangeTextEditorSelection(e.data.line);break;case"updateView":h(e.data.line)}},!1),document.addEventListener("dblclick",e=>{if(!f.doubleClickToSwitchToEditor)return;for(let t=e.target;t;t=t.parentNode)if("A"===t.tagName)return;const t=e.pageY,n=c.getEditorLineNumberForPageOffset(t);"number"!=typeof n||isNaN(n)||g.postMessage("didClick",{line:Math.floor(n)})});const y=["http:","https:","mailto:","vscode:","vscode-insiders:"];function w(){p.scrollProgress=window.scrollY/document.body.clientHeight,d.setState(p)}document.addEventListener("click",e=>{if(!e)return;let t=e.target;for(;t;){if(t.tagName&&"A"===t.tagName&&t.href){if(t.getAttribute("href").startsWith("#"))return;if(y.some(e=>t.href.startsWith(e)))return;const n=t.getAttribute("data-href")||t.getAttribute("href");return/^[a-z\-]+:/i.test(n)?void 0:(g.postMessage("openLink",{href:n}),e.preventDefault(),void e.stopPropagation())}t=t.parentNode}},!0),window.addEventListener("scroll",a(()=>{if(w(),u)u=!1;else{const e=c.getEditorLineNumberForPageOffset(window.scrollY);"number"!=typeof e||isNaN(e)||g.postMessage("revealLine",{line:e})}},50))}).call(this,n(4).setImmediate)},function(e,t,n){(function(e){var o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,window,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,window,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(window,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout((function(){e._onTimeout&&e._onTimeout()}),t))},n(5),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(1))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var o,i,r,c,s,a=1,u={},l=!1,f=e.document,d=Object.getPrototypeOf&&Object.getPrototypeOf(e);d=d&&d.setTimeout?d:e,"[object process]"==={}.toString.call(e.process)?o=function(e){t.nextTick((function(){p(e)}))}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((r=new MessageChannel).port1.onmessage=function(e){p(e.data)},o=function(e){r.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(i=f.documentElement,o=function(e){var t=f.createElement("script");t.onreadystatechange=function(){p(e),t.onreadystatechange=null,i.removeChild(t),t=null},i.appendChild(t)}):o=function(e){setTimeout(p,0,e)}:(c="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(c)&&p(+t.data.slice(c.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),o=function(t){e.postMessage(c+t,"*")}),d.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n1)for(var n=1;nnew class{postMessage(t,n){e.postMessage({type:t,source:o.getSettings().source,body:n})}}},function(e,t,n){(function(t){var n="Expected a function",o=NaN,i="[object Symbol]",r=/^\s+|\s+$/g,c=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,a=/^0o[0-7]+$/i,u=parseInt,l="object"==typeof t&&t&&t.Object===Object&&t,f="object"==typeof self&&self&&self.Object===Object&&self,d=l||f||Function("return this")(),m=Object.prototype.toString,p=Math.max,g=Math.min,h=function(){return d.Date.now()};function v(e,t,o){var i,r,c,s,a,u,l=0,f=!1,d=!1,m=!0;if("function"!=typeof e)throw new TypeError(n);function v(t){var n=i,o=r;return i=r=void 0,l=t,s=e.apply(o,n)}function b(e){var n=e-u;return void 0===u||n>=t||n<0||d&&e-l>=c}function T(){var e=h();if(b(e))return E(e);a=setTimeout(T,function(e){var n=t-(e-u);return d?g(n,c-(e-l)):n}(e))}function E(e){return a=void 0,m&&i?v(e):(i=r=void 0,s)}function _(){var e=h(),n=b(e);if(i=arguments,r=this,u=e,n){if(void 0===a)return function(e){return l=e,a=setTimeout(T,t),f?v(e):s}(u);if(d)return a=setTimeout(T,t),v(u)}return void 0===a&&(a=setTimeout(T,t)),s}return t=w(t)||0,y(o)&&(f=!!o.leading,c=(d="maxWait"in o)?p(w(o.maxWait)||0,t):c,m="trailing"in o?!!o.trailing:m),_.cancel=function(){void 0!==a&&clearTimeout(a),l=0,i=u=r=a=void 0},_.flush=function(){return void 0===a?s:E(h())},_}function y(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function w(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&m.call(e)==i}(e))return o;if(y(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=y(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(r,"");var n=s.test(e);return n||a.test(e)?u(e.slice(2),n?2:8):c.test(e)?o:+e}e.exports=function(e,t,o){var i=!0,r=!0;if("function"!=typeof e)throw new TypeError(n);return y(o)&&(i="leading"in o?!!o.leading:i,r="trailing"in o?!!o.trailing:r),v(e,t,{leading:i,maxWait:t,trailing:r})}}).call(this,n(1))}]); \ No newline at end of file +!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=3)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});let o=void 0;function i(e){const t=document.getElementById("vscode-markdown-preview-data");if(t){const n=t.getAttribute(e);if(n)return JSON.parse(n)}throw new Error(`Could not load data for ${e}`)}t.getData=i,t.getSettings=function(){if(o)return o;if(o=i("data-settings"))return o;throw new Error("Could not load settings")}},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(0),i="code-line";function r(e){return t=0,n=o.getSettings().lineCount-1,i=e,Math.min(n,Math.max(t,i));var t,n,i}const c=(()=>{let e;return()=>{if(!e){e=[{element:document.body,line:0}];for(const t of document.getElementsByClassName(i)){const n=+t.getAttribute("data-line");isNaN(n)||("CODE"===t.tagName&&t.parentElement&&"PRE"===t.parentElement.tagName?e.push({element:t.parentElement,line:n}):e.push({element:t,line:n}))}}return e}})();function s(e){const t=Math.floor(e),n=c();let o=n[0]||null;for(const e of n){if(e.line===t)return{previous:e,next:void 0};if(e.line>t)return{previous:o,next:e};o=e}return{previous:o}}function a(e){const t=c(),n=e-window.scrollY;let o=-1,i=t.length-1;for(;o+1=n?i=e:o=e}const r=t[i],s=u(r);if(i>=1&&s.top>n){return{previous:t[o],next:r}}return i>1&&in?{previous:r,next:t[i+1]}:{previous:r}}function u({element:e}){const t=e.getBoundingClientRect(),n=e.querySelector(`.${i}`);if(n){const e=n.getBoundingClientRect(),o=Math.max(1,e.top-t.top);return{top:t.top,height:o}}return t}t.getElementsForSourceLine=s,t.getLineElementsAtPageOffset=a,t.scrollToRevealSourceLine=function(e){if(!o.getSettings().scrollPreviewWithEditor)return;if(e<=0)return void window.scroll(window.scrollX,0);const{previous:t,next:n}=s(e);if(!t)return;let i=0;const r=u(t),c=r.top;if(n&&n.line!==t.line){i=c+(e-t.line)/(n.line-t.line)*(n.element.getBoundingClientRect().top-c)}else{const t=e-Math.floor(e);i=c+r.height*t}window.scroll(window.scrollX,Math.max(1,window.scrollY+i))},t.getEditorLineNumberForPageOffset=function(e){const{previous:t,next:n}=a(e);if(t){const o=u(t),i=e-window.scrollY-o.top;if(n){const e=i/(u(n).top-o.top);return r(t.line+e*(n.line-t.line))}{const e=i/o.height;return r(t.line+e)}}return null},t.getLineElementForFragment=function(e){return c().find(t=>t.element.id===e)}},function(e,t,n){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0});const o=n(7),i=n(8),r=n(9),c=n(2),s=n(0),a=n(10);let u=!0;const l=new o.ActiveLineMarker,f=s.getSettings(),d=acquireVsCodeApi(),m=d.getState(),p={..."object"==typeof m?m:{},...s.getData("data-state")};d.setState(p);const g=r.createPosterForVsCode(d);window.cspAlerter.setPoster(g),window.styleLoadingMonitor.setPoster(g),window.onload=()=>{v()},i.onceDocumentLoaded(()=>{const t=p.scrollProgress;"number"!=typeof t||f.fragment?f.scrollPreviewWithEditor&&e(()=>{if(f.fragment){p.fragment=void 0,d.setState(p);const e=c.getLineElementForFragment(f.fragment);e&&(u=!0,c.scrollToRevealSourceLine(e.line))}else isNaN(f.line)||(u=!0,c.scrollToRevealSourceLine(f.line))}):e(()=>{u=!0,window.scrollTo(0,t*document.body.clientHeight)})});const h=(()=>{const e=a(e=>{u=!0,c.scrollToRevealSourceLine(e)},50);return t=>{isNaN(t)||(p.line=t,e(t))}})();let v=a(()=>{const e=[];let t=document.getElementsByTagName("img");if(t){let n;for(n=0;n{u=!0,w(),v()},!0),window.addEventListener("message",e=>{if(e.data.source===f.source)switch(e.data.type){case"onDidChangeTextEditorSelection":l.onDidChangeTextEditorSelection(e.data.line);break;case"updateView":h(e.data.line)}},!1),document.addEventListener("dblclick",e=>{if(!f.doubleClickToSwitchToEditor)return;for(let t=e.target;t;t=t.parentNode)if("A"===t.tagName)return;const t=e.pageY,n=c.getEditorLineNumberForPageOffset(t);"number"!=typeof n||isNaN(n)||g.postMessage("didClick",{line:Math.floor(n)})});const y=["http:","https:","mailto:","vscode:","vscode-insiders:"];function w(){p.scrollProgress=window.scrollY/document.body.clientHeight,d.setState(p)}document.addEventListener("click",e=>{if(!e)return;let t=e.target;for(;t;){if(t.tagName&&"A"===t.tagName&&t.href){if(t.getAttribute("href").startsWith("#"))return;let n=t.getAttribute("data-href");if(!n){if(y.some(e=>t.href.startsWith(e)))return;n=t.getAttribute("href")}return/^[a-z\-]+:/i.test(n)?void 0:(g.postMessage("openLink",{href:n}),e.preventDefault(),void e.stopPropagation())}t=t.parentNode}},!0),window.addEventListener("scroll",a(()=>{if(w(),u)u=!1;else{const e=c.getEditorLineNumberForPageOffset(window.scrollY);"number"!=typeof e||isNaN(e)||g.postMessage("revealLine",{line:e})}},50))}).call(this,n(4).setImmediate)},function(e,t,n){(function(e){var o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,window,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,window,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(window,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout((function(){e._onTimeout&&e._onTimeout()}),t))},n(5),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(1))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var o,i,r,c,s,a=1,u={},l=!1,f=e.document,d=Object.getPrototypeOf&&Object.getPrototypeOf(e);d=d&&d.setTimeout?d:e,"[object process]"==={}.toString.call(e.process)?o=function(e){t.nextTick((function(){p(e)}))}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((r=new MessageChannel).port1.onmessage=function(e){p(e.data)},o=function(e){r.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(i=f.documentElement,o=function(e){var t=f.createElement("script");t.onreadystatechange=function(){p(e),t.onreadystatechange=null,i.removeChild(t),t=null},i.appendChild(t)}):o=function(e){setTimeout(p,0,e)}:(c="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(c)&&p(+t.data.slice(c.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),o=function(t){e.postMessage(c+t,"*")}),d.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n1)for(var n=1;nnew class{postMessage(t,n){e.postMessage({type:t,source:o.getSettings().source,body:n})}}},function(e,t,n){(function(t){var n="Expected a function",o=NaN,i="[object Symbol]",r=/^\s+|\s+$/g,c=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,a=/^0o[0-7]+$/i,u=parseInt,l="object"==typeof t&&t&&t.Object===Object&&t,f="object"==typeof self&&self&&self.Object===Object&&self,d=l||f||Function("return this")(),m=Object.prototype.toString,p=Math.max,g=Math.min,h=function(){return d.Date.now()};function v(e,t,o){var i,r,c,s,a,u,l=0,f=!1,d=!1,m=!0;if("function"!=typeof e)throw new TypeError(n);function v(t){var n=i,o=r;return i=r=void 0,l=t,s=e.apply(o,n)}function b(e){var n=e-u;return void 0===u||n>=t||n<0||d&&e-l>=c}function T(){var e=h();if(b(e))return E(e);a=setTimeout(T,function(e){var n=t-(e-u);return d?g(n,c-(e-l)):n}(e))}function E(e){return a=void 0,m&&i?v(e):(i=r=void 0,s)}function _(){var e=h(),n=b(e);if(i=arguments,r=this,u=e,n){if(void 0===a)return function(e){return l=e,a=setTimeout(T,t),f?v(e):s}(u);if(d)return a=setTimeout(T,t),v(u)}return void 0===a&&(a=setTimeout(T,t)),s}return t=w(t)||0,y(o)&&(f=!!o.leading,c=(d="maxWait"in o)?p(w(o.maxWait)||0,t):c,m="trailing"in o?!!o.trailing:m),_.cancel=function(){void 0!==a&&clearTimeout(a),l=0,i=u=r=a=void 0},_.flush=function(){return void 0===a?s:E(h())},_}function y(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function w(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&m.call(e)==i}(e))return o;if(y(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=y(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(r,"");var n=s.test(e);return n||a.test(e)?u(e.slice(2),n?2:8):c.test(e)?o:+e}e.exports=function(e,t,o){var i=!0,r=!0;if("function"!=typeof e)throw new TypeError(n);return y(o)&&(i="leading"in o?!!o.leading:i,r="trailing"in o?!!o.trailing:r),v(e,t,{leading:i,maxWait:t,trailing:r})}}).call(this,n(1))}]); \ No newline at end of file diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index 2bffee4656..69731ba891 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -163,13 +163,15 @@ document.addEventListener('click', event => { return; } - // Pass through known schemes - if (passThroughLinkSchemes.some(scheme => node.href.startsWith(scheme))) { - return; + let hrefText = node.getAttribute('data-href'); + if (!hrefText) { + // Pass through known schemes + if (passThroughLinkSchemes.some(scheme => node.href.startsWith(scheme))) { + return; + } + hrefText = node.getAttribute('href'); } - const hrefText = node.getAttribute('data-href') || node.getAttribute('href'); - // If original link doesn't look like a url, delegate back to VS Code to resolve if (!/^[a-z\-]+:/i.test(hrefText)) { messaging.postMessage('openLink', { href: hrefText }); diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index 0794d50127..9db55f18ae 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -15,6 +15,10 @@ "*", "onAuthenticationRequest:microsoft" ], + "extensionKind": [ + "ui", + "workspace" + ], "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "main": "./out/extension.js", "browser": "./dist/browser/extension.js", diff --git a/package.json b/package.json index 6468379916..74bd5b03f2 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "slickgrid": "github:anthonydresser/SlickGrid#2.3.33", "spdlog": "^0.11.1", "sudo-prompt": "9.1.1", + "tas-client": "^0.0.950", "v8-inspect-profiler": "^0.0.20", "vscode-nsfw": "1.2.8", "vscode-oniguruma": "1.3.1", diff --git a/src/sql/base/browser/ui/buttonMenu/buttonMenu.ts b/src/sql/base/browser/ui/buttonMenu/buttonMenu.ts index 81016ff455..1f2b2ca7d3 100644 --- a/src/sql/base/browser/ui/buttonMenu/buttonMenu.ts +++ b/src/sql/base/browser/ui/buttonMenu/buttonMenu.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./buttonMenu'; -import { IAction, IActionRunner } from 'vs/base/common/actions'; -import { BaseActionViewItem, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction, IActionRunner, IActionViewItemProvider } from 'vs/base/common/actions'; import { IDisposable } from 'vs/base/common/lifecycle'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { append, $, addClasses } from 'vs/base/browser/dom'; -import { IDropdownMenuOptions, DropdownMenu, IActionProvider, IContextMenuProvider, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown'; +import { IDropdownMenuOptions, DropdownMenu, IActionProvider, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown'; +import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; +import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class DropdownMenuActionViewItem extends BaseActionViewItem { private menuActionsOrProvider: ReadonlyArray | IActionProvider; diff --git a/src/sql/base/browser/ui/taskbar/actionbar.ts b/src/sql/base/browser/ui/taskbar/actionbar.ts index 68975ce583..8a16e388cc 100644 --- a/src/sql/base/browser/ui/taskbar/actionbar.ts +++ b/src/sql/base/browser/ui/taskbar/actionbar.ts @@ -3,17 +3,18 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions'; +import { IAction, IActionRunner, ActionRunner, IActionViewItem } from 'vs/base/common/actions'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { - IActionBarOptions, ActionsOrientation, IActionViewItem, - IActionOptions, ActionViewItem, BaseActionViewItem + IActionBarOptions, ActionsOrientation, + IActionOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import * as lifecycle from 'vs/base/common/lifecycle'; import * as DOM from 'vs/base/browser/dom'; import * as types from 'vs/base/common/types'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; const defaultOptions: IActionBarOptions = { orientation: ActionsOrientation.HORIZONTAL, diff --git a/src/sql/base/browser/ui/taskbar/overflowActionbar.ts b/src/sql/base/browser/ui/taskbar/overflowActionbar.ts index e761d7c66f..0b7267a86e 100644 --- a/src/sql/base/browser/ui/taskbar/overflowActionbar.ts +++ b/src/sql/base/browser/ui/taskbar/overflowActionbar.ts @@ -2,11 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction } from 'vs/base/common/actions'; +import { IAction, IActionViewItem } from 'vs/base/common/actions'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { - IActionBarOptions, ActionsOrientation, IActionViewItem, + IActionBarOptions, ActionsOrientation, IActionOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import * as DOM from 'vs/base/browser/dom'; diff --git a/src/sql/base/browser/ui/taskbar/taskbar.ts b/src/sql/base/browser/ui/taskbar/taskbar.ts index 291f224418..8660cd6245 100644 --- a/src/sql/base/browser/ui/taskbar/taskbar.ts +++ b/src/sql/base/browser/ui/taskbar/taskbar.ts @@ -8,8 +8,8 @@ import 'vs/css!./media/icons'; import { ActionBar } from './actionbar'; -import { IActionRunner, IAction } from 'vs/base/common/actions'; -import { ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionRunner, IAction, IActionViewItem } from 'vs/base/common/actions'; +import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { IToolBarOptions } from 'vs/base/browser/ui/toolbar/toolbar'; import { OverflowActionBar } from 'sql/base/browser/ui/taskbar/overflowActionbar'; diff --git a/src/sql/workbench/browser/parts/views/treeView.ts b/src/sql/workbench/browser/parts/views/treeView.ts index 5f80911261..14ea30e18e 100644 --- a/src/sql/workbench/browser/parts/views/treeView.ts +++ b/src/sql/workbench/browser/parts/views/treeView.ts @@ -6,11 +6,11 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IAction, ActionRunner } from 'vs/base/common/actions'; +import { IAction, ActionRunner, IActionViewItemProvider } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IMenuService, MenuId, MenuItemAction, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; -import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuService, MenuId, MenuItemAction, registerAction2, Action2, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuEntryActionViewItem, createAndFillInContextMenuActions, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ITreeItemLabel, Extensions, IViewDescriptorService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; @@ -21,7 +21,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { ICommandService } from 'vs/platform/commands/common/commands'; import * as DOM from 'vs/base/browser/dom'; import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; -import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { URI } from 'vs/base/common/uri'; import { dirname, basename } from 'vs/base/common/resources'; import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService'; @@ -47,6 +47,7 @@ import { ITreeItem, ITreeView } from 'sql/workbench/common/views'; import { UserCancelledConnectionError } from 'sql/base/common/errors'; import { IOEShimService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerViewTreeShim'; import { NodeContextKey } from 'sql/workbench/browser/parts/views/nodeContext'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class TreeViewPane extends ViewPane { @@ -429,7 +430,15 @@ export class TreeView extends Disposable implements ITreeView { } private createTree() { - const actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) : undefined; + const actionViewItemProvider = (action: IAction) => { + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); + } + + return undefined; + }; const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id)); this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); const dataSource = this.instantiationService.createInstance(TreeDataSource, this, this.id, (task: Promise) => this.progressService.withProgress({ location: this.id }, () => task)); diff --git a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts index 659f970ffc..d96f177b68 100644 --- a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts +++ b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts @@ -20,8 +20,7 @@ import { EditDataInput } from 'sql/workbench/browser/editData/editDataInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import * as queryContext from 'sql/workbench/contrib/query/common/queryContext'; import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar'; -import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, IActionViewItem } from 'vs/base/common/actions'; import { IQueryModelService } from 'sql/workbench/services/query/common/queryModel'; import { IEditorDescriptorService } from 'sql/workbench/services/queryEditor/browser/editorDescriptorService'; import { diff --git a/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts b/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts index bb037f06f0..02956d191d 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts @@ -6,8 +6,8 @@ import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { Action, IAction } from 'vs/base/common/actions'; -import { ActionBar, Separator, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Action, IAction, Separator } from 'vs/base/common/actions'; +import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { CellActionBase, CellContext } from 'sql/workbench/contrib/notebook/browser/cellViews/codeActions'; import { CellModel } from 'sql/workbench/services/notebook/browser/models/cell'; import { CellTypes, CellType } from 'sql/workbench/services/notebook/common/contracts'; diff --git a/src/sql/workbench/contrib/notebook/test/browser/cellToolbarActions.test.ts b/src/sql/workbench/contrib/notebook/test/browser/cellToolbarActions.test.ts index 08df808673..7204029df7 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/cellToolbarActions.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/cellToolbarActions.test.ts @@ -11,7 +11,6 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { CellContext } from 'sql/workbench/contrib/notebook/browser/cellViews/codeActions'; import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; @@ -20,6 +19,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import { CellModel } from 'sql/workbench/services/notebook/browser/models/cell'; import { IProductService } from 'vs/platform/product/common/productService'; +import { Separator } from 'vs/base/common/actions'; suite('CellToolbarActions', function (): void { suite('removeDuplicatedAndStartingSeparators', function (): void { diff --git a/src/sql/workbench/contrib/query/browser/gridPanel.ts b/src/sql/workbench/contrib/query/browser/gridPanel.ts index 1a72720656..ed31d49a37 100644 --- a/src/sql/workbench/contrib/query/browser/gridPanel.ts +++ b/src/sql/workbench/contrib/query/browser/gridPanel.ts @@ -32,11 +32,11 @@ import { range, find } from 'vs/base/common/arrays'; import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { Disposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { generateUuid } from 'vs/base/common/uuid'; -import { Separator, ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { isInDOM, Dimension } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, Separator } from 'vs/base/common/actions'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ILogService } from 'vs/platform/log/common/log'; import { localize } from 'vs/nls'; diff --git a/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts b/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts index 0fe0727a8c..43245d1e7b 100644 --- a/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts +++ b/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ITree } from 'vs/base/parts/tree/browser/tree'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, Separator } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -24,7 +24,6 @@ import { TreeNodeContextKey } from 'sql/workbench/services/objectExplorer/common import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement'; import { ServerInfoContextKey } from 'sql/workbench/services/connection/common/serverInfoContextKey'; import { fillInActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { firstIndex, find } from 'vs/base/common/arrays'; /** diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts index 5cd028f8c5..8bc02e4774 100644 --- a/src/vs/base/browser/contextmenu.ts +++ b/src/vs/base/browser/contextmenu.ts @@ -3,10 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction, IActionRunner } from 'vs/base/common/actions'; -import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction, IActionRunner, IActionViewItem } from 'vs/base/common/actions'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { SubmenuAction } from 'vs/base/browser/ui/menu/menu'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; export interface IContextMenuEvent { @@ -16,15 +14,9 @@ export interface IContextMenuEvent { readonly metaKey?: boolean; } -export class ContextSubMenu extends SubmenuAction { - constructor(label: string, public entries: Array) { - super(label, entries, 'contextsubmenu'); - } -} - export interface IContextMenuDelegate { getAnchor(): HTMLElement | { x: number; y: number; width?: number; height?: number; }; - getActions(): ReadonlyArray; + getActions(): IAction[]; getCheckedActionsRepresentation?(action: IAction): 'radio' | 'checkbox'; getActionViewItem?(action: IAction): IActionViewItem | undefined; getActionsContext?(event?: IContextMenuEvent): any; @@ -36,3 +28,7 @@ export interface IContextMenuDelegate { anchorAlignment?: AnchorAlignment; domForShadowRoot?: HTMLElement; } + +export interface IContextMenuProvider { + showContextMenu(delegate: IContextMenuDelegate): void; +} diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts new file mode 100644 index 0000000000..3133bc7851 --- /dev/null +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -0,0 +1,406 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./actionbar'; +import * as platform from 'vs/base/common/platform'; +import * as nls from 'vs/nls'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { SelectBox, ISelectOptionItem, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox'; +import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, Separator, IActionViewItem } from 'vs/base/common/actions'; +import * as DOM from 'vs/base/browser/dom'; +import * as types from 'vs/base/common/types'; +import { EventType, Gesture } from 'vs/base/browser/touch'; +import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { DataTransfers } from 'vs/base/browser/dnd'; +import { isFirefox } from 'vs/base/browser/browser'; + +export interface IBaseActionViewItemOptions { + draggable?: boolean; + isMenu?: boolean; + useEventAsContext?: boolean; +} + +export class BaseActionViewItem extends Disposable implements IActionViewItem { + + element: HTMLElement | undefined; + + _context: any; + _action: IAction; + + private _actionRunner: IActionRunner | undefined; + + constructor(context: any, action: IAction, protected options: IBaseActionViewItemOptions = {}) { + super(); + + this._context = context || this; + this._action = action; + + if (action instanceof Action) { + this._register(action.onDidChange(event => { + if (!this.element) { + // we have not been rendered yet, so there + // is no point in updating the UI + return; + } + + this.handleActionChangeEvent(event); + })); + } + } + + private handleActionChangeEvent(event: IActionChangeEvent): void { + if (event.enabled !== undefined) { + this.updateEnabled(); + } + + if (event.checked !== undefined) { + this.updateChecked(); + } + + if (event.class !== undefined) { + this.updateClass(); + } + + if (event.label !== undefined) { + this.updateLabel(); + this.updateTooltip(); + } + + if (event.tooltip !== undefined) { + this.updateTooltip(); + } + } + + get actionRunner(): IActionRunner { + if (!this._actionRunner) { + this._actionRunner = this._register(new ActionRunner()); + } + + return this._actionRunner; + } + + set actionRunner(actionRunner: IActionRunner) { + this._actionRunner = actionRunner; + } + + getAction(): IAction { + return this._action; + } + + isEnabled(): boolean { + return this._action.enabled; + } + + setActionContext(newContext: unknown): void { + this._context = newContext; + } + + render(container: HTMLElement): void { + const element = this.element = container; + this._register(Gesture.addTarget(container)); + + const enableDragging = this.options && this.options.draggable; + if (enableDragging) { + container.draggable = true; + + if (isFirefox) { + // Firefox: requires to set a text data transfer to get going + this._register(DOM.addDisposableListener(container, DOM.EventType.DRAG_START, e => e.dataTransfer?.setData(DataTransfers.TEXT, this._action.label))); + } + } + + this._register(DOM.addDisposableListener(element, EventType.Tap, e => this.onClick(e))); + + this._register(DOM.addDisposableListener(element, DOM.EventType.MOUSE_DOWN, e => { + if (!enableDragging) { + DOM.EventHelper.stop(e, true); // do not run when dragging is on because that would disable it + } + + if (this._action.enabled && e.button === 0) { + DOM.addClass(element, 'active'); + } + })); + + if (platform.isMacintosh) { + // macOS: allow to trigger the button when holding Ctrl+key and pressing the + // main mouse button. This is for scenarios where e.g. some interaction forces + // the Ctrl+key to be pressed and hold but the user still wants to interact + // with the actions (for example quick access in quick navigation mode). + this._register(DOM.addDisposableListener(element, DOM.EventType.CONTEXT_MENU, e => { + if (e.button === 0 && e.ctrlKey === true) { + this.onClick(e); + } + })); + } + + this._register(DOM.addDisposableListener(element, DOM.EventType.CLICK, e => { + DOM.EventHelper.stop(e, true); + // See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard + // > Writing to the clipboard + // > You can use the "cut" and "copy" commands without any special + // permission if you are using them in a short-lived event handler + // for a user action (for example, a click handler). + + // => to get the Copy and Paste context menu actions working on Firefox, + // there should be no timeout here + if (this.options && this.options.isMenu) { + this.onClick(e); + } else { + platform.setImmediate(() => this.onClick(e)); + } + })); + + this._register(DOM.addDisposableListener(element, DOM.EventType.DBLCLICK, e => { + DOM.EventHelper.stop(e, true); + })); + + [DOM.EventType.MOUSE_UP, DOM.EventType.MOUSE_OUT].forEach(event => { + this._register(DOM.addDisposableListener(element, event, e => { + DOM.EventHelper.stop(e); + DOM.removeClass(element, 'active'); + })); + }); + } + + onClick(event: DOM.EventLike): void { + DOM.EventHelper.stop(event, true); + + const context = types.isUndefinedOrNull(this._context) ? this.options?.useEventAsContext ? event : undefined : this._context; + this.actionRunner.run(this._action, context); + } + + focus(): void { + if (this.element) { + this.element.focus(); + DOM.addClass(this.element, 'focused'); + } + } + + blur(): void { + if (this.element) { + this.element.blur(); + DOM.removeClass(this.element, 'focused'); + } + } + + protected updateEnabled(): void { + // implement in subclass + } + + protected updateLabel(): void { + // implement in subclass + } + + protected updateTooltip(): void { + // implement in subclass + } + + protected updateClass(): void { + // implement in subclass + } + + protected updateChecked(): void { + // implement in subclass + } + + dispose(): void { + if (this.element) { + DOM.removeNode(this.element); + this.element = undefined; + } + + super.dispose(); + } +} + +export interface IActionViewItemOptions extends IBaseActionViewItemOptions { + icon?: boolean; + label?: boolean; + keybinding?: string | null; +} + +export class ActionViewItem extends BaseActionViewItem { + + protected label: HTMLElement | undefined; + protected options: IActionViewItemOptions; + + private cssClass?: string; + + constructor(context: unknown, action: IAction, options: IActionViewItemOptions = {}) { + super(context, action, options); + + this.options = options; + this.options.icon = options.icon !== undefined ? options.icon : false; + this.options.label = options.label !== undefined ? options.label : true; + this.cssClass = ''; + } + + render(container: HTMLElement): void { + super.render(container); + + if (this.element) { + this.label = DOM.append(this.element, DOM.$('a.action-label')); + } + + if (this.label) { + if (this._action.id === Separator.ID) { + this.label.setAttribute('role', 'presentation'); // A separator is a presentation item + } else { + if (this.options.isMenu) { + this.label.setAttribute('role', 'menuitem'); + } else { + this.label.setAttribute('role', 'button'); + } + } + } + + if (this.options.label && this.options.keybinding && this.element) { + DOM.append(this.element, DOM.$('span.keybinding')).textContent = this.options.keybinding; + } + + this.updateClass(); + this.updateLabel(); + this.updateTooltip(); + this.updateEnabled(); + this.updateChecked(); + } + + focus(): void { + super.focus(); + + if (this.label) { + this.label.focus(); + } + } + + updateLabel(): void { + if (this.options.label && this.label) { + this.label.textContent = this.getAction().label; + } + } + + updateTooltip(): void { + let title: string | null = null; + + if (this.getAction().tooltip) { + title = this.getAction().tooltip; + + } else if (!this.options.label && this.getAction().label && this.options.icon) { + title = this.getAction().label; + + if (this.options.keybinding) { + title = nls.localize({ key: 'titleLabel', comment: ['action title', 'action keybinding'] }, "{0} ({1})", title, this.options.keybinding); + } + } + + if (title && this.label) { + this.label.title = title; + } + } + + updateClass(): void { + if (this.cssClass && this.label) { + DOM.removeClasses(this.label, this.cssClass); + } + + if (this.options.icon) { + this.cssClass = this.getAction().class; + + if (this.label) { + DOM.addClass(this.label, 'codicon'); + if (this.cssClass) { + DOM.addClasses(this.label, this.cssClass); + } + } + + this.updateEnabled(); + } else { + if (this.label) { + DOM.removeClass(this.label, 'codicon'); + } + } + } + + updateEnabled(): void { + if (this.getAction().enabled) { + if (this.label) { + this.label.removeAttribute('aria-disabled'); + DOM.removeClass(this.label, 'disabled'); + this.label.tabIndex = 0; + } + + if (this.element) { + DOM.removeClass(this.element, 'disabled'); + } + } else { + if (this.label) { + this.label.setAttribute('aria-disabled', 'true'); + DOM.addClass(this.label, 'disabled'); + DOM.removeTabIndexAndUpdateFocus(this.label); + } + + if (this.element) { + DOM.addClass(this.element, 'disabled'); + } + } + } + + updateChecked(): void { + if (this.label) { + if (this.getAction().checked) { + DOM.addClass(this.label, 'checked'); + } else { + DOM.removeClass(this.label, 'checked'); + } + } + } +} + +export class SelectActionViewItem extends BaseActionViewItem { + protected selectBox: SelectBox; + + constructor(ctx: unknown, action: IAction, options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, selectBoxOptions?: ISelectBoxOptions) { + super(ctx, action); + + this.selectBox = new SelectBox(options, selected, contextViewProvider, undefined, selectBoxOptions); + + this._register(this.selectBox); + this.registerListeners(); + } + + setOptions(options: ISelectOptionItem[], selected?: number): void { + this.selectBox.setOptions(options, selected); + } + + select(index: number): void { + this.selectBox.select(index); + } + + private registerListeners(): void { + this._register(this.selectBox.onDidSelect(e => { + this.actionRunner.run(this._action, this.getActionContext(e.selected, e.index)); + })); + } + + protected getActionContext(option: string, index: number) { + return option; + } + + focus(): void { + if (this.selectBox) { + this.selectBox.focus(); + } + } + + blur(): void { + if (this.selectBox) { + this.selectBox.blur(); + } + } + + render(container: HTMLElement): void { + this.selectBox.render(container); + } +} diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 73752865a0..c7913e7413 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -4,382 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./actionbar'; -import * as platform from 'vs/base/common/platform'; -import * as nls from 'vs/nls'; -import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { SelectBox, ISelectOptionItem, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox'; -import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, IRunEvent } from 'vs/base/common/actions'; +import { Disposable, dispose } from 'vs/base/common/lifecycle'; +import { IAction, IActionRunner, ActionRunner, IRunEvent, Separator, IActionViewItem, IActionViewItemProvider } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; import * as types from 'vs/base/common/types'; -import { EventType, Gesture } from 'vs/base/browser/touch'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { Event, Emitter } from 'vs/base/common/event'; -import { DataTransfers } from 'vs/base/browser/dnd'; -import { isFirefox } from 'vs/base/browser/browser'; - -export interface IActionViewItem extends IDisposable { - actionRunner: IActionRunner; - setActionContext(context: any): void; - render(element: HTMLElement): void; - isEnabled(): boolean; - focus(fromRight?: boolean): void; - blur(): void; -} - -export interface IBaseActionViewItemOptions { - draggable?: boolean; - isMenu?: boolean; - useEventAsContext?: boolean; -} - -export class BaseActionViewItem extends Disposable implements IActionViewItem { - - element: HTMLElement | undefined; - - _context: any; - _action: IAction; - - private _actionRunner: IActionRunner | undefined; - - constructor(context: any, action: IAction, protected options?: IBaseActionViewItemOptions) { - super(); - - this._context = context || this; - this._action = action; - - if (action instanceof Action) { - this._register(action.onDidChange(event => { - if (!this.element) { - // we have not been rendered yet, so there - // is no point in updating the UI - return; - } - - this.handleActionChangeEvent(event); - })); - } - } - - private handleActionChangeEvent(event: IActionChangeEvent): void { - if (event.enabled !== undefined) { - this.updateEnabled(); - } - - if (event.checked !== undefined) { - this.updateChecked(); - } - - if (event.class !== undefined) { - this.updateClass(); - } - - if (event.label !== undefined) { - this.updateLabel(); - this.updateTooltip(); - } - - if (event.tooltip !== undefined) { - this.updateTooltip(); - } - } - - get actionRunner(): IActionRunner { - if (!this._actionRunner) { - this._actionRunner = this._register(new ActionRunner()); - } - - return this._actionRunner; - } - - set actionRunner(actionRunner: IActionRunner) { - this._actionRunner = actionRunner; - } - - getAction(): IAction { - return this._action; - } - - isEnabled(): boolean { - return this._action.enabled; - } - - setActionContext(newContext: unknown): void { - this._context = newContext; - } - - render(container: HTMLElement): void { - const element = this.element = container; - this._register(Gesture.addTarget(container)); - - const enableDragging = this.options && this.options.draggable; - if (enableDragging) { - container.draggable = true; - - if (isFirefox) { - // Firefox: requires to set a text data transfer to get going - this._register(DOM.addDisposableListener(container, DOM.EventType.DRAG_START, e => e.dataTransfer?.setData(DataTransfers.TEXT, this._action.label))); - } - } - - this._register(DOM.addDisposableListener(element, EventType.Tap, e => this.onClick(e))); - - this._register(DOM.addDisposableListener(element, DOM.EventType.MOUSE_DOWN, e => { - if (!enableDragging) { - DOM.EventHelper.stop(e, true); // do not run when dragging is on because that would disable it - } - - if (this._action.enabled && e.button === 0) { - DOM.addClass(element, 'active'); - } - })); - - if (platform.isMacintosh) { - // macOS: allow to trigger the button when holding Ctrl+key and pressing the - // main mouse button. This is for scenarios where e.g. some interaction forces - // the Ctrl+key to be pressed and hold but the user still wants to interact - // with the actions (for example quick access in quick navigation mode). - this._register(DOM.addDisposableListener(element, DOM.EventType.CONTEXT_MENU, e => { - if (e.button === 0 && e.ctrlKey === true) { - this.onClick(e); - } - })); - } - - this._register(DOM.addDisposableListener(element, DOM.EventType.CLICK, e => { - DOM.EventHelper.stop(e, true); - // See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard - // > Writing to the clipboard - // > You can use the "cut" and "copy" commands without any special - // permission if you are using them in a short-lived event handler - // for a user action (for example, a click handler). - - // => to get the Copy and Paste context menu actions working on Firefox, - // there should be no timeout here - if (this.options && this.options.isMenu) { - this.onClick(e); - } else { - platform.setImmediate(() => this.onClick(e)); - } - })); - - this._register(DOM.addDisposableListener(element, DOM.EventType.DBLCLICK, e => { - DOM.EventHelper.stop(e, true); - })); - - [DOM.EventType.MOUSE_UP, DOM.EventType.MOUSE_OUT].forEach(event => { - this._register(DOM.addDisposableListener(element, event, e => { - DOM.EventHelper.stop(e); - DOM.removeClass(element, 'active'); - })); - }); - } - - onClick(event: DOM.EventLike): void { - DOM.EventHelper.stop(event, true); - - const context = types.isUndefinedOrNull(this._context) ? this.options?.useEventAsContext ? event : undefined : this._context; - this.actionRunner.run(this._action, context); - } - - focus(): void { - if (this.element) { - this.element.focus(); - DOM.addClass(this.element, 'focused'); - } - } - - blur(): void { - if (this.element) { - this.element.blur(); - DOM.removeClass(this.element, 'focused'); - } - } - - protected updateEnabled(): void { - // implement in subclass - } - - protected updateLabel(): void { - // implement in subclass - } - - protected updateTooltip(): void { - // implement in subclass - } - - protected updateClass(): void { - // implement in subclass - } - - protected updateChecked(): void { - // implement in subclass - } - - dispose(): void { - if (this.element) { - DOM.removeNode(this.element); - this.element = undefined; - } - - super.dispose(); - } -} - -export class Separator extends Action { - - static readonly ID = 'vs.actions.separator'; - - constructor(label?: string) { - super(Separator.ID, label, label ? 'separator text' : 'separator'); - this.checked = false; - this.enabled = false; - } -} - -export interface IActionViewItemOptions extends IBaseActionViewItemOptions { - icon?: boolean; - label?: boolean; - keybinding?: string | null; -} - -export class ActionViewItem extends BaseActionViewItem { - - protected label: HTMLElement | undefined; - protected options: IActionViewItemOptions; - - private cssClass?: string; - - constructor(context: unknown, action: IAction, options: IActionViewItemOptions = {}) { - super(context, action, options); - - this.options = options; - this.options.icon = options.icon !== undefined ? options.icon : false; - this.options.label = options.label !== undefined ? options.label : true; - this.cssClass = ''; - } - - render(container: HTMLElement): void { - super.render(container); - - if (this.element) { - this.label = DOM.append(this.element, DOM.$('a.action-label')); - } - - if (this.label) { - if (this._action.id === Separator.ID) { - this.label.setAttribute('role', 'presentation'); // A separator is a presentation item - } else { - if (this.options.isMenu) { - this.label.setAttribute('role', 'menuitem'); - } else { - this.label.setAttribute('role', 'button'); - } - } - } - - if (this.options.label && this.options.keybinding && this.element) { - DOM.append(this.element, DOM.$('span.keybinding')).textContent = this.options.keybinding; - } - - this.updateClass(); - this.updateLabel(); - this.updateTooltip(); - this.updateEnabled(); - this.updateChecked(); - } - - focus(): void { - super.focus(); - - if (this.label) { - this.label.focus(); - } - } - - updateLabel(): void { - if (this.options.label && this.label) { - this.label.textContent = this.getAction().label; - } - } - - updateTooltip(): void { - let title: string | null = null; - - if (this.getAction().tooltip) { - title = this.getAction().tooltip; - - } else if (!this.options.label && this.getAction().label && this.options.icon) { - title = this.getAction().label; - - if (this.options.keybinding) { - title = nls.localize({ key: 'titleLabel', comment: ['action title', 'action keybinding'] }, "{0} ({1})", title, this.options.keybinding); - } - } - - if (title && this.label) { - this.label.title = title; - } - } - - updateClass(): void { - if (this.cssClass && this.label) { - DOM.removeClasses(this.label, this.cssClass); - } - - if (this.options.icon) { - this.cssClass = this.getAction().class; - - if (this.label) { - DOM.addClass(this.label, 'codicon'); - if (this.cssClass) { - DOM.addClasses(this.label, this.cssClass); - } - } - - this.updateEnabled(); - } else { - if (this.label) { - DOM.removeClass(this.label, 'codicon'); - } - } - } - - updateEnabled(): void { - if (this.getAction().enabled) { - if (this.label) { - this.label.removeAttribute('aria-disabled'); - DOM.removeClass(this.label, 'disabled'); - this.label.tabIndex = 0; - } - - if (this.element) { - DOM.removeClass(this.element, 'disabled'); - } - } else { - if (this.label) { - this.label.setAttribute('aria-disabled', 'true'); - DOM.addClass(this.label, 'disabled'); - DOM.removeTabIndexAndUpdateFocus(this.label); - } - - if (this.element) { - DOM.addClass(this.element, 'disabled'); - } - } - } - - updateChecked(): void { - if (this.label) { - if (this.getAction().checked) { - DOM.addClass(this.label, 'checked'); - } else { - DOM.removeClass(this.label, 'checked'); - } - } - } -} +import { IActionViewItemOptions, ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export const enum ActionsOrientation { HORIZONTAL, @@ -393,41 +25,30 @@ export interface ActionTrigger { keyDown: boolean; } -export interface IActionViewItemProvider { - (action: IAction): IActionViewItem | undefined; -} - export interface IActionBarOptions { - orientation?: ActionsOrientation; - context?: any; - actionViewItemProvider?: IActionViewItemProvider; - actionRunner?: IActionRunner; - ariaLabel?: string; - animated?: boolean; - triggerKeys?: ActionTrigger; - allowContextMenu?: boolean; - preventLoopNavigation?: boolean; + readonly orientation?: ActionsOrientation; + readonly context?: any; + readonly actionViewItemProvider?: IActionViewItemProvider; + readonly actionRunner?: IActionRunner; + readonly ariaLabel?: string; + readonly animated?: boolean; + readonly triggerKeys?: ActionTrigger; + readonly allowContextMenu?: boolean; + readonly preventLoopNavigation?: boolean; } -const defaultOptions: IActionBarOptions = { - orientation: ActionsOrientation.HORIZONTAL, - context: null, - triggerKeys: { - keys: [KeyCode.Enter, KeyCode.Space], - keyDown: false - } -}; - export interface IActionOptions extends IActionViewItemOptions { index?: number; } export class ActionBar extends Disposable implements IActionRunner { - options: IActionBarOptions; + private readonly options: IActionBarOptions; private _actionRunner: IActionRunner; private _context: unknown; + private _orientation: ActionsOrientation; + private _triggerKeys: ActionTrigger; // View Items viewItems: IActionViewItem[]; @@ -450,15 +71,16 @@ export class ActionBar extends Disposable implements IActionRunner { private _onDidBeforeRun = this._register(new Emitter()); readonly onDidBeforeRun: Event = this._onDidBeforeRun.event; - constructor(container: HTMLElement, options: IActionBarOptions = defaultOptions) { + constructor(container: HTMLElement, options: IActionBarOptions = {}) { super(); this.options = options; - this._context = options.context; - - if (!this.options.triggerKeys) { - this.options.triggerKeys = defaultOptions.triggerKeys; - } + this._context = options.context ?? null; + this._orientation = this.options.orientation ?? ActionsOrientation.HORIZONTAL; + this._triggerKeys = this.options.triggerKeys ?? { + keys: [KeyCode.Enter, KeyCode.Space], + keyDown: false + }; if (this.options.actionRunner) { this._actionRunner = this.options.actionRunner; @@ -483,7 +105,7 @@ export class ActionBar extends Disposable implements IActionRunner { let previousKey: KeyCode; let nextKey: KeyCode; - switch (this.options.orientation) { + switch (this._orientation) { case ActionsOrientation.HORIZONTAL: previousKey = KeyCode.LeftArrow; nextKey = KeyCode.RightArrow; @@ -517,7 +139,7 @@ export class ActionBar extends Disposable implements IActionRunner { this._onDidCancel.fire(); } else if (this.isTriggerKeyEvent(event)) { // Staying out of the else branch even if not triggered - if (this.options.triggerKeys && this.options.triggerKeys.keyDown) { + if (this._triggerKeys.keyDown) { this.doTrigger(event); } } else { @@ -535,7 +157,7 @@ export class ActionBar extends Disposable implements IActionRunner { // Run action on Enter/Space if (this.isTriggerKeyEvent(event)) { - if (this.options.triggerKeys && !this.options.triggerKeys.keyDown) { + if (!this._triggerKeys.keyDown) { this.doTrigger(event); } @@ -582,11 +204,9 @@ export class ActionBar extends Disposable implements IActionRunner { private isTriggerKeyEvent(event: StandardKeyboardEvent): boolean { let ret = false; - if (this.options.triggerKeys) { - this.options.triggerKeys.keys.forEach(keyCode => { - ret = ret || event.equals(keyCode); - }); - } + this._triggerKeys.keys.forEach(keyCode => { + ret = ret || event.equals(keyCode); + }); return ret; } @@ -845,53 +465,6 @@ export class ActionBar extends Disposable implements IActionRunner { } } -export class SelectActionViewItem extends BaseActionViewItem { - protected selectBox: SelectBox; - - constructor(ctx: unknown, action: IAction, options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, selectBoxOptions?: ISelectBoxOptions) { - super(ctx, action); - - this.selectBox = new SelectBox(options, selected, contextViewProvider, undefined, selectBoxOptions); - - this._register(this.selectBox); - this.registerListeners(); - } - - setOptions(options: ISelectOptionItem[], selected?: number): void { - this.selectBox.setOptions(options, selected); - } - - select(index: number): void { - this.selectBox.select(index); - } - - private registerListeners(): void { - this._register(this.selectBox.onDidSelect(e => { - this.actionRunner.run(this._action, this.getActionContext(e.selected, e.index)); - })); - } - - protected getActionContext(option: string, index: number) { - return option; - } - - focus(): void { - if (this.selectBox) { - this.selectBox.focus(); - } - } - - blur(): void { - if (this.selectBox) { - this.selectBox.blur(); - } - } - - render(container: HTMLElement): void { - this.selectBox.render(container); - } -} - export function prepareActions(actions: IAction[]): IAction[] { if (!actions.length) { return actions; diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index fe6584234c..2a6f0cf31f 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -10,9 +10,9 @@ import { Widget } from 'vs/base/browser/ui/widget'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Codicon } from 'vs/base/common/codicons'; +import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export interface ICheckboxOpts extends ICheckboxStyles { readonly actionClassName?: string; diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index b9c33e6506..9a06c87cac 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -5,14 +5,13 @@ import 'vs/css!./dropdown'; import { Gesture, EventType as GestureEventType } from 'vs/base/browser/touch'; -import { ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions'; -import { BaseActionViewItem, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IContextViewProvider, IAnchor, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IMenuOptions } from 'vs/base/browser/ui/menu/menu'; -import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; -import { EventHelper, EventType, removeClass, addClass, append, $, addDisposableListener, addClasses, DOMEvent } from 'vs/base/browser/dom'; -import { IContextMenuDelegate } from 'vs/base/browser/contextmenu'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { EventHelper, EventType, removeClass, addClass, append, $, addDisposableListener, DOMEvent } from 'vs/base/browser/dom'; +import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Emitter } from 'vs/base/common/event'; @@ -201,17 +200,13 @@ export class Dropdown extends BaseDropdown { } } -export interface IContextMenuProvider { - showContextMenu(delegate: IContextMenuDelegate): void; -} - export interface IActionProvider { - getActions(): ReadonlyArray; + getActions(): IAction[]; } export interface IDropdownMenuOptions extends IBaseDropdownOptions { contextMenuProvider: IContextMenuProvider; - actions?: ReadonlyArray; + actions?: IAction[]; actionProvider?: IActionProvider; menuClassName?: string; menuAsChild?: boolean; // scope down for #99448 @@ -220,7 +215,7 @@ export interface IDropdownMenuOptions extends IBaseDropdownOptions { export class DropdownMenu extends BaseDropdown { private _contextMenuProvider: IContextMenuProvider; private _menuOptions: IMenuOptions | undefined; - private _actions: ReadonlyArray = []; + private _actions: IAction[] = []; private actionProvider?: IActionProvider; private menuClassName: string; private menuAsChild?: boolean; @@ -243,7 +238,7 @@ export class DropdownMenu extends BaseDropdown { return this._menuOptions; } - private get actions(): ReadonlyArray { + private get actions(): IAction[] { if (this.actionProvider) { return this.actionProvider.getActions(); } @@ -251,7 +246,7 @@ export class DropdownMenu extends BaseDropdown { return this._actions; } - private set actions(actions: ReadonlyArray) { + private set actions(actions: IAction[]) { this._actions = actions; } @@ -283,106 +278,3 @@ export class DropdownMenu extends BaseDropdown { removeClass(this.element, 'active'); } } - -export class DropdownMenuActionViewItem extends BaseActionViewItem { - private menuActionsOrProvider: ReadonlyArray | IActionProvider; - private dropdownMenu: DropdownMenu | undefined; - private contextMenuProvider: IContextMenuProvider; - private actionViewItemProvider?: IActionViewItemProvider; - private keybindings?: (action: IAction) => ResolvedKeybinding | undefined; - private clazz: string | undefined; - private anchorAlignmentProvider: (() => AnchorAlignment) | undefined; - private menuAsChild?: boolean; - - private _onDidChangeVisibility = this._register(new Emitter()); - readonly onDidChangeVisibility = this._onDidChangeVisibility.event; - - constructor(action: IAction, menuActions: ReadonlyArray, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner | undefined, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean); - constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner | undefined, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean); - constructor(action: IAction, menuActionsOrProvider: ReadonlyArray | IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner | undefined, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean) { - super(null, action); - - this.menuActionsOrProvider = menuActionsOrProvider; - this.contextMenuProvider = contextMenuProvider; - this.actionViewItemProvider = actionViewItemProvider; - if (actionRunner) { - this.actionRunner = actionRunner; - } - this.keybindings = keybindings; - this.clazz = clazz; - this.anchorAlignmentProvider = anchorAlignmentProvider; - this.menuAsChild = menuAsChild; - } - - render(container: HTMLElement): void { - const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => { - this.element = append(el, $('a.action-label.codicon')); // todo@aeschli: remove codicon, should come through `this.clazz` - if (this.clazz) { - addClasses(this.element, this.clazz); - } - - this.element.tabIndex = 0; - this.element.setAttribute('role', 'button'); - this.element.setAttribute('aria-haspopup', 'true'); - this.element.setAttribute('aria-expanded', 'false'); - this.element.title = this._action.label || ''; - - return null; - }; - - const options: IDropdownMenuOptions = { - contextMenuProvider: this.contextMenuProvider, - labelRenderer: labelRenderer, - menuAsChild: this.menuAsChild - }; - - // Render the DropdownMenu around a simple action to toggle it - if (Array.isArray(this.menuActionsOrProvider)) { - options.actions = this.menuActionsOrProvider; - } else { - options.actionProvider = this.menuActionsOrProvider as IActionProvider; - } - - this.dropdownMenu = this._register(new DropdownMenu(container, options)); - this._register(this.dropdownMenu.onDidChangeVisibility(visible => { - this.element?.setAttribute('aria-expanded', `${visible}`); - this._onDidChangeVisibility.fire(visible); - })); - - this.dropdownMenu.menuOptions = { - actionViewItemProvider: this.actionViewItemProvider, - actionRunner: this.actionRunner, - getKeyBinding: this.keybindings, - context: this._context - }; - - if (this.anchorAlignmentProvider) { - const that = this; - - this.dropdownMenu.menuOptions = { - ...this.dropdownMenu.menuOptions, - get anchorAlignment(): AnchorAlignment { - return that.anchorAlignmentProvider!(); - } - }; - } - } - - setActionContext(newContext: unknown): void { - super.setActionContext(newContext); - - if (this.dropdownMenu) { - if (this.dropdownMenu.menuOptions) { - this.dropdownMenu.menuOptions.context = newContext; - } else { - this.dropdownMenu.menuOptions = { context: newContext }; - } - } - } - - show(): void { - if (this.dropdownMenu) { - this.dropdownMenu.show(); - } - } -} diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts new file mode 100644 index 0000000000..c6ffcc87c8 --- /dev/null +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -0,0 +1,136 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./dropdown'; +import { IAction, IActionRunner, IActionViewItemProvider } from 'vs/base/common/actions'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { append, $, addClasses } from 'vs/base/browser/dom'; +import { Emitter } from 'vs/base/common/event'; +import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { IActionProvider, DropdownMenu, IDropdownMenuOptions, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown'; +import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; +import { asArray } from 'vs/base/common/arrays'; + +export interface IKeybindingProvider { + (action: IAction): ResolvedKeybinding | undefined; +} + +export interface IAnchorAlignmentProvider { + (): AnchorAlignment; +} + +export interface IDropdownMenuActionViewItemOptions extends IBaseActionViewItemOptions { + readonly actionViewItemProvider?: IActionViewItemProvider; + readonly keybindingProvider?: IKeybindingProvider; + readonly actionRunner?: IActionRunner; + readonly classNames?: string[] | string; + readonly anchorAlignmentProvider?: IAnchorAlignmentProvider; + readonly menuAsChild?: boolean; +} + +export class DropdownMenuActionViewItem extends BaseActionViewItem { + private menuActionsOrProvider: readonly IAction[] | IActionProvider; + private dropdownMenu: DropdownMenu | undefined; + private contextMenuProvider: IContextMenuProvider; + + private _onDidChangeVisibility = this._register(new Emitter()); + readonly onDidChangeVisibility = this._onDidChangeVisibility.event; + + constructor( + action: IAction, + menuActionsOrProvider: readonly IAction[] | IActionProvider, + contextMenuProvider: IContextMenuProvider, + protected options: IDropdownMenuActionViewItemOptions = {} + ) { + super(null, action, options); + + this.menuActionsOrProvider = menuActionsOrProvider; + this.contextMenuProvider = contextMenuProvider; + + if (this.options.actionRunner) { + this.actionRunner = this.options.actionRunner; + } + } + + render(container: HTMLElement): void { + const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => { + this.element = append(el, $('a.action-label')); + + const classNames = this.options.classNames ? asArray(this.options.classNames) : []; + + // todo@aeschli: remove codicon, should come through `this.options.classNames` + if (!classNames.find(c => c === 'icon')) { + classNames.push('codicon'); + } + + addClasses(this.element, ...classNames); + + this.element.tabIndex = 0; + this.element.setAttribute('role', 'button'); + this.element.setAttribute('aria-haspopup', 'true'); + this.element.setAttribute('aria-expanded', 'false'); + this.element.title = this._action.label || ''; + + return null; + }; + + const options: IDropdownMenuOptions = { + contextMenuProvider: this.contextMenuProvider, + labelRenderer: labelRenderer, + menuAsChild: this.options.menuAsChild + }; + + // Render the DropdownMenu around a simple action to toggle it + if (Array.isArray(this.menuActionsOrProvider)) { + options.actions = this.menuActionsOrProvider; + } else { + options.actionProvider = this.menuActionsOrProvider as IActionProvider; + } + + this.dropdownMenu = this._register(new DropdownMenu(container, options)); + this._register(this.dropdownMenu.onDidChangeVisibility(visible => { + this.element?.setAttribute('aria-expanded', `${visible}`); + this._onDidChangeVisibility.fire(visible); + })); + + this.dropdownMenu.menuOptions = { + actionViewItemProvider: this.options.actionViewItemProvider, + actionRunner: this.actionRunner, + getKeyBinding: this.options.keybindingProvider, + context: this._context + }; + + if (this.options.anchorAlignmentProvider) { + const that = this; + + this.dropdownMenu.menuOptions = { + ...this.dropdownMenu.menuOptions, + get anchorAlignment(): AnchorAlignment { + return that.options.anchorAlignmentProvider!(); + } + }; + } + } + + setActionContext(newContext: unknown): void { + super.setActionContext(newContext); + + if (this.dropdownMenu) { + if (this.dropdownMenu.menuOptions) { + this.dropdownMenu.menuOptions.context = newContext; + } else { + this.dropdownMenu.menuOptions = { context: newContext }; + } + } + } + + show(): void { + if (this.dropdownMenu) { + this.dropdownMenu.show(); + } + } +} diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 4035da836c..50bcdd3587 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -5,8 +5,8 @@ import * as nls from 'vs/nls'; import * as strings from 'vs/base/common/strings'; -import { IActionRunner, IAction, Action } from 'vs/base/common/actions'; -import { ActionBar, IActionViewItemProvider, ActionsOrientation, Separator, ActionViewItem, IActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionRunner, IAction, SubmenuAction, Separator, IActionViewItemProvider } from 'vs/base/common/actions'; +import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses, clearNode, createStyleSheet, isInShadowDOM } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -19,6 +19,7 @@ import { Event } from 'vs/base/common/event'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { Codicon, registerIcon, stripCodicons } from 'vs/base/common/codicons'; +import { BaseActionViewItem, ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { formatRule } from 'vs/base/browser/ui/codicons/codiconStyles'; export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/; @@ -42,6 +43,7 @@ export interface IMenuOptions { anchorAlignment?: AnchorAlignment; expandDirection?: Direction; useEventAsContext?: boolean; + submenuIds?: Set; } export interface IMenuStyles { @@ -55,12 +57,6 @@ export interface IMenuStyles { separatorColor?: Color; } -export class SubmenuAction extends Action { - constructor(label: string, public entries: ReadonlyArray, cssClass?: string) { - super(!!cssClass ? cssClass : 'submenu', label, '', true); - } -} - interface ISubMenuData { parent: Menu; submenu?: Menu; @@ -209,6 +205,15 @@ export class Menu extends ActionBar { menuElement.style.maxHeight = `${Math.max(10, window.innerHeight - container.getBoundingClientRect().top - 30)}px`; + actions = actions.filter(a => { + if (options.submenuIds?.has(a.id)) { + console.warn(`Found submenu cycle: ${a.id}`); + return false; + } + + return true; + }); + this.push(actions, { icon: true, label: true, isMenu: true }); container.appendChild(this.scrollableElement.getDomNode()); @@ -317,7 +322,8 @@ export class Menu extends ActionBar { if (action instanceof Separator) { return new MenuSeparatorActionViewItem(options.context, action, { icon: true }); } else if (action instanceof SubmenuAction) { - const menuActionViewItem = new SubmenuMenuActionViewItem(action, action.entries, parentData, options); + const actions = Array.isArray(action.actions) ? action.actions : action.actions(); + const menuActionViewItem = new SubmenuMenuActionViewItem(action, actions, parentData, { ...options, submenuIds: new Set([...(options.submenuIds || []), action.id]) }); if (options.enableMnemonics) { const mnemonic = menuActionViewItem.getMnemonic(); diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 7fa6a5984f..417434ef39 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -11,8 +11,8 @@ import * as nls from 'vs/nls'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch'; -import { cleanMnemonic, IMenuOptions, Menu, MENU_ESCAPED_MNEMONIC_REGEX, MENU_MNEMONIC_REGEX, SubmenuAction, IMenuStyles, Direction } from 'vs/base/browser/ui/menu/menu'; -import { ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions'; +import { cleanMnemonic, IMenuOptions, Menu, MENU_ESCAPED_MNEMONIC_REGEX, MENU_MNEMONIC_REGEX, IMenuStyles, Direction } from 'vs/base/browser/ui/menu/menu'; +import { ActionRunner, IAction, IActionRunner, SubmenuAction, Separator } from 'vs/base/common/actions'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import { KeyCode, ResolvedKeybinding, KeyMod } from 'vs/base/common/keyCodes'; @@ -22,7 +22,6 @@ import { asArray } from 'vs/base/common/arrays'; import { ScanCodeUtils, ScanCode } from 'vs/base/common/scanCode'; import { isMacintosh } from 'vs/base/common/platform'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { Codicon, registerIcon } from 'vs/base/common/codicons'; const $ = DOM.$; @@ -40,7 +39,7 @@ export interface IMenuBarOptions { } export interface MenuBarMenu { - actions: ReadonlyArray; + actions: IAction[]; label: string; } @@ -59,7 +58,7 @@ export class MenuBar extends Disposable { buttonElement: HTMLElement; titleElement: HTMLElement; label: string; - actions?: ReadonlyArray; + actions?: IAction[]; }[]; private overflowMenu!: { @@ -506,7 +505,7 @@ export class MenuBar extends Disposable { this.overflowMenu.actions = []; for (let idx = this.numMenusShown; idx < this.menuCache.length; idx++) { - this.overflowMenu.actions.push(new SubmenuAction(this.menuCache[idx].label, this.menuCache[idx].actions || [])); + this.overflowMenu.actions.push(new SubmenuAction(`menubar.submenu.${this.menuCache[idx].label}`, this.menuCache[idx].label, this.menuCache[idx].actions || [])); } if (this.overflowMenu.buttonElement.nextElementSibling !== this.menuCache[this.numMenusShown].buttonElement) { diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 8d78b986a6..44787ee572 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -5,17 +5,16 @@ import 'vs/css!./toolbar'; import * as nls from 'vs/nls'; -import { Action, IActionRunner, IAction } from 'vs/base/common/actions'; -import { ActionBar, ActionsOrientation, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IContextMenuProvider, DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; +import { Action, IActionRunner, IAction, IActionViewItemProvider, SubmenuAction } from 'vs/base/common/actions'; +import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { withNullAsUndefined } from 'vs/base/common/types'; import { Codicon, registerIcon } from 'vs/base/common/codicons'; -import { Emitter } from 'vs/base/common/event'; - -export const CONTEXT = 'context.toolbar'; +import { EventMultiplexer } from 'vs/base/common/event'; +import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; +import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; const toolBarMoreIcon = registerIcon('toolbar-more', Codicon.more); @@ -37,12 +36,13 @@ export class ToolBar extends Disposable { private actionBar: ActionBar; private toggleMenuAction: ToggleMenuAction; private toggleMenuActionViewItem: DropdownMenuActionViewItem | undefined; - private toggleMenuActionViewItemDisposable: IDisposable = Disposable.None; + private submenuActionViewItems: DropdownMenuActionViewItem[] = []; private hasSecondaryActions: boolean = false; private lookupKeybindings: boolean; - private _onDidChangeDropdownVisibility = this._register(new Emitter()); + private _onDidChangeDropdownVisibility = this._register(new EventMultiplexer()); readonly onDidChangeDropdownVisibility = this._onDidChangeDropdownVisibility.event; + private disposables = new DisposableStore(); constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) { super(); @@ -61,35 +61,57 @@ export class ToolBar extends Disposable { ariaLabel: options.ariaLabel, actionRunner: options.actionRunner, actionViewItemProvider: (action: IAction) => { - - // Return special action item for the toggle menu action if (action.id === ToggleMenuAction.ID) { - - this.toggleMenuActionViewItemDisposable.dispose(); - - // Create new this.toggleMenuActionViewItem = new DropdownMenuActionViewItem( action, (action).menuActions, contextMenuProvider, - this.options.actionViewItemProvider, - this.actionRunner, - this.options.getKeyBinding, - toolBarMoreIcon.classNames, - this.options.anchorAlignmentProvider, - true + { + actionViewItemProvider: this.options.actionViewItemProvider, + actionRunner: this.actionRunner, + keybindingProvider: this.options.getKeyBinding, + classNames: toolBarMoreIcon.classNames, + anchorAlignmentProvider: this.options.anchorAlignmentProvider, + menuAsChild: true + } ); this.toggleMenuActionViewItem.setActionContext(this.actionBar.context); - - this.toggleMenuActionViewItemDisposable = combinedDisposable( - this.toggleMenuActionViewItem, - this.toggleMenuActionViewItem.onDidChangeVisibility(e => this._onDidChangeDropdownVisibility.fire(e)) - ); + this.disposables.add(this._onDidChangeDropdownVisibility.add(this.toggleMenuActionViewItem.onDidChangeVisibility)); return this.toggleMenuActionViewItem; } - return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined; + if (options.actionViewItemProvider) { + const result = options.actionViewItemProvider(action); + + if (result) { + return result; + } + } + + if (action instanceof SubmenuAction) { + const actions = Array.isArray(action.actions) ? action.actions : action.actions(); + const result = new DropdownMenuActionViewItem( + action, + actions, + contextMenuProvider, + { + actionViewItemProvider: this.options.actionViewItemProvider, + actionRunner: this.actionRunner, + keybindingProvider: this.options.getKeyBinding, + classNames: action.class, + anchorAlignmentProvider: this.options.anchorAlignmentProvider, + menuAsChild: true + } + ); + result.setActionContext(this.actionBar.context); + this.submenuActionViewItems.push(result); + this.disposables.add(this._onDidChangeDropdownVisibility.add(result.onDidChangeVisibility)); + + return result; + } + + return undefined; } })); } @@ -107,6 +129,9 @@ export class ToolBar extends Disposable { if (this.toggleMenuActionViewItem) { this.toggleMenuActionViewItem.setActionContext(context); } + for (const actionViewItem of this.submenuActionViewItems) { + actionViewItem.setActionContext(context); + } } getContainer(): HTMLElement { @@ -126,6 +151,8 @@ export class ToolBar extends Disposable { } setActions(primaryActions: ReadonlyArray, secondaryActions?: ReadonlyArray): void { + this.clear(); + let primaryActionsToSet = primaryActions ? primaryActions.slice(0) : []; // Inject additional action to open secondary actions if present @@ -135,8 +162,6 @@ export class ToolBar extends Disposable { primaryActionsToSet.push(this.toggleMenuAction); } - this.actionBar.clear(); - primaryActionsToSet.forEach(action => { this.actionBar.push(action, { icon: true, label: false, keybinding: this.getKeybindingLabel(action) }); }); @@ -148,25 +173,15 @@ export class ToolBar extends Disposable { return withNullAsUndefined(key?.getLabel()); } - addPrimaryAction(primaryAction: IAction): () => void { - return () => { - - // Add after the "..." action if we have secondary actions - if (this.hasSecondaryActions) { - let itemCount = this.actionBar.length(); - this.actionBar.push(primaryAction, { icon: true, label: false, index: itemCount, keybinding: this.getKeybindingLabel(primaryAction) }); - } - - // Otherwise just add to the end - else { - this.actionBar.push(primaryAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(primaryAction) }); - } - }; + private clear(): void { + this.submenuActionViewItems = []; + this.disposables.clear(); + this.actionBar.clear(); } dispose(): void { + this.clear(); super.dispose(); - this.toggleMenuActionViewItemDisposable.dispose(); } } diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 14d6633ca9..b60edab1a6 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -39,14 +39,18 @@ export interface IActionRunner extends IDisposable { } export interface IActionViewItem extends IDisposable { - readonly actionRunner: IActionRunner; + actionRunner: IActionRunner; setActionContext(context: any): void; render(element: any /* HTMLElement */): void; isEnabled(): boolean; - focus(): void; + focus(fromRight?: boolean): void; // TODO@isidorn what is this? blur(): void; } +export interface IActionViewItemProvider { + (action: IAction): IActionViewItem | undefined; +} + export interface IActionChangeEvent { readonly label?: string; readonly tooltip?: string; @@ -218,3 +222,22 @@ export class RadioGroup extends Disposable { } } } + +export class Separator extends Action { + + static readonly ID = 'vs.actions.separator'; + + constructor(label?: string) { + super(Separator.ID, label, label ? 'separator text' : 'separator'); + this.checked = false; + this.enabled = false; + } +} + +export type SubmenuActions = IAction[] | (() => IAction[]); + +export class SubmenuAction extends Action { + constructor(id: string, label: string, readonly actions: SubmenuActions, cssClass?: string) { + super(id, label, cssClass, true); + } +} diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 772bd0b824..ccec07c0b1 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -473,11 +473,10 @@ export function range(arg: number, to?: number): number[] { } export function index(array: ReadonlyArray, indexer: (t: T) => string): { [key: string]: T; }; -export function index(array: ReadonlyArray, indexer: (t: T) => string, merger?: (t: T, r: R) => R): { [key: string]: R; }; -export function index(array: ReadonlyArray, indexer: (t: T) => string, merger: (t: T, r: R) => R = t => t as any): { [key: string]: R; } { +export function index(array: ReadonlyArray, indexer: (t: T) => string, mapper: (t: T) => R): { [key: string]: R; }; +export function index(array: ReadonlyArray, indexer: (t: T) => string, mapper?: (t: T) => R): { [key: string]: R; } { return array.reduce((r, t) => { - const key = indexer(t); - r[key] = merger(t, r[key]); + r[indexer(t)] = mapper ? mapper(t) : t; return r; }, Object.create(null)); } diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index a1775a9ac9..f17d0de55c 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -18,7 +18,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Button, IButtonStyles } from 'vs/base/browser/ui/button/button'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import Severity from 'vs/base/common/severity'; -import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { equals } from 'vs/base/common/arrays'; import { TimeoutTimer } from 'vs/base/common/async'; @@ -28,6 +28,7 @@ import { List, IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWid import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { Color } from 'vs/base/common/color'; import { registerIcon, Codicon } from 'vs/base/common/codicons'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export interface IQuickInputOptions { idPrefix: string; diff --git a/src/vs/base/test/browser/actionbar.test.ts b/src/vs/base/test/browser/actionbar.test.ts index 5363219378..c83aef6d9f 100644 --- a/src/vs/base/test/browser/actionbar.test.ts +++ b/src/vs/base/test/browser/actionbar.test.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Separator, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; -import { Action } from 'vs/base/common/actions'; +import { prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Action, Separator } from 'vs/base/common/actions'; suite('Actionbar', () => { diff --git a/src/vs/code/buildfile.js b/src/vs/code/buildfile.js index 6c0c89a30c..cc3701e30d 100644 --- a/src/vs/code/buildfile.js +++ b/src/vs/code/buildfile.js @@ -22,9 +22,8 @@ exports.collectModules = function () { createModuleDescription('vs/code/electron-main/main', []), createModuleDescription('vs/code/node/cli', []), createModuleDescription('vs/code/node/cliProcessMain', ['vs/code/node/cli']), - createModuleDescription('vs/code/electron-browser/issue/issueReporterMain', []), + createModuleDescription('vs/code/electron-sandbox/issue/issueReporterMain', []), createModuleDescription('vs/code/electron-browser/sharedProcess/sharedProcessMain', []), - createModuleDescription('vs/code/electron-browser/issue/issueReporterMain', []), createModuleDescription('vs/platform/driver/node/driver', []), createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorerMain', []) ]; diff --git a/src/vs/code/electron-browser/issue/issueReporter.html b/src/vs/code/electron-sandbox/issue/issueReporter.html similarity index 100% rename from src/vs/code/electron-browser/issue/issueReporter.html rename to src/vs/code/electron-sandbox/issue/issueReporter.html diff --git a/src/vs/code/electron-browser/issue/issueReporter.js b/src/vs/code/electron-sandbox/issue/issueReporter.js similarity index 92% rename from src/vs/code/electron-browser/issue/issueReporter.js rename to src/vs/code/electron-sandbox/issue/issueReporter.js index 6103f1f09a..48cab308fa 100644 --- a/src/vs/code/electron-browser/issue/issueReporter.js +++ b/src/vs/code/electron-sandbox/issue/issueReporter.js @@ -14,6 +14,6 @@ const bootstrapWindow = (() => { return window.MonacoBootstrapWindow; })(); -bootstrapWindow.load(['vs/code/electron-browser/issue/issueReporterMain'], function (issueReporter, configuration) { +bootstrapWindow.load(['vs/code/electron-sandbox/issue/issueReporterMain'], function (issueReporter, configuration) { issueReporter.startup(configuration); }, { forceEnableDeveloperKeybindings: true, disallowReloadKeybinding: true }); diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts similarity index 87% rename from src/vs/code/electron-browser/issue/issueReporterMain.ts rename to src/vs/code/electron-sandbox/issue/issueReporterMain.ts index 184a195841..7b78dc79fb 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -5,9 +5,8 @@ import 'vs/css!./media/issueReporter'; import 'vs/base/browser/ui/codicons/codiconStyles'; // make sure codicon css is loaded -import * as os from 'os'; import { ElectronService, IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; -import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { ipcRenderer, process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox/window'; import { $, windowOpenNoOpener, addClass } from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; @@ -17,29 +16,15 @@ import { debounce } from 'vs/base/common/decorators'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { escape } from 'vs/base/common/strings'; -import { getDelayedChannel, createChannelSender } from 'vs/base/parts/ipc/common/ipc'; -import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; -import { IssueReporterData as IssueReporterModelData, IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel'; -import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage'; +import { IssueReporterData as IssueReporterModelData, IssueReporterModel } from 'vs/code/electron-sandbox/issue/issueReporterModel'; +import BaseHtml from 'vs/code/electron-sandbox/issue/issueReporterPage'; import { localize } from 'vs/nls'; import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics'; -import { EnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { ISettingsSearchIssueReporterData, IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue'; -import { getLogLevel, ILogService } from 'vs/platform/log/common/log'; -import { FollowerLogService, LoggerChannelClient } from 'vs/platform/log/common/logIpc'; -import { SpdLogService } from 'vs/platform/log/node/spdlogService'; -import product from 'vs/platform/product/common/product'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { combinedAppender, LogAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; -import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; -import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; +import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; const MAX_URL_LENGTH = 2045; @@ -49,9 +34,23 @@ interface SearchResult { state?: string; } -export interface IssueReporterConfiguration extends INativeWindowConfiguration { +export interface IssueReporterConfiguration extends IWindowConfiguration { + windowId: number; + disableExtensions: boolean; data: IssueReporterData; features: IssueReporterFeatures; + os: { + type: string; + arch: string; + release: string; + }, + product: { + nameShort: string; + version: string; + commit: string | undefined; + date: string | undefined; + reportIssueUrl: string | undefined; + } } export function startup(configuration: IssueReporterConfiguration) { @@ -66,10 +65,7 @@ export function startup(configuration: IssueReporterConfiguration) { } export class IssueReporter extends Disposable { - private environmentService!: INativeEnvironmentService; private electronService!: IElectronService; - private telemetryService!: ITelemetryService; - private logService!: ILogService; private readonly issueReporterModel: IssueReporterModel; private numberOfSearchResultsDisplayed = 0; private receivedSystemInfo = false; @@ -79,7 +75,7 @@ export class IssueReporter extends Disposable { private readonly previewButton!: Button; - constructor(configuration: IssueReporterConfiguration) { + constructor(private readonly configuration: IssueReporterConfiguration) { super(); this.initServices(configuration); @@ -90,10 +86,10 @@ export class IssueReporter extends Disposable { this.issueReporterModel = new IssueReporterModel({ issueType: configuration.data.issueType || IssueType.Bug, versionInfo: { - vscodeVersion: `${product.nameShort} ${product.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`, - os: `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}` + vscodeVersion: `${configuration.product.nameShort} ${configuration.product.version} (${configuration.product.commit || 'Commit unknown'}, ${configuration.product.date || 'Date unknown'})`, + os: `${this.configuration.os.type} ${this.configuration.os.arch} ${this.configuration.os.release}${isSnap ? ' snap' : ''}` }, - extensionsDisabled: !!this.environmentService.disableExtensions, + extensionsDisabled: !!configuration.disableExtensions, fileOnExtension: configuration.data.extensionId ? !targetExtension?.isBuiltin : undefined, selectedExtension: targetExtension, }); @@ -121,7 +117,6 @@ export class IssueReporter extends Disposable { } ipcRenderer.on('vscode:issuePerformanceInfoResponse', (_: unknown, info: Partial) => { - this.logService.trace('issueReporter: Received performance data'); this.issueReporterModel.update(info); this.receivedPerformanceInfo = true; @@ -132,7 +127,6 @@ export class IssueReporter extends Disposable { }); ipcRenderer.on('vscode:issueSystemInfoResponse', (_: unknown, info: SystemInfo) => { - this.logService.trace('issueReporter: Received system data'); this.issueReporterModel.update({ systemInfo: info }); this.receivedSystemInfo = true; @@ -144,7 +138,6 @@ export class IssueReporter extends Disposable { if (configuration.data.issueType === IssueType.PerformanceIssue) { ipcRenderer.send('vscode:issuePerformanceInfoRequest'); } - this.logService.trace('issueReporter: Sent data requests'); if (window.document.documentElement.lang !== 'en') { show(this.getElementById('english')); @@ -266,7 +259,7 @@ export class IssueReporter extends Disposable { this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: installedExtensions }); this.updateExtensionTable(nonThemes, numberOfThemeExtesions); - if (this.environmentService.disableExtensions || installedExtensions.length === 0) { + if (this.configuration.disableExtensions || installedExtensions.length === 0) { (this.getElementById('disableExtensions')).disabled = true; } @@ -314,40 +307,13 @@ export class IssueReporter extends Disposable { } } - private initServices(configuration: INativeWindowConfiguration): void { + private initServices(configuration: IssueReporterConfiguration): void { const serviceCollection = new ServiceCollection(); const mainProcessService = new MainProcessService(configuration.windowId); serviceCollection.set(IMainProcessService, mainProcessService); this.electronService = new ElectronService(configuration.windowId, mainProcessService) as IElectronService; serviceCollection.set(IElectronService, this.electronService); - - this.environmentService = new EnvironmentService(configuration, configuration.execPath); - - const logService = new SpdLogService(`issuereporter${configuration.windowId}`, this.environmentService.logsPath, getLogLevel(this.environmentService)); - const loggerClient = new LoggerChannelClient(mainProcessService.getChannel('logger')); - this.logService = new FollowerLogService(loggerClient, logService); - - const sharedProcessService = createChannelSender(mainProcessService.getChannel('sharedProcess')); - - const sharedProcess = sharedProcessService.whenSharedProcessReady() - .then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${configuration.windowId}`)); - - const instantiationService = new InstantiationService(serviceCollection, true); - 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 || 'Commit unknown', product.version, configuration.machineId, product.msftInternalDomains, this.environmentService.installSourcePath); - const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot]; - const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true }; - - const telemetryService = instantiationService.createInstance(TelemetryService, config); - this._register(telemetryService); - - this.telemetryService = telemetryService; - } else { - this.telemetryService = NullTelemetryService; - } } private setEventHandlers(): void { @@ -617,11 +583,11 @@ export class IssueReporter extends Disposable { }, timeToWait * 1000); } } - }).catch(e => { - this.logSearchError(e); + }).catch(_ => { + // Ignore }); - }).catch(e => { - this.logSearchError(e); + }).catch(_ => { + // Ignore }); } @@ -648,11 +614,11 @@ export class IssueReporter extends Disposable { } else { throw new Error('Unexpected response, no candidates property'); } - }).catch((error) => { - this.logSearchError(error); + }).catch(_ => { + // Ignore }); - }).catch((error) => { - this.logSearchError(error); + }).catch(_ => { + // Ignore }); } @@ -705,18 +671,6 @@ export class IssueReporter extends Disposable { } } - private logSearchError(error: Error) { - this.logService.warn('issueReporter#search ', error.message); - type IssueReporterSearchErrorClassification = { - message: { classification: 'CallstackOrException', purpose: 'PerformanceAndHealth' } - }; - - type IssueReporterSearchError = { - message: string; - }; - this.telemetryService.publicLogError2('issueReporterSearchError', { message: error.message }); - } - private setUpTypes(): void { const makeOption = (issueType: IssueType, description: string) => ``; @@ -910,15 +864,6 @@ export class IssueReporter extends Disposable { return false; } - type IssueReporterSubmitClassification = { - issueType: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - numSimilarIssuesDisplayed: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - }; - type IssueReporterSubmitEvent = { - issueType: any; - numSimilarIssuesDisplayed: number; - }; - this.telemetryService.publicLog2('issueReporterSubmit', { issueType: this.issueReporterModel.getData().issueType, numSimilarIssuesDisplayed: this.numberOfSearchResultsDisplayed }); this.hasBeenSubmitted = true; const baseUrl = this.getIssueUrlWithTitle((this.getElementById('issue-title')).value); @@ -967,7 +912,7 @@ export class IssueReporter extends Disposable { } private getIssueUrlWithTitle(issueTitle: string): string { - let repositoryUrl = product.reportIssueUrl; + let repositoryUrl = this.configuration.product.reportIssueUrl; if (this.issueReporterModel.fileOnExtension()) { const extensionGitHubUrl = this.getExtensionGitHubUrl(); if (extensionGitHubUrl) { @@ -975,7 +920,7 @@ export class IssueReporter extends Disposable { } } - const queryStringPrefix = product.reportIssueUrl && product.reportIssueUrl.indexOf('?') === -1 ? '?' : '&'; + const queryStringPrefix = this.configuration.product.reportIssueUrl && this.configuration.product.reportIssueUrl.indexOf('?') === -1 ? '?' : '&'; return `${repositoryUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`; } @@ -1136,7 +1081,7 @@ export class IssueReporter extends Disposable { private updateExtensionTable(extensions: IssueReporterExtensionData[], numThemeExtensions: number): void { const target = document.querySelector('.block-extensions .block-info'); if (target) { - if (this.environmentService.disableExtensions) { + if (this.configuration.disableExtensions) { target.innerHTML = localize('disabledExtensions', "Extensions are disabled"); return; } @@ -1193,7 +1138,6 @@ export class IssueReporter extends Disposable { // Exclude right click if (event.which < 3) { windowOpenNoOpener((event.target).href); - this.telemetryService.publicLog2('issueReporterViewSimilarIssue'); } } diff --git a/src/vs/code/electron-browser/issue/issueReporterModel.ts b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts similarity index 97% rename from src/vs/code/electron-browser/issue/issueReporterModel.ts rename to src/vs/code/electron-sandbox/issue/issueReporterModel.ts index 101b556e61..7cc9455f6b 100644 --- a/src/vs/code/electron-browser/issue/issueReporterModel.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { assign } from 'vs/base/common/objects'; import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/common/issue'; import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; @@ -49,7 +48,7 @@ export class IssueReporterModel { allExtensions: [] }; - this._data = initialData ? assign(defaultData, initialData) : defaultData; + this._data = initialData ? Object.assign(defaultData, initialData) : defaultData; } getData(): IssueReporterData { @@ -57,7 +56,7 @@ export class IssueReporterModel { } update(newData: Partial): void { - assign(this._data, newData); + Object.assign(this._data, newData); } // {{SQL CARBON EDIT}} diff --git a/src/vs/code/electron-browser/issue/issueReporterPage.ts b/src/vs/code/electron-sandbox/issue/issueReporterPage.ts similarity index 100% rename from src/vs/code/electron-browser/issue/issueReporterPage.ts rename to src/vs/code/electron-sandbox/issue/issueReporterPage.ts diff --git a/src/vs/code/electron-browser/issue/media/issueReporter.css b/src/vs/code/electron-sandbox/issue/media/issueReporter.css similarity index 100% rename from src/vs/code/electron-browser/issue/media/issueReporter.css rename to src/vs/code/electron-sandbox/issue/media/issueReporter.css diff --git a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts b/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts similarity index 98% rename from src/vs/code/electron-browser/issue/test/testReporterModel.test.ts rename to src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts index d25195a9be..564960924e 100644 --- a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts +++ b/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel'; +import { IssueReporterModel } from 'vs/code/electron-sandbox/issue/issueReporterModel'; import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; import { IssueType } from 'vs/platform/issue/common/issue'; diff --git a/src/vs/editor/contrib/codeAction/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/codeActionMenu.ts index 9eeb38d33a..8a06d7dd68 100644 --- a/src/vs/editor/contrib/codeAction/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/codeActionMenu.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { getDomNodePagePosition } from 'vs/base/browser/dom'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; -import { Action, IAction } from 'vs/base/common/actions'; +import { Action, IAction, Separator } from 'vs/base/common/actions'; import { canceled } from 'vs/base/common/errors'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index dda6683316..81f846b049 100644 --- a/src/vs/editor/contrib/contextmenu/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/contextmenu.ts @@ -6,9 +6,8 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { ActionViewItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, Separator, SubmenuAction } from 'vs/base/common/actions'; import { KeyCode, KeyMod, ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; @@ -23,7 +22,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { ITextModel } from 'vs/editor/common/model'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { ContextSubMenu } from 'vs/base/browser/contextmenu'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class ContextMenuController implements IEditorContribution { @@ -153,7 +152,7 @@ export class ContextMenuController implements IEditorContribution { if (action instanceof SubmenuItemAction) { const subActions = this._getMenuActions(model, action.item.submenu); if (subActions.length > 0) { - result.push(new ContextSubMenu(action.label, subActions)); + result.push(new SubmenuAction(action.id, action.label, subActions)); addedItems++; } } else { @@ -174,7 +173,7 @@ export class ContextMenuController implements IEditorContribution { return result; } - private _doShowContextMenu(actions: ReadonlyArray, anchor: IAnchor | null = null): void { + private _doShowContextMenu(actions: IAction[], anchor: IAnchor | null = null): void { if (!this._editor.hasModel()) { return; } diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index d0fe8db126..ea051172ce 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -26,9 +26,9 @@ import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/action import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuId, IMenuService } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; class MessageWidget { @@ -246,10 +246,10 @@ export class MarkerNavigationWidget extends PeekViewWidget { @IThemeService private readonly _themeService: IThemeService, @IOpenerService private readonly _openerService: IOpenerService, @IMenuService private readonly _menuService: IMenuService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService ) { - super(editor, { showArrow: true, showFrame: true, isAccessible: true }); + super(editor, { showArrow: true, showFrame: true, isAccessible: true }, instantiationService); this._severity = MarkerSeverity.Warning; this._backgroundColor = Color.white; @@ -311,8 +311,8 @@ export class MarkerNavigationWidget extends PeekViewWidget { protected _getActionBarOptions(): IActionBarOptions { return { - orientation: ActionsOrientation.HORIZONTAL, - actionViewItemProvider: action => action instanceof MenuItemAction ? this._instantiationService.createInstance(MenuEntryActionViewItem, action) : undefined + ...super._getActionBarOptions(), + orientation: ActionsOrientation.HORIZONTAL }; } diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts index 03508fe154..47f851c9bd 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts @@ -220,7 +220,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { @ILabelService private readonly _uriLabel: ILabelService, @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, ) { - super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true }); + super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true }, _instantiationService); this._applyTheme(themeService.getColorTheme()); this._callOnDispose.add(themeService.onDidColorThemeChange(this._applyTheme.bind(this))); diff --git a/src/vs/editor/contrib/peekView/peekView.ts b/src/vs/editor/contrib/peekView/peekView.ts index bf1f4db9d3..93a14207c5 100644 --- a/src/vs/editor/contrib/peekView/peekView.ts +++ b/src/vs/editor/contrib/peekView/peekView.ts @@ -18,7 +18,7 @@ import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeE import { IOptions, IStyles, ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import * as nls from 'vs/nls'; import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor, createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -26,7 +26,8 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { registerColor, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { Codicon } from 'vs/base/common/codicons'; - +import { MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; export const IPeekViewService = createDecorator('IPeekViewService'); export interface IPeekViewService { @@ -115,7 +116,11 @@ export abstract class PeekViewWidget extends ZoneWidget { protected _actionbarWidget?: ActionBar; protected _bodyElement?: HTMLDivElement; - constructor(editor: ICodeEditor, options: IPeekViewOptions = {}) { + constructor( + editor: ICodeEditor, + options: IPeekViewOptions, + @IInstantiationService protected readonly instantiationService: IInstantiationService + ) { super(editor, options); objects.mixin(this.options, defaultOptions, false); } @@ -199,7 +204,17 @@ export abstract class PeekViewWidget extends ZoneWidget { } protected _getActionBarOptions(): IActionBarOptions { - return {}; + return { + actionViewItemProvider: action => { + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); + } + + return undefined; + } + }; } protected _onTitleClick(event: IMouseEvent): void { diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index ddeed38978..5afb938e71 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -43,9 +43,10 @@ import { MarkdownString } from 'vs/base/common/htmlContent'; import { flatten, isFalsyOrEmpty } from 'vs/base/common/arrays'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMenuService } from 'vs/platform/actions/common/actions'; -import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IAction } from 'vs/base/common/actions'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction, IActionViewItemProvider } from 'vs/base/common/actions'; import { Codicon, registerIcon } from 'vs/base/common/codicons'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; const expandSuggestionDocsByDefault = false; diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 7ce37993a8..562ddc90da 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -552,6 +552,9 @@ export class StandaloneTelemetryService implements ITelemetryService { public setEnabled(value: boolean): void { } + public setExperimentProperty(name: string, value: string): void { + } + public publicLog(eventName: string, data?: any): Promise { return Promise.resolve(undefined); } diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 158a797f75..a3fcc4bacd 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -5,8 +5,7 @@ import { addClasses, createCSSRule, removeClasses, asCSSUrl } from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; -import { ActionViewItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, Separator } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; import { IdGenerator } from 'vs/base/common/idGenerator'; import { IDisposable, toDisposable, MutableDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; @@ -17,6 +16,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; // The alternative key on all platforms is alt. On windows we also support shift as an alternative key #44136 class AlternativeKeyEmitter extends Emitter { @@ -125,19 +126,11 @@ export function fillInActions(groups: ReadonlyArray<[string, ReadonlyArray(); - static readonly ICON_PATH_TO_CSS_RULES: Map = new Map(); +export class MenuEntryActionViewItem extends ActionViewItem { private _wantsAltCommand: boolean = false; private readonly _itemClassDispose = this._register(new MutableDisposable()); @@ -165,7 +158,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { this._altKey.suppressAltKeyUp(); } - this.actionRunner.run(this._commandAction) + this.actionRunner.run(this._commandAction, this._context) .then(undefined, err => this._notificationService.error(err)); } @@ -236,7 +229,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { } } - _updateItemClass(item: ICommandAction): void { + protected _updateItemClass(item: ICommandAction): void { // {{SQL CARBON EDIT}} make it overwritable this._itemClassDispose.value = undefined; const icon = this._commandAction.checked && (item.toggled as { icon?: Icon })?.icon ? (item.toggled as { icon: Icon }).icon : item.icon; @@ -257,17 +250,17 @@ export class MenuEntryActionViewItem extends ActionViewItem { // icon path let iconClass: string; - if (icon?.dark?.scheme) { + if (icon.dark?.scheme) { const iconPathMapKey = icon.dark.toString(); - if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { - iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; + if (ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { + iconClass = ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; } else { iconClass = ids.nextId(); createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(icon.light || icon.dark)}`); createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(icon.dark)}`); - MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); + ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); } if (this.label) { @@ -284,17 +277,34 @@ export class MenuEntryActionViewItem extends ActionViewItem { } } -// Need to subclass MenuEntryActionViewItem in order to respect -// the action context coming from any action bar, without breaking -// existing users -export class ContextAwareMenuEntryActionViewItem extends MenuEntryActionViewItem { +export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem { - onClick(event: MouseEvent): void { - event.preventDefault(); - event.stopPropagation(); + constructor( + action: SubmenuItemAction, + @INotificationService _notificationService: INotificationService, + @IContextMenuService _contextMenuService: IContextMenuService + ) { + const classNames: string[] = []; - this.actionRunner.run(this._commandAction, this._context) - .then(undefined, err => this._notificationService.error(err)); + if (action.item.icon) { + if (ThemeIcon.isThemeIcon(action.item.icon)) { + classNames.push(ThemeIcon.asClassName(action.item.icon)!); + } else if (action.item.icon.dark?.scheme) { + const iconPathMapKey = action.item.icon.dark.toString(); + + if (ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { + classNames.push('icon', ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!); + } else { + const className = ids.nextId(); + classNames.push('icon', className); + createCSSRule(`.icon.${className}`, `background-image: ${asCSSUrl(action.item.icon.light || action.item.icon.dark)}`); + createCSSRule(`.vs-dark .icon.${className}, .hc-black .icon.${className}`, `background-image: ${asCSSUrl(action.item.icon.dark)}`); + ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, className); + } + } + } + + super(action, Array.isArray(action.actions) ? action.actions : action.actions(), _contextMenuService, { classNames }); } } @@ -323,7 +333,7 @@ export class LabeledMenuItemActionItem extends MenuEntryActionViewItem { // Overwrite item class to ensure that we can pass in a CSS class that other items use // Leverages the _defaultCSSClassToAdd property that's passed into the constructor - _updateItemClass(item: ICommandAction): void { + protected _updateItemClass(item: ICommandAction): void { dispose(this._labeledItemClassDispose); this._labeledItemClassDispose = undefined; @@ -336,13 +346,13 @@ export class LabeledMenuItemActionItem extends MenuEntryActionViewItem { if (item.icon?.dark?.scheme) { const iconPathMapKey = item.icon.dark.toString(); - if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { - iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; + if (ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { + iconClass = ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; } else { iconClass = ids.nextId(); createCSSRule(`.codicon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.light || item.icon.dark)}`); createCSSRule(`.vs-dark .codicon.${iconClass}, .hc-black .codicon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.dark)}`); - MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); + ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); } if (this.label) { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index caefe60e16..c9c73e0e36 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Action } from 'vs/base/common/actions'; +import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions'; import { SyncDescriptor0, createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IConstructorSignature2, createDecorator, BrandedService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindings, KeybindingsRegistry, IKeybindingRule } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -47,6 +47,7 @@ export interface IMenuItem { export interface ISubmenuItem { title: string | ILocalizedString; submenu: MenuId; + icon?: Icon; when?: ContextKeyExpression; group?: 'navigation' | string; order?: number; @@ -302,12 +303,35 @@ export class ExecuteCommandAction extends Action { } } -export class SubmenuItemAction extends Action { +export class SubmenuItemAction extends SubmenuAction { - readonly item: ISubmenuItem; - constructor(item: ISubmenuItem) { - typeof item.title === 'string' ? super('', item.title, 'submenu') : super('', item.title.value, 'submenu'); - this.item = item; + constructor( + readonly item: ISubmenuItem, + menuService: IMenuService, + contextKeyService: IContextKeyService, + options?: IMenuActionOptions + ) { + super(`submenuitem.${item.submenu.id}`, typeof item.title === 'string' ? item.title : item.title.value, () => { + const result: IAction[] = []; + const menu = menuService.createMenu(item.submenu, contextKeyService); + const groups = menu.getActions(options); + menu.dispose(); + + for (let group of groups) { + const [, actions] = group; + + if (actions.length > 0) { + result.push(...actions); + result.push(new Separator()); + } + } + + if (result.length) { + result.pop(); // remove last separator + } + + return result; + }, 'submenu'); } } diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index b6570bcef1..b945fc432b 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -20,7 +20,7 @@ export class MenuService implements IMenuService { } createMenu(id: MenuId, contextKeyService: IContextKeyService): IMenu { - return new Menu(id, this._commandService, contextKeyService); + return new Menu(id, this._commandService, contextKeyService, this); } } @@ -38,7 +38,8 @@ class Menu implements IMenu { constructor( private readonly _id: MenuId, @ICommandService private readonly _commandService: ICommandService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IMenuService private readonly _menuService: IMenuService ) { this._build(); @@ -114,7 +115,7 @@ class Menu implements IMenu { if (this._contextKeyService.contextMatchesRules(item.when)) { const action = isIMenuItem(item) ? new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService) - : new SubmenuItemAction(item); + : new SubmenuItemAction(item, this._menuService, this._contextKeyService, options); activeActions.push(action); } diff --git a/src/vs/platform/browser/checkbox.ts b/src/vs/platform/browser/checkbox.ts index 009cf20994..7e0330286a 100644 --- a/src/vs/platform/browser/checkbox.ts +++ b/src/vs/platform/browser/checkbox.ts @@ -3,11 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import { CheckboxActionViewItem } from 'vs/base/browser/ui/checkbox/checkbox'; import { IAction } from 'vs/base/common/actions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachCheckboxStyler } from 'vs/platform/theme/common/styler'; +import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class ThemableCheckboxActionViewItem extends CheckboxActionViewItem { diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 33737e2dbc..31f0e7e3c4 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -170,7 +170,7 @@ class Query { withFilter(filterType: FilterType, ...values: string[]): Query { const criteria = [ ...this.state.criteria, - ...values.map(value => ({ filterType, value })) + ...values.length ? values.map(value => ({ filterType, value })) : [{ filterType }] ]; return new Query(assign({}, this.state, { criteria })); @@ -494,6 +494,12 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return ''; }); + // Use featured filter + text = text.replace(/\bfeatured(\s+|\b|$)/g, () => { + query = query.withFilter(FilterType.Featured); + return ''; + }); + text = text.trim(); if (text) { diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 4d1bf88d9e..c6aa2c53f1 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; +import * as os from 'os'; +import product from 'vs/platform/product/common/product'; import * as objects from 'vs/base/common/objects'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { ICommonIssueService, IssueReporterData, IssueReporterFeatures, ProcessExplorerData } from 'vs/platform/issue/common/issue'; @@ -196,12 +198,23 @@ export class IssueMainService implements ICommonIssueService { backgroundColor: data.styles.backgroundColor || DEFAULT_BACKGROUND_COLOR, webPreferences: { preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath, - nodeIntegration: true, enableWebSQL: false, enableRemoteModule: false, spellcheck: false, nativeWindowOpen: true, - zoomFactor: zoomLevelToZoomFactor(data.zoomLevel) + zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), + ...this.environmentService.sandbox ? + + // Sandbox + { + sandbox: true, + contextIsolation: true + } : + + // No Sandbox + { + nodeIntegration: true + } } }); @@ -408,10 +421,23 @@ export class IssueMainService implements ICommonIssueService { machineId: this.machineId, userEnv: this.userEnv, data, - features + features, + disableExtensions: this.environmentService.disableExtensions, + os: { + type: os.type(), + arch: os.arch(), + release: os.release(), + }, + product: { + nameShort: product.nameShort, + version: product.version, + commit: product.commit, + date: product.date, + reportIssueUrl: product.reportIssueUrl + } }; - return toLauchUrl('vs/code/electron-browser/issue/issueReporter.html', windowConfiguration); + return toLauchUrl('vs/code/electron-sandbox/issue/issueReporter.html', windowConfiguration); } } diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 74c4499dd1..63b1845bc9 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -49,6 +49,13 @@ export interface IProductConfiguration { readonly settingsSearchBuildId?: number; readonly settingsSearchUrl?: string; + readonly tasConfig?: { + endpoint: string; + telemetryEventName: string; + featuresTelemetryPropertyName: string; + assignmentContextTelemetryPropertyName: string; + }; + readonly experimentsUrl?: string; readonly extensionsGallery?: { diff --git a/src/vs/platform/telemetry/common/telemetry.ts b/src/vs/platform/telemetry/common/telemetry.ts index 2563bb819b..f192000ff4 100644 --- a/src/vs/platform/telemetry/common/telemetry.ts +++ b/src/vs/platform/telemetry/common/telemetry.ts @@ -46,6 +46,8 @@ export interface ITelemetryService { getTelemetryInfo(): Promise; + setExperimentProperty(name: string, value: string): void; + isOptedIn: boolean; } diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 4288f10e99..3f23651387 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -31,6 +31,7 @@ export class TelemetryService implements ITelemetryService { private _appender: ITelemetryAppender; private _commonProperties: Promise<{ [name: string]: any; }>; + private _experimentProperties: { [name: string]: string } = {}; private _piiPaths: string[]; private _userOptIn: boolean; private _enabled: boolean; @@ -79,6 +80,10 @@ export class TelemetryService implements ITelemetryService { } } + setExperimentProperty(name: string, value: string): void { + this._experimentProperties[name] = value; + } + setEnabled(value: boolean): void { this._enabled = value; } @@ -119,6 +124,9 @@ export class TelemetryService implements ITelemetryService { // (first) add common properties data = mixin(data, values); + // (next) add experiment properties + data = mixin(data, this._experimentProperties); + // (last) remove all PII from data data = cloneAndChange(data, value => { if (typeof value === 'string') { diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index 40f9a7571d..695551a683 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -28,6 +28,7 @@ export const NullTelemetryService = new class implements ITelemetryService { return this.publicLogError(eventName, data as ITelemetryData); } + setExperimentProperty() { } setEnabled() { } isOptedIn = true; getTelemetryInfo(): Promise { diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 3a77f29a6e..b38d1a20a4 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -392,7 +392,7 @@ export const menuSeparatorBackground = registerColor('menu.separatorBackground', export const snippetTabstopHighlightBackground = registerColor('editor.snippetTabstopHighlightBackground', { dark: new Color(new RGBA(124, 124, 124, 0.3)), light: new Color(new RGBA(10, 50, 100, 0.2)), hc: new Color(new RGBA(124, 124, 124, 0.3)) }, nls.localize('snippetTabstopHighlightBackground', "Highlight background color of a snippet tabstop.")); export const snippetTabstopHighlightBorder = registerColor('editor.snippetTabstopHighlightBorder', { dark: null, light: null, hc: null }, nls.localize('snippetTabstopHighlightBorder', "Highlight border color of a snippet tabstop.")); export const snippetFinalTabstopHighlightBackground = registerColor('editor.snippetFinalTabstopHighlightBackground', { dark: null, light: null, hc: null }, nls.localize('snippetFinalTabstopHighlightBackground', "Highlight background color of the final tabstop of a snippet.")); -export const snippetFinalTabstopHighlightBorder = registerColor('editor.snippetFinalTabstopHighlightBorder', { dark: '#525252', light: new Color(new RGBA(10, 50, 100, 0.5)), hc: '#525252' }, nls.localize('snippetFinalTabstopHighlightBorder', "Highlight border color of the final stabstop of a snippet.")); +export const snippetFinalTabstopHighlightBorder = registerColor('editor.snippetFinalTabstopHighlightBorder', { dark: '#525252', light: new Color(new RGBA(10, 50, 100, 0.5)), hc: '#525252' }, nls.localize('snippetFinalTabstopHighlightBorder', "Highlight border color of the final tabstop of a snippet.")); /** * Breadcrumb colors diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index df55287576..19163950cd 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -18,6 +18,7 @@ import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { fromNow } from 'vs/base/common/date'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { Platform, platform } from 'vs/base/common/platform'; const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser']; @@ -390,7 +391,11 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu } const remoteConnection = this.remoteAgentService.getConnection(); - if (remoteConnection && remoteConnection.remoteAuthority && remoteConnection.remoteAuthority.startsWith('vsonline') && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) { + const isVSO = remoteConnection !== null + ? remoteConnection.remoteAuthority.startsWith('vsonline') + : platform === Platform.Web; + + if (isVSO && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) { addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName); return true; } diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 8300ea6bb6..8eae05cf3e 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -634,7 +634,7 @@ export class MainThreadTask implements MainThreadTaskShape { return URI.parse(`${info.scheme}://${info.authority}${path}`); }, context: this._extHostContext, - resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise => { + resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise => { const vars: string[] = []; toResolve.variables.forEach(item => vars.push(item)); return Promise.resolve(this._proxy.$resolveVariables(workspaceFolder.uri, { process: toResolve.process, variables: vars })).then(values => { @@ -642,8 +642,12 @@ export class MainThreadTask implements MainThreadTaskShape { forEach(values.variables, (entry) => { partiallyResolvedVars.push(entry.value); }); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { this._configurationResolverService.resolveWithInteraction(workspaceFolder, partiallyResolvedVars, 'tasks', undefined, target).then(resolvedVars => { + if (!resolvedVars) { + resolve(undefined); + } + const result: ResolvedVariables = { process: undefined, variables: new Map() @@ -677,4 +681,9 @@ export class MainThreadTask implements MainThreadTaskShape { } }); } + + async $registerSupportedExecutions(custom?: boolean, shell?: boolean, process?: boolean): Promise { + return this._taskService.registerSupportedExecutions(custom, shell, process); + } + } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 76175cb3ef..7f4505aa1b 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -791,6 +791,7 @@ export interface MainThreadTaskShape extends IDisposable { $terminateTask(id: string): Promise; $registerTaskSystem(scheme: string, info: tasks.TaskSystemInfoDTO): void; $customExecutionComplete(id: string, result?: number): Promise; + $registerSupportedExecutions(custom?: boolean, shell?: boolean, process?: boolean): Promise; } export interface MainThreadExtensionServiceShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 8d82d15a6e..b9df47c06f 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -419,6 +419,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask this._activeCustomExecutions2 = new Map(); this._logService = logService; this._deprecationService = deprecationService; + this._proxy.$registerSupportedExecutions(true); } public registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable { diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 9862b63aee..edb40ebaf5 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -10,14 +10,203 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { forEach } from 'vs/base/common/collections'; import { IExtensionPointUser, ExtensionMessageCollector, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { MenuId, MenuRegistry, ILocalizedString, IMenuItem, ICommandAction } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry, ILocalizedString, IMenuItem, ICommandAction, ISubmenuItem } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { Iterable } from 'vs/base/common/iterator'; +import { index } from 'vs/base/common/arrays'; + +interface IAPIMenu { + readonly key: string; + readonly id: MenuId; + readonly description: string; + readonly proposed?: boolean; // defaults to false + readonly supportsSubmenus?: boolean; // defaults to true +} + +const apiMenus: IAPIMenu[] = [ + { + key: 'commandPalette', + id: MenuId.CommandPalette, + description: localize('menus.commandPalette', "The Command Palette"), + supportsSubmenus: false + }, + { + key: 'touchBar', + id: MenuId.TouchBarContext, + description: localize('menus.touchBar', "The touch bar (macOS only)"), + supportsSubmenus: false + }, + { + key: 'editor/title', + id: MenuId.EditorTitle, + description: localize('menus.editorTitle', "The editor title menu") + }, + { + key: 'editor/context', + id: MenuId.EditorContext, + description: localize('menus.editorContext', "The editor context menu") + }, + { + key: 'explorer/context', + id: MenuId.ExplorerContext, + description: localize('menus.explorerContext', "The file explorer context menu") + }, + { + key: 'editor/title/context', + id: MenuId.EditorTitleContext, + description: localize('menus.editorTabContext', "The editor tabs context menu") + }, + { + key: 'debug/callstack/context', + id: MenuId.DebugCallStackContext, + description: localize('menus.debugCallstackContext', "The debug callstack context menu") + }, + { + key: 'debug/toolBar', + id: MenuId.DebugToolBar, + description: localize('menus.debugToolBar', "The debug toolbar menu") + }, + { + key: 'menuBar/webNavigation', + id: MenuId.MenubarWebNavigationMenu, + description: localize('menus.webNavigation', "The top level navigational menu (web only)"), + proposed: true, + supportsSubmenus: false + }, + { + key: 'scm/title', + id: MenuId.SCMTitle, + description: localize('menus.scmTitle', "The Source Control title menu") + }, + { + key: 'scm/sourceControl', + id: MenuId.SCMSourceControl, + description: localize('menus.scmSourceControl', "The Source Control menu") + }, + { + key: 'scm/resourceState/context', + id: MenuId.SCMResourceContext, + description: localize('menus.resourceGroupContext', "The Source Control resource group context menu") + }, + { + key: 'scm/resourceFolder/context', + id: MenuId.SCMResourceFolderContext, + description: localize('menus.resourceStateContext', "The Source Control resource state context menu") + }, + { + key: 'scm/resourceGroup/context', + id: MenuId.SCMResourceGroupContext, + description: localize('menus.resourceFolderContext', "The Source Control resource folder context menu") + }, + { + key: 'scm/change/title', + id: MenuId.SCMChangeContext, + description: localize('menus.changeTitle', "The Source Control inline change menu") + }, + { + key: 'statusBar/windowIndicator', + id: MenuId.StatusBarWindowIndicatorMenu, + description: localize('menus.statusBarWindowIndicator', "The window indicator menu in the status bar"), + proposed: true, + supportsSubmenus: false + }, + { + key: 'view/title', + id: MenuId.ViewTitle, + description: localize('view.viewTitle', "The contributed view title menu") + }, + { + key: 'view/item/context', + id: MenuId.ViewItemContext, + description: localize('view.itemContext', "The contributed view item context menu") + }, + { + key: 'comments/commentThread/title', + id: MenuId.CommentThreadTitle, + description: localize('commentThread.title', "The contributed comment thread title menu") + }, + { + key: 'comments/commentThread/context', + id: MenuId.CommentThreadActions, + description: localize('commentThread.actions', "The contributed comment thread context menu, rendered as buttons below the comment editor"), + supportsSubmenus: false + }, + { + key: 'comments/comment/title', + id: MenuId.CommentTitle, + description: localize('comment.title', "The contributed comment title menu") + }, + { + key: 'comments/comment/context', + id: MenuId.CommentActions, + description: localize('comment.actions', "The contributed comment context menu, rendered as buttons below the comment editor"), + supportsSubmenus: false + }, + { + key: 'notebook/cell/title', + id: MenuId.NotebookCellTitle, + description: localize('notebook.cell.title', "The contributed notebook cell title menu"), + proposed: true + }, + { + key: 'extension/context', + id: MenuId.ExtensionContext, + description: localize('menus.extensionContext', "The extension context menu") + }, + { + key: 'timeline/title', + id: MenuId.TimelineTitle, + description: localize('view.timelineTitle', "The Timeline view title menu") + }, + { + key: 'timeline/item/context', + id: MenuId.TimelineItemContext, + description: localize('view.timelineContext', "The Timeline view item context menu") + }, + // {{SQL CARBON EDIT}} start menu entries + { + key: 'dashboard/toolbar', + id: MenuId.DashboardToolbar, + description: localize('dashboard.toolbar', "The dashboard toolbar action menu") + }, + { + key: 'notebook/cell/title', + id: MenuId.NotebookCellTitle, + description: localize('notebook.cellTitle', "The notebook cell title menu") + }, + { + key: 'notebooks/title', + id: MenuId.NotebookTitle, + description: localize('notebook.title', "The notebook title menu") + }, + { + key: 'notebook/toolbar', + id: MenuId.NotebookToolbar, + description: localize('notebook.toolbar', "The notebook toolbar menu") + }, + { + key: 'dataExplorer/action', + id: MenuId.DataExplorerAction, + description: localize('dataExplorer.action', "The dataexplorer view container title action menu") + }, + { + key: 'dataExplorer/context', + id: MenuId.DataExplorerContext, + description: localize('dataExplorer.context', "The dataexplorer item context menu") + }, + { + key: 'objectExplorer/item/context', + id: MenuId.DataExplorerContext, + description: localize('objectExplorer.context', "The object explorer item context menu") + }, + // {{SQL CARBON EDIT}} end menu entries +]; namespace schema { - // --- menus contribution point + // --- menus, submenus contribution point export interface IUserFriendlyMenuItem { command: string; @@ -26,87 +215,102 @@ namespace schema { group?: string; } - export function parseMenuId(value: string): MenuId | undefined { - switch (value) { - case 'commandPalette': return MenuId.CommandPalette; - case 'touchBar': return MenuId.TouchBarContext; - case 'editor/title': return MenuId.EditorTitle; - case 'editor/context': return MenuId.EditorContext; - case 'explorer/context': return MenuId.ExplorerContext; - case 'editor/title/context': return MenuId.EditorTitleContext; - case 'debug/callstack/context': return MenuId.DebugCallStackContext; - case 'debug/toolbar': return MenuId.DebugToolBar; - case 'debug/toolBar': return MenuId.DebugToolBar; - case 'menuBar/webNavigation': return MenuId.MenubarWebNavigationMenu; - case 'scm/title': return MenuId.SCMTitle; - case 'scm/sourceControl': return MenuId.SCMSourceControl; - case 'scm/resourceState/context': return MenuId.SCMResourceContext; - case 'scm/resourceFolder/context': return MenuId.SCMResourceFolderContext; - case 'scm/resourceGroup/context': return MenuId.SCMResourceGroupContext; - case 'scm/change/title': return MenuId.SCMChangeContext;// - case 'statusBar/windowIndicator': return MenuId.StatusBarWindowIndicatorMenu; - case 'view/title': return MenuId.ViewTitle; - case 'view/item/context': return MenuId.ViewItemContext; - // {{SQL CARBON EDIT}} - case 'objectExplorer/item/context': return MenuId.ObjectExplorerItemContext; - case 'notebook/toolbar': return MenuId.NotebookToolbar; - case 'dataExplorer/context': return MenuId.DataExplorerContext; - case 'dataExplorer/action': return MenuId.DataExplorerAction; - case 'notebooks/title': return MenuId.NotebookTitle; - case 'comments/commentThread/title': return MenuId.CommentThreadTitle; - case 'comments/commentThread/context': return MenuId.CommentThreadActions; - case 'comments/comment/title': return MenuId.CommentTitle; - case 'comments/comment/context': return MenuId.CommentActions; - case 'notebook/cell/title': return MenuId.NotebookCellTitle; - case 'extension/context': return MenuId.ExtensionContext; - case 'dashboard/toolbar': return MenuId.DashboardToolbar; - case 'timeline/title': return MenuId.TimelineTitle; - case 'timeline/item/context': return MenuId.TimelineItemContext; - } - - return undefined; + export interface IUserFriendlySubmenuItem { + submenu: string; + when?: string; + group?: string; } - export function isProposedAPI(menuId: MenuId): boolean { - switch (menuId) { - case MenuId.StatusBarWindowIndicatorMenu: - case MenuId.MenubarWebNavigationMenu: - case MenuId.NotebookCellTitle: - return true; - } - return false; + export interface IUserFriendlySubmenu { + id: string; + label: string; + icon?: IUserFriendlyIcon; } - export function isValidMenuItems(menu: IUserFriendlyMenuItem[], collector: ExtensionMessageCollector): boolean { - if (!Array.isArray(menu)) { - collector.error(localize('requirearray', "menu items must be an array")); + export function isMenuItem(item: IUserFriendlyMenuItem | IUserFriendlySubmenuItem): item is IUserFriendlyMenuItem { + return typeof (item as IUserFriendlyMenuItem).command === 'string'; + } + + export function isValidMenuItem(item: IUserFriendlyMenuItem, collector: ExtensionMessageCollector): boolean { + if (typeof item.command !== 'string') { + collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command')); + return false; + } + if (item.alt && typeof item.alt !== 'string') { + collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'alt')); + return false; + } + if (item.when && typeof item.when !== 'string') { + collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when')); + return false; + } + if (item.group && typeof item.group !== 'string') { + collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'group')); return false; } - for (let item of menu) { - if (typeof item.command !== 'string') { - collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command')); - return false; - } - if (item.alt && typeof item.alt !== 'string') { - collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'alt')); - return false; - } - if (item.when && typeof item.when !== 'string') { - collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when')); - return false; - } - if (item.group && typeof item.group !== 'string') { - collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'group')); - return false; + return true; + } + + export function isValidSubmenuItem(item: IUserFriendlySubmenuItem, collector: ExtensionMessageCollector): boolean { + if (typeof item.submenu !== 'string') { + collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'submenu')); + return false; + } + if (item.when && typeof item.when !== 'string') { + collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when')); + return false; + } + if (item.group && typeof item.group !== 'string') { + collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'group')); + return false; + } + + return true; + } + + export function isValidItems(items: (IUserFriendlyMenuItem | IUserFriendlySubmenuItem)[], collector: ExtensionMessageCollector): boolean { + if (!Array.isArray(items)) { + collector.error(localize('requirearray', "submenu items must be an array")); + return false; + } + + for (let item of items) { + if (isMenuItem(item)) { + if (!isValidMenuItem(item, collector)) { + return false; + } + } else { + if (!isValidSubmenuItem(item, collector)) { + return false; + } } } return true; } + export function isValidSubmenu(submenu: IUserFriendlySubmenu, collector: ExtensionMessageCollector): boolean { + if (typeof submenu !== 'object') { + collector.error(localize('require', "submenu items must be an object")); + return false; + } + + if (typeof submenu.id !== 'string') { + collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'id')); + return false; + } + if (typeof submenu.label !== 'string') { + collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'label')); + return false; + } + + return true; + } + const menuItem: IJSONSchema = { type: 'object', + required: ['command'], properties: { command: { description: localize('vscode.extension.contributes.menuItem.command', 'Identifier of the command to execute. The command must be declared in the \'commands\'-section'), @@ -127,136 +331,73 @@ namespace schema { } }; + const submenuItem: IJSONSchema = { + type: 'object', + required: ['submenu'], + properties: { + submenu: { + description: localize('vscode.extension.contributes.menuItem.submenu', 'Identifier of the submenu to display in this item.'), + type: 'string' + }, + when: { + description: localize('vscode.extension.contributes.menuItem.when', 'Condition which must be true to show this item'), + type: 'string' + }, + group: { + description: localize('vscode.extension.contributes.menuItem.group', 'Group into which this command belongs'), + type: 'string' + } + } + }; + + const submenu: IJSONSchema = { + type: 'object', + required: ['id', 'label'], + properties: { + id: { + description: localize('vscode.extension.contributes.submenu.id', 'Identifier of the menu to display as a submenu.'), + type: 'string' + }, + label: { + description: localize('vscode.extension.contributes.submenu.label', 'The label of the menu item which leads to this submenu.'), + type: 'string' + }, + icon: { + description: localize('vscode.extension.contributes.submenu.icon', '(Optional) Icon which is used to represent the submenu in the UI. Either a file path, an object with file paths for dark and light themes, or a theme icon references, like `\\$(zap)`'), + anyOf: [{ + type: 'string' + }, + { + type: 'object', + properties: { + light: { + description: localize('vscode.extension.contributes.submenu.icon.light', 'Icon path when a light theme is used'), + type: 'string' + }, + dark: { + description: localize('vscode.extension.contributes.submenu.icon.dark', 'Icon path when a dark theme is used'), + type: 'string' + } + } + }] + } + } + }; + export const menusContribution: IJSONSchema = { description: localize('vscode.extension.contributes.menus', "Contributes menu items to the editor"), type: 'object', - properties: { - 'commandPalette': { - description: localize('menus.commandPalette', "The Command Palette"), - type: 'array', - items: menuItem - }, - 'touchBar': { - description: localize('menus.touchBar', "The touch bar (macOS only)"), - type: 'array', - items: menuItem - }, - 'editor/title': { - description: localize('menus.editorTitle', "The editor title menu"), - type: 'array', - items: menuItem - }, - 'editor/context': { - description: localize('menus.editorContext', "The editor context menu"), - type: 'array', - items: menuItem - }, - 'explorer/context': { - description: localize('menus.explorerContext', "The file explorer context menu"), - type: 'array', - items: menuItem - }, - 'editor/title/context': { - description: localize('menus.editorTabContext', "The editor tabs context menu"), - type: 'array', - items: menuItem - }, - 'debug/callstack/context': { - description: localize('menus.debugCallstackContext', "The debug callstack context menu"), - type: 'array', - items: menuItem - }, - 'debug/toolBar': { - description: localize('menus.debugToolBar', "The debug toolbar menu"), - type: 'array', - items: menuItem - }, - 'menuBar/webNavigation': { - description: localize('menus.webNavigation', "The top level navigational menu (web only)"), - type: 'array', - items: menuItem - }, - 'scm/title': { - description: localize('menus.scmTitle', "The Source Control title menu"), - type: 'array', - items: menuItem - }, - 'scm/sourceControl': { - description: localize('menus.scmSourceControl', "The Source Control menu"), - type: 'array', - items: menuItem - }, - 'scm/resourceGroup/context': { - description: localize('menus.resourceGroupContext', "The Source Control resource group context menu"), - type: 'array', - items: menuItem - }, - 'scm/resourceState/context': { - description: localize('menus.resourceStateContext', "The Source Control resource state context menu"), - type: 'array', - items: menuItem - }, - 'scm/resourceFolder/context': { - description: localize('menus.resourceFolderContext', "The Source Control resource folder context menu"), - type: 'array', - items: menuItem - }, - 'scm/change/title': { - description: localize('menus.changeTitle', "The Source Control inline change menu"), - type: 'array', - items: menuItem - }, - 'view/title': { - description: localize('view.viewTitle', "The contributed view title menu"), - type: 'array', - items: menuItem - }, - 'view/item/context': { - description: localize('view.itemContext', "The contributed view item context menu"), - type: 'array', - items: menuItem - }, - 'comments/commentThread/title': { - description: localize('commentThread.title', "The contributed comment thread title menu"), - type: 'array', - items: menuItem - }, - 'comments/commentThread/context': { - description: localize('commentThread.actions', "The contributed comment thread context menu, rendered as buttons below the comment editor"), - type: 'array', - items: menuItem - }, - 'comments/comment/title': { - description: localize('comment.title', "The contributed comment title menu"), - type: 'array', - items: menuItem - }, - 'comments/comment/context': { - description: localize('comment.actions', "The contributed comment context menu, rendered as buttons below the comment editor"), - type: 'array', - items: menuItem - }, - 'notebook/cell/title': { - description: localize('notebook.cell.title', "The contributed notebook cell title menu"), - type: 'array', - items: menuItem - }, - 'extension/context': { - description: localize('menus.extensionContext', "The extension context menu"), - type: 'array', - items: menuItem - }, - 'timeline/title': { - description: localize('view.timelineTitle', "The Timeline view title menu"), - type: 'array', - items: menuItem - }, - 'timeline/item/context': { - description: localize('view.timelineContext', "The Timeline view item context menu"), - type: 'array', - items: menuItem - }, - } + properties: index(apiMenus, menu => menu.key, menu => ({ + description: menu.proposed ? `(${localize('proposed', "Proposed API")}) ${menu.description}` : menu.description, + type: 'array', + items: menu.supportsSubmenus === false ? menuItem : { oneOf: [menuItem, submenuItem] } + })) + }; + + export const submenusContribution: IJSONSchema = { + description: localize('vscode.extension.contributes.submenus', "(Proposed API) Contributes submenu items to the editor"), + type: 'array', + items: submenu }; // --- commands contribution point @@ -437,74 +578,175 @@ commandsExtensionPoint.setHandler(extensions => { _commandRegistrations.add(MenuRegistry.addCommands(newCommands)); }); -const _menuRegistrations = new DisposableStore(); +interface IRegisteredSubmenu { + readonly id: MenuId; + readonly label: string; + readonly icon?: { dark: URI; light?: URI; } | ThemeIcon; +} -ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyMenuItem[] }>({ - extensionPoint: 'menus', - jsonSchema: schema.menusContribution -}).setHandler(extensions => { +const _submenus = new Map(); - // remove all previous menu registrations - _menuRegistrations.clear(); +const submenusExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'submenus', + jsonSchema: schema.submenusContribution +}); - const items: { id: MenuId, item: IMenuItem }[] = []; +submenusExtensionPoint.setHandler(extensions => { + + _submenus.clear(); for (let extension of extensions) { const { value, collector } = extension; forEach(value, entry => { - if (!schema.isValidMenuItems(entry.value, collector)) { + if (!schema.isValidSubmenu(entry.value, collector)) { return; } - const menu = schema.parseMenuId(entry.key); - if (typeof menu === 'undefined') { + if (!entry.value.id) { + collector.warn(localize('submenuId.invalid.id', "`{0}` is not a valid submenu identifier", entry.value.id)); + return; + } + if (!entry.value.label) { + collector.warn(localize('submenuId.invalid.label', "`{0}` is not a valid submenu label", entry.value.label)); + return; + } + + if (!extension.description.enableProposedApi) { + collector.error(localize('submenu.proposedAPI.invalid', "Submenus are proposed API and are only available when running out of dev or with the following command line switch: --enable-proposed-api {1}", extension.description.identifier.value)); + return; + } + + let absoluteIcon: { dark: URI; light?: URI; } | ThemeIcon | undefined; + if (entry.value.icon) { + if (typeof entry.value.icon === 'string') { + absoluteIcon = ThemeIcon.fromString(entry.value.icon) || { dark: resources.joinPath(extension.description.extensionLocation, entry.value.icon) }; + } else { + absoluteIcon = { + dark: resources.joinPath(extension.description.extensionLocation, entry.value.icon.dark), + light: resources.joinPath(extension.description.extensionLocation, entry.value.icon.light) + }; + } + } + + const item: IRegisteredSubmenu = { + id: new MenuId(`api:${entry.value.id}`), + label: entry.value.label, + icon: absoluteIcon + }; + + _submenus.set(entry.value.id, item); + }); + } +}); + +const _apiMenusByKey = new Map(Iterable.map(Iterable.from(apiMenus), menu => ([menu.key, menu]))); +const _menuRegistrations = new DisposableStore(); + +const menusExtensionPoint = ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: (schema.IUserFriendlyMenuItem | schema.IUserFriendlySubmenuItem)[] }>({ + extensionPoint: 'menus', + jsonSchema: schema.menusContribution, + deps: [submenusExtensionPoint] +}); + +menusExtensionPoint.setHandler(extensions => { + + // remove all previous menu registrations + _menuRegistrations.clear(); + + const items: { id: MenuId, item: IMenuItem | ISubmenuItem }[] = []; + + for (let extension of extensions) { + const { value, collector } = extension; + + forEach(value, entry => { + if (!schema.isValidItems(entry.value, collector)) { + return; + } + + let menu = _apiMenusByKey.get(entry.key); + let isSubmenu = false; + + if (!menu) { + const submenu = _submenus.get(entry.key); + + if (submenu) { + menu = { + key: entry.key, + id: submenu.id, + description: '' + }; + isSubmenu = true; + } + } + + if (!menu) { collector.warn(localize('menuId.invalid', "`{0}` is not a valid menu identifier", entry.key)); return; } - if (schema.isProposedAPI(menu) && !extension.description.enableProposedApi) { + if (menu.proposed && !extension.description.enableProposedApi) { collector.error(localize('proposedAPI.invalid', "{0} is a proposed menu identifier and is only available when running out of dev or with the following command line switch: --enable-proposed-api {1}", entry.key, extension.description.identifier.value)); return; } - for (let item of entry.value) { - let command = MenuRegistry.getCommand(item.command); - let alt = item.alt && MenuRegistry.getCommand(item.alt) || undefined; + if (isSubmenu && !extension.description.enableProposedApi) { + collector.error(localize('proposedAPI.invalid.submenu', "{0} is a submenu identifier and is only available when running out of dev or with the following command line switch: --enable-proposed-api {1}", entry.key, extension.description.identifier.value)); + return; + } - if (!command) { - collector.error(localize('missing.command', "Menu item references a command `{0}` which is not defined in the 'commands' section.", item.command)); - continue; - } - if (item.alt && !alt) { - collector.warn(localize('missing.altCommand', "Menu item references an alt-command `{0}` which is not defined in the 'commands' section.", item.alt)); - } - if (item.command === item.alt) { - collector.info(localize('dupe.command', "Menu item references the same command as default and alt-command")); + for (const menuItem of entry.value) { + let item: IMenuItem | ISubmenuItem; + + if (schema.isMenuItem(menuItem)) { + const command = MenuRegistry.getCommand(menuItem.command); + const alt = menuItem.alt && MenuRegistry.getCommand(menuItem.alt) || undefined; + + if (!command) { + collector.error(localize('missing.command', "Menu item references a command `{0}` which is not defined in the 'commands' section.", menuItem.command)); + continue; + } + if (menuItem.alt && !alt) { + collector.warn(localize('missing.altCommand', "Menu item references an alt-command `{0}` which is not defined in the 'commands' section.", menuItem.alt)); + } + if (menuItem.command === menuItem.alt) { + collector.info(localize('dupe.command', "Menu item references the same command as default and alt-command")); + } + + item = { command, alt, group: undefined, order: undefined, when: undefined }; + } else { + if (!extension.description.enableProposedApi) { + collector.error(localize('proposedAPI.invalid.submenureference', "Menu item references a submenu which is only available when running out of dev or with the following command line switch: --enable-proposed-api {0}", extension.description.identifier.value)); + continue; + } + + if (menu.supportsSubmenus === false) { + collector.error(localize('proposedAPI.unsupported.submenureference', "Menu item references a submenu for a menu which doesn't have submenu support.")); + continue; + } + + const submenu = _submenus.get(menuItem.submenu); + + if (!submenu) { + collector.error(localize('missing.submenu', "Menu item references a submenu `{0}` which is not defined in the 'submenus' section.", menuItem.submenu)); + continue; + } + + item = { submenu: submenu.id, icon: submenu.icon, title: submenu.label, group: undefined, order: undefined, when: undefined }; } - let group: string | undefined; - let order: number | undefined; - if (item.group) { - const idx = item.group.lastIndexOf('@'); + if (menuItem.group) { + const idx = menuItem.group.lastIndexOf('@'); if (idx > 0) { - group = item.group.substr(0, idx); - order = Number(item.group.substr(idx + 1)) || undefined; + item.group = menuItem.group.substr(0, idx); + item.order = Number(menuItem.group.substr(idx + 1)) || undefined; } else { - group = item.group; + item.group = menuItem.group; } } - items.push({ - id: menu, - item: { - command, - alt, - group, - order, - when: ContextKeyExpr.deserialize(item.when) - } - }); + item.when = ContextKeyExpr.deserialize(menuItem.when); + items.push({ id: menu.id, item }); } }); } diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 6a6833a487..33b75f403c 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -47,6 +47,7 @@ export class ExtHostTask extends ExtHostTaskBase { platform: process.platform }); } + this._proxy.$registerSupportedExecutions(true, true, true); } public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise { diff --git a/src/vs/workbench/browser/actions/textInputActions.ts b/src/vs/workbench/browser/actions/textInputActions.ts index f4b3b4c572..5995fbcecc 100644 --- a/src/vs/workbench/browser/actions/textInputActions.ts +++ b/src/vs/workbench/browser/actions/textInputActions.ts @@ -3,9 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction, Action } from 'vs/base/common/actions'; +import { IAction, Action, Separator } from 'vs/base/common/actions'; import { localize } from 'vs/nls'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Disposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index b328bf51ff..c848bc0b2b 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -3,8 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions'; -import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction, IActionRunner, ActionRunner, IActionViewItem } from 'vs/base/common/actions'; import { Component } from 'vs/workbench/common/component'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IComposite, ICompositeControl } from 'vs/workbench/common/composite'; diff --git a/src/vs/workbench/browser/panecomposite.ts b/src/vs/workbench/browser/panecomposite.ts index 55b6108115..223eda38b4 100644 --- a/src/vs/workbench/browser/panecomposite.ts +++ b/src/vs/workbench/browser/panecomposite.ts @@ -15,10 +15,9 @@ import { Composite } from 'vs/workbench/browser/composite'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ViewPaneContainer } from './parts/views/viewPaneContainer'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; -import { IAction, IActionViewItem } from 'vs/base/common/actions'; +import { IAction, IActionViewItem, Separator } from 'vs/base/common/actions'; import { ViewContainerMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; import { MenuId } from 'vs/platform/actions/common/actions'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; export class PaneComposite extends Composite implements IPaneComposite { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index e89b6aea4b..2731036cf8 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; -import { Action, IAction } from 'vs/base/common/actions'; +import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions'; import { KeyCode } from 'vs/base/common/keyCodes'; import { dispose } from 'vs/base/common/lifecycle'; import { SyncActionDescriptor, IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; @@ -27,12 +27,11 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { Codicon } from 'vs/base/common/codicons'; -import { ActionViewItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { isMacintosh } from 'vs/base/common/platform'; -import { ContextSubMenu } from 'vs/base/browser/contextmenu'; import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { AuthenticationSession } from 'vs/editor/common/modes'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; export class ViewContainerActivityAction extends ActivityAction { @@ -163,7 +162,7 @@ export class AccountsActionViewItem extends ActivityActionViewItem { }); const result = await Promise.all(allSessions); - let menus: (IAction | ContextSubMenu)[] = []; + let menus: IAction[] = []; result.forEach(sessionInfo => { const providerDisplayName = this.authenticationService.getLabel(sessionInfo.providerId); Object.keys(sessionInfo.sessions).forEach(accountName => { @@ -177,7 +176,7 @@ export class AccountsActionViewItem extends ActivityActionViewItem { const actions = hasEmbedderAccountSession ? [manageExtensionsAction] : [manageExtensionsAction, signOutAction]; - const menu = new ContextSubMenu(`${accountName} (${providerDisplayName})`, actions); + const menu = new SubmenuAction('activitybar.submenu', `${accountName} (${providerDisplayName})`, actions); menus.push(menu); }); }); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index b5ed80a630..f5e38f745e 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/activitybarpart'; import * as nls from 'vs/nls'; -import { ActionsOrientation, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIIVTY_ID } from 'vs/workbench/common/activity'; import { Part } from 'vs/workbench/browser/part'; import { GlobalActivityActionViewItem, ViewContainerActivityAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewContainerActivityAction, HomeAction, HomeActionViewItem, ACCOUNTS_VISIBILITY_PREFERENCE_KEY } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; @@ -37,7 +37,7 @@ import { isWeb } from 'vs/base/common/platform'; import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { Before2D } from 'vs/workbench/browser/dnd'; import { Codicon, iconRegistry } from 'vs/base/common/codicons'; -import { Action } from 'vs/base/common/actions'; +import { Action, Separator } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 3e1a34d6f2..051b8d09e3 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { Action, IAction } from 'vs/base/common/actions'; +import { Action, IAction, Separator } from 'vs/base/common/actions'; import { illegalArgument } from 'vs/base/common/errors'; import * as arrays from 'vs/base/common/arrays'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ActionBar, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions'; import { Dimension, $, addDisposableListener, EventType, EventHelper, toggleClass, isAncestor } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -629,7 +629,7 @@ export class CompositeBar extends Widget implements ICompositeBar { }); } - private getContextMenuActions(): ReadonlyArray { + private getContextMenuActions(): IAction[] { const actions: IAction[] = this.model.visibleItems .map(({ id, name, activityAction }) => ({ id, diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 9c07b50ac4..4751eccd9b 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; +import { Action, Separator } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; -import { BaseActionViewItem, IBaseActionViewItemOptions, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -21,6 +20,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { CompositeDragAndDropObserver, ICompositeDragAndDrop, Before2D, toggleDropEffect } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; import { Codicon } from 'vs/base/common/codicons'; +import { IBaseActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export interface ICompositeActivity { badge: IBadge; diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 2b73322765..bdefcbd5c0 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -11,9 +11,9 @@ import * as strings from 'vs/base/common/strings'; import { Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { IActionViewItem, ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; +import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, IActionViewItem } from 'vs/base/common/actions'; import { Part, IPartOptions } from 'vs/workbench/browser/part'; import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite'; import { IComposite } from 'vs/workbench/common/composite'; diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 3260127cab..45ba6c8dfa 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -7,16 +7,16 @@ import 'vs/css!./media/titlecontrol'; import { applyDragImage, DataTransfers } from 'vs/base/browser/dnd'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { IAction, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; +import { IAction, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, IActionViewItem } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { getCodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { localize } from 'vs/nls'; -import { createActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { ExecuteCommandAction, IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { createAndFillInActionBarActions, createAndFillInContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { ExecuteCommandAction, IMenu, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -164,17 +164,22 @@ export abstract class TitleControl extends Themable { const activeEditorPane = this.group.activeEditorPane; // Check Active Editor - let actionViewItem: IActionViewItem | undefined = undefined; if (activeEditorPane instanceof BaseEditor) { - actionViewItem = activeEditorPane.getActionViewItem(action); + const result = activeEditorPane.getActionViewItem(action); + + if (result) { + return result; + } } // Check extensions - if (!actionViewItem) { - actionViewItem = createActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); } - return actionViewItem; + return undefined; } protected updateEditorActionsToolbar(): void { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 5d1c14c453..4dd5f9a23c 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -16,7 +16,6 @@ import { IAction, IActionRunner } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { dispose, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { INotificationViewItem, NotificationViewItem, NotificationViewItemContentChangeKind, INotificationMessage, ChoiceAction } from 'vs/workbench/common/notifications'; import { ClearNotificationAction, ExpandNotificationAction, CollapseNotificationAction, ConfigureNotificationAction } from 'vs/workbench/browser/parts/notifications/notificationsActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -24,6 +23,7 @@ import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { Severity } from 'vs/platform/notification/common/notification'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; +import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; export class NotificationsListDelegate implements IListVirtualDelegate { @@ -211,7 +211,7 @@ export class NotificationRenderer implements IListRenderer { if (action && action instanceof ConfigureNotificationAction) { - const item = new DropdownMenuActionViewItem(action, action.configurationActions, this.contextMenuService, undefined, this.actionRunner, undefined, action.class); + const item = new DropdownMenuActionViewItem(action, action.configurationActions, this.contextMenuService, { actionRunner: this.actionRunner, classNames: action.class }); data.toDispose.add(item); return item; diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index a510eac4f7..7b5d15affc 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -14,7 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { Action, IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; +import { Action, IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, Separator } from 'vs/base/common/actions'; import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, ThemeColor, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER } from 'vs/workbench/common/theme'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -29,7 +29,6 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { coalesce } from 'vs/base/common/arrays'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ToggleStatusbarVisibilityAction } from 'vs/workbench/browser/actions/layoutActions'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { assertIsDefined } from 'vs/base/common/types'; import { Emitter } from 'vs/base/common/event'; import { Command } from 'vs/editor/common/modes'; diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 349b980149..eba1fc99dc 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -8,8 +8,7 @@ import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action import { registerThemingParticipant, IColorTheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IAction, Action } from 'vs/base/common/actions'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction, Action, SubmenuAction, Separator } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { isMacintosh, isWeb, isIOS } from 'vs/base/common/platform'; @@ -27,7 +26,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { MenuBar, IMenuBarOptions } from 'vs/base/browser/ui/menu/menubar'; -import { SubmenuAction, Direction } from 'vs/base/browser/ui/menu/menu'; +import { Direction } from 'vs/base/browser/ui/menu/menu'; import { attachMenuStyler } from 'vs/platform/theme/common/styler'; import { mnemonicMenuLabel, unmnemonicLabel } from 'vs/base/common/labels'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -600,7 +599,7 @@ export class CustomMenubarControl extends MenubarControl { const submenuActions: SubmenuAction[] = []; updateActions(submenu, submenuActions, topLevelTitle); - target.push(new SubmenuAction(mnemonicMenuLabel(action.label), submenuActions)); + target.push(new SubmenuAction(action.id, mnemonicMenuLabel(action.label), submenuActions)); } else { action.label = mnemonicMenuLabel(this.calculateActionLabel(action)); target.push(action); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 3ce94cb88c..296fea3763 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -12,8 +12,8 @@ import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass, createCSSRule, asCSSUrl, addClasses } from 'vs/base/browser/dom'; import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { firstIndex } from 'vs/base/common/arrays'; -import { IAction } from 'vs/base/common/actions'; -import { IActionViewItem, ActionsOrientation, Separator, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction, Separator, IActionViewItem } from 'vs/base/common/actions'; +import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { Registry } from 'vs/platform/registry/common/platform'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -33,8 +33,8 @@ import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewl import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { Component } from 'vs/workbench/common/component'; -import { MenuId, MenuItemAction, registerAction2, Action2, IAction2Options } from 'vs/platform/actions/common/actions'; -import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { MenuId, MenuItemAction, registerAction2, Action2, IAction2Options, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; import { parseLinkedText } from 'vs/base/common/linkedText'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -482,7 +482,9 @@ export abstract class ViewPane extends Pane implements IView { getActionViewItem(action: IAction): IActionViewItem | undefined { if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action); + return this.instantiationService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); } return undefined; } diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index 7570fe87ef..ed9dad473b 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Action, IAction } from 'vs/base/common/actions'; +import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite'; @@ -26,8 +26,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { PaneComposite } from 'vs/workbench/browser/panecomposite'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { ContextSubMenu } from 'vs/base/browser/contextmenu'; import { Event } from 'vs/base/common/event'; export abstract class Viewlet extends PaneComposite implements IViewlet { @@ -79,7 +77,7 @@ export abstract class Viewlet extends PaneComposite implements IViewlet { } return [ - new ContextSubMenu(nls.localize('views', "Views"), viewVisibilityActions), + new SubmenuAction('workbench.views', nls.localize('views', "Views"), viewVisibilityActions), new Separator(), ...secondaryActions ]; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 4002c1f1f3..b9593ae2f5 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -32,9 +32,9 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { Color } from 'vs/base/common/color'; import { TreeMouseEventTarget, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { URI } from 'vs/base/common/uri'; -import { MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuId, IMenuService } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; const enum State { Loading = 'loading', @@ -94,7 +94,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { - super(editor, { showFrame: true, showArrow: true, isResizeable: true, isAccessible: true }); + super(editor, { showFrame: true, showArrow: true, isResizeable: true, isAccessible: true }, _instantiationService); this.create(); this._peekViewService.addExclusiveWidget(editor, this); this._applyTheme(themeService.getColorTheme()); @@ -142,8 +142,8 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { protected _getActionBarOptions(): IActionBarOptions { return { - orientation: ActionsOrientation.HORIZONTAL, - actionViewItemProvider: action => action instanceof MenuItemAction ? this._instantiationService.createInstance(MenuEntryActionViewItem, action) : undefined + ...super._getActionBarOptions(), + orientation: ActionsOrientation.HORIZONTAL }; } diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 94ceccb411..472a63dd47 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -6,8 +6,8 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import * as modes from 'vs/editor/common/modes'; -import { ActionsOrientation, ActionViewItem, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { Action, IActionRunner, IAction } from 'vs/base/common/actions'; +import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Action, IActionRunner, IAction, Separator } from 'vs/base/common/actions'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; @@ -23,17 +23,17 @@ import { Emitter, Event } from 'vs/base/common/event'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { ToggleReactionsAction, ReactionAction, ReactionActionViewItem } from './reactionsAction'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; import { MenuItemAction, SubmenuItemAction, IMenu } from 'vs/platform/actions/common/actions'; -import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; export class CommentNode extends Disposable { private _domNode: HTMLElement; @@ -79,7 +79,6 @@ export class CommentNode extends Disposable { @ICommentService private commentService: ICommentService, @IModelService private modelService: IModelService, @IModeService private modeService: IModeService, - @IKeybindingService private keybindingService: IKeybindingService, @INotificationService private notificationService: INotificationService, @IContextMenuService private contextMenuService: IContextMenuService, @IContextKeyService contextKeyService: IContextKeyService @@ -154,13 +153,12 @@ export class CommentNode extends Disposable { action, (action).menuActions, this.contextMenuService, - action => { - return this.actionViewItemProvider(action as Action); - }, - this.actionRunner!, - undefined, - 'toolbar-toggle-pickReactions codicon codicon-reactions', - () => { return AnchorAlignment.RIGHT; } + { + actionViewItemProvider: action => this.actionViewItemProvider(action as Action), + actionRunner: this.actionRunner, + classNames: ['toolbar-toggle-pickReactions', 'codicon', 'codicon-reactions'], + anchorAlignmentProvider: () => AnchorAlignment.RIGHT + } ); } return this.actionViewItemProvider(action as Action); @@ -221,8 +219,9 @@ export class CommentNode extends Disposable { let item = new ReactionActionViewItem(action); return item; } else if (action instanceof MenuItemAction) { - let item = new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); - return item; + return this.instantiationService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); } else { let item = new ActionViewItem({}, action, options); return item; @@ -259,16 +258,17 @@ export class CommentNode extends Disposable { toggleReactionAction, (toggleReactionAction).menuActions, this.contextMenuService, - action => { - if (action.id === ToggleReactionsAction.ID) { - return toggleReactionActionViewItem; - } - return this.actionViewItemProvider(action as Action); - }, - this.actionRunner!, - undefined, - 'toolbar-toggle-pickReactions', - () => { return AnchorAlignment.RIGHT; } + { + actionViewItemProvider: action => { + if (action.id === ToggleReactionsAction.ID) { + return toggleReactionActionViewItem; + } + return this.actionViewItemProvider(action as Action); + }, + actionRunner: this.actionRunner, + classNames: 'toolbar-toggle-pickReactions', + anchorAlignmentProvider: () => AnchorAlignment.RIGHT + } ); return toggleReactionAction; @@ -283,13 +283,12 @@ export class CommentNode extends Disposable { action, (action).menuActions, this.contextMenuService, - action => { - return this.actionViewItemProvider(action as Action); - }, - this.actionRunner!, - undefined, - 'toolbar-toggle-pickReactions', - () => { return AnchorAlignment.RIGHT; } + { + actionViewItemProvider: action => this.actionViewItemProvider(action as Action), + actionRunner: this.actionRunner, + classNames: 'toolbar-toggle-pickReactions', + anchorAlignmentProvider: () => AnchorAlignment.RIGHT + } ); } return this.actionViewItemProvider(action as Action); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index cca200fc61..72cd4dbabc 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action, IAction } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; import { Color } from 'vs/base/common/color'; @@ -26,13 +26,10 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; import { peekViewBorder } from 'vs/editor/contrib/peekView/peekView'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import * as nls from 'vs/nls'; -import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { contrastBorder, editorForeground, focusBorder, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, transparent } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; @@ -49,6 +46,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; const COLLAPSE_ACTION_CLASS = 'expand-review-action codicon-chevron-up'; @@ -109,15 +107,12 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _owner: string, private _commentThread: modes.CommentThread, private _pendingComment: string | null, - @IInstantiationService instantiationService: IInstantiationService, + @IInstantiationService private instantiationService: IInstantiationService, @IModeService private modeService: IModeService, @IModelService private modelService: IModelService, @IThemeService private themeService: IThemeService, @ICommentService private commentService: ICommentService, @IOpenerService private openerService: IOpenerService, - @IKeybindingService private keybindingService: IKeybindingService, - @INotificationService private notificationService: INotificationService, - @IContextMenuService private contextMenuService: IContextMenuService, @IContextKeyService contextKeyService: IContextKeyService ) { super(editor, { keepEditorSelection: true }); @@ -239,11 +234,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._actionbarWidget = new ActionBar(actionsContainer, { actionViewItemProvider: (action: IAction) => { if (action instanceof MenuItemAction) { - let item = new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); - return item; + return this.instantiationService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); } else { - let item = new ActionViewItem({}, action, { label: false, icon: true }); - return item; + return new ActionViewItem({}, action, { label: false, icon: true }); } } }); diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index d48a1cbcfc..33f3446c48 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ContextSubMenu } from 'vs/base/browser/contextmenu'; import { $ } from 'vs/base/browser/dom'; import { Action, IAction } from 'vs/base/common/actions'; import { coalesce, findFirstInSorted } from 'vs/base/common/arrays'; @@ -547,8 +546,8 @@ export class CommentController implements IEditorContribution { return picks; } - private getContextMenuActions(commentInfos: { ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges }[], lineNumber: number): (IAction | ContextSubMenu)[] { - const actions: (IAction | ContextSubMenu)[] = []; + private getContextMenuActions(commentInfos: { ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges }[], lineNumber: number): IAction[] { + const actions: IAction[] = []; commentInfos.forEach(commentInfo => { const { ownerId, extensionId, label } = commentInfo; diff --git a/src/vs/workbench/contrib/comments/browser/reactionsAction.ts b/src/vs/workbench/contrib/comments/browser/reactionsAction.ts index 94962c7bb2..72b4ae009d 100644 --- a/src/vs/workbench/contrib/comments/browser/reactionsAction.ts +++ b/src/vs/workbench/contrib/comments/browser/reactionsAction.ts @@ -5,9 +5,9 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action, IAction } from 'vs/base/common/actions'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class ToggleReactionsAction extends Action { static readonly ID = 'toolbar.toggle.pickReactions'; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index fb8d3bdef1..13eeec60fb 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -8,7 +8,7 @@ import * as env from 'vs/base/common/platform'; import * as dom from 'vs/base/browser/dom'; import { URI } from 'vs/base/common/uri'; import severity from 'vs/base/common/severity'; -import { IAction, Action } from 'vs/base/common/actions'; +import { IAction, Action, SubmenuAction } from 'vs/base/common/actions'; import { Range } from 'vs/editor/common/core/range'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType, IContentWidget, IActiveCodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness, ITextModel, OverviewRulerLane, IModelDecorationOverviewRulerOptions } from 'vs/editor/common/model'; @@ -18,7 +18,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { RemoveBreakpointAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugConfiguration, State, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; -import { ContextSubMenu } from 'vs/base/browser/contextmenu'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { BreakpointWidget } from 'vs/workbench/contrib/debug/browser/breakpointWidget'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -288,8 +287,8 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi })); } - private getContextMenuActions(breakpoints: ReadonlyArray, uri: URI, lineNumber: number, column?: number): Array { - const actions: Array = []; + private getContextMenuActions(breakpoints: ReadonlyArray, uri: URI, lineNumber: number, column?: number): IAction[] { + const actions: IAction[] = []; if (breakpoints.length === 1) { const breakpointType = breakpoints[0].logMessage ? nls.localize('logPoint', "Logpoint") : nls.localize('breakpoint', "Breakpoint"); actions.push(new RemoveBreakpointAction(RemoveBreakpointAction.ID, nls.localize('removeBreakpoint', "Remove {0}", breakpointType), this.debugService)); @@ -310,7 +309,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi )); } else if (breakpoints.length > 1) { const sorted = breakpoints.slice().sort((first, second) => (first.column && second.column) ? first.column - second.column : 1); - actions.push(new ContextSubMenu(nls.localize('removeBreakpoints', "Remove Breakpoints"), sorted.map(bp => new Action( + actions.push(new SubmenuAction('debug.removeBreakpoints', nls.localize('removeBreakpoints', "Remove Breakpoints"), sorted.map(bp => new Action( 'removeInlineBreakpoint', bp.column ? nls.localize('removeInlineBreakpointOnColumn', "Remove Inline Breakpoint on Column {0}", bp.column) : nls.localize('removeLineBreakpoint', "Remove Line Breakpoint"), undefined, @@ -318,7 +317,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi () => this.debugService.removeBreakpoints(bp.getId()) )))); - actions.push(new ContextSubMenu(nls.localize('editBreakpoints', "Edit Breakpoints"), sorted.map(bp => + actions.push(new SubmenuAction('debug.editBReakpoints', nls.localize('editBreakpoints', "Edit Breakpoints"), sorted.map(bp => new Action('editBreakpoint', bp.column ? nls.localize('editInlineBreakpointOnColumn', "Edit Inline Breakpoint on Column {0}", bp.column) : nls.localize('editLineBrekapoint', "Edit Line Breakpoint"), undefined, @@ -327,7 +326,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi ) ))); - actions.push(new ContextSubMenu(nls.localize('enableDisableBreakpoints', "Enable/Disable Breakpoints"), sorted.map(bp => new Action( + actions.push(new SubmenuAction('debug.enableDisableBreakpoints', nls.localize('enableDisableBreakpoints', "Enable/Disable Breakpoints"), sorted.map(bp => new Action( bp.enabled ? 'disableColumnBreakpoint' : 'enableColumnBreakpoint', bp.enabled ? (bp.column ? nls.localize('disableInlineColumnBreakpoint', "Disable Inline Breakpoint on Column {0}", bp.column) : nls.localize('disableBreakpointOnLine', "Disable Line Breakpoint")) : (bp.column ? nls.localize('enableBreakpoints', "Enable Inline Breakpoint on Column {0}", bp.column) : nls.localize('enableBreakpointOnLine', "Enable Line Breakpoint")), @@ -548,7 +547,7 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { private readonly breakpoint: IBreakpoint | undefined, private readonly debugService: IDebugService, private readonly contextMenuService: IContextMenuService, - private readonly getContextMenuActions: () => ReadonlyArray + private readonly getContextMenuActions: () => IAction[] ) { this.range = this.editor.getModel().getDecorationRange(decorationId); this.toDispose.push(this.editor.onDidChangeModelDecorations(() => { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 950279debd..dc58ee7284 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as resources from 'vs/base/common/resources'; import * as dom from 'vs/base/browser/dom'; -import { IAction, Action } from 'vs/base/common/actions'; +import { IAction, Action, Separator } from 'vs/base/common/actions'; import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IDebugModel, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAllBreakpointsAction, RemoveBreakpointAction, EnableAllBreakpointsAction, DisableAllBreakpointsAction, ReapplyBreakpointsAction } from 'vs/workbench/contrib/debug/browser/debugActions'; @@ -16,7 +16,6 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Constants } from 'vs/base/common/uint'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IListVirtualDelegate, IListContextMenuEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; import { IEditorPane } from 'vs/workbench/common/editor'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 165e1a8a12..b5051a6f29 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -11,7 +11,7 @@ import { IDebugService, State, IStackFrame, IDebugSession, IThread, CONTEXT_CALL import { Thread, StackFrame, ThreadAndSessionIds } from 'vs/workbench/contrib/debug/common/debugModel'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MenuId, IMenu, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuId, IMenu, IMenuService, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IAction, Action } from 'vs/base/common/actions'; @@ -21,7 +21,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ILabelService } from 'vs/platform/label/common/label'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { createAndFillInContextMenuActions, createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { createAndFillInContextMenuActions, createAndFillInActionBarActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -489,10 +489,7 @@ class SessionsRenderer implements ICompressibleTreeRenderer { if (action instanceof MenuItemAction) { - // We need the MenuEntryActionViewItem so the icon would get rendered - return new MenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + return this.instantiationService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); } return undefined; diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 77bb0d12d4..4c2c979380 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IAction, IActionRunner } from 'vs/base/common/actions'; +import { IAction, IActionRunner, IActionViewItem } from 'vs/base/common/actions'; import { KeyCode } from 'vs/base/common/keyCodes'; import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { SelectBox, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; -import { SelectActionViewItem, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IDebugService, IDebugSession, IDebugConfiguration, IConfig, ILaunch } from 'vs/workbench/contrib/debug/common/debug'; @@ -20,6 +19,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ADD_CONFIGURATION_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; +import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; const $ = dom.$; diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 917a3dda12..dcf12b968a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -9,8 +9,8 @@ import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import * as arrays from 'vs/base/common/arrays'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IAction, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; -import { ActionBar, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, Separator } from 'vs/base/common/actions'; +import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDebugConfiguration, IDebugService, State } from 'vs/workbench/contrib/debug/common/debug'; @@ -21,13 +21,12 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { registerThemingParticipant, IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { registerColor, contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenu, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { createAndFillInActionBarActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -57,7 +56,6 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { @IStorageService private readonly storageService: IStorageService, @IConfigurationService private readonly configurationService: IConfigurationService, @IThemeService themeService: IThemeService, - @IKeybindingService private readonly keybindingService: IKeybindingService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IMenuService menuService: IMenuService, @IContextMenuService contextMenuService: IContextMenuService, @@ -80,9 +78,10 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { actionViewItemProvider: (action: IAction) => { if (action.id === FocusSessionAction.ID) { return this.instantiationService.createInstance(FocusSessionActionViewItem, action); - } - if (action instanceof MenuItemAction) { - return new MenuEntryActionViewItem(action, this.keybindingService, this.notificationService, contextMenuService); + } else if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); } return undefined; diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 810f0909a5..ab340eed8d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -5,9 +5,8 @@ import 'vs/css!./media/debugViewlet'; import * as nls from 'vs/nls'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, IActionViewItem } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; -import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { StartAction, ConfigureAction, SelectAndStartAction, FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; @@ -24,12 +23,10 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { memoize } from 'vs/base/common/decorators'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ViewPane, ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IMenu, MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenu, MenuId, IMenuService, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView'; import { ToggleViewAction } from 'vs/workbench/browser/actions/layoutActions'; @@ -55,11 +52,9 @@ export class DebugViewPaneContainer extends ViewPaneContainer { @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService, - @IKeybindingService private readonly keybindingService: IKeybindingService, @IContextViewService private readonly contextViewService: IContextViewService, @IMenuService private readonly menuService: IMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @INotificationService private readonly notificationService: INotificationService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); @@ -165,7 +160,9 @@ export class DebugViewPaneContainer extends ViewPaneContainer { return new FocusSessionActionViewItem(action, this.debugService, this.themeService, this.contextViewService, this.configurationService); } if (action instanceof MenuItemAction) { - return new MenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + return this.instantiationService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); } return undefined; diff --git a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css index 660d52a085..3a04db7165 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css @@ -38,7 +38,7 @@ cursor: grabbing; } -.monaco-workbench .debug-toolbar .monaco-action-bar .action-item > .action-label { +.monaco-workbench .debug-toolbar .monaco-action-bar .action-item .action-label { width: 32px; height: 32px; margin-right: 0; diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 33a6e68472..3b8b38c538 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/repl'; import { URI as uri } from 'vs/base/common/uri'; -import { IAction, IActionViewItem, Action } from 'vs/base/common/actions'; +import { IAction, IActionViewItem, Action, Separator } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -41,7 +41,6 @@ import { first } from 'vs/base/common/arrays'; import { ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { removeAnsiEscapeCodes } from 'vs/base/common/strings'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -440,7 +439,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { return this.instantiationService.createInstance(SelectReplActionViewItem, this.selectReplAction); } - return undefined; + return super.getActionViewItem(action); } getActions(): IAction[] { diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 920fbcaca1..e23686febe 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -13,9 +13,8 @@ import { Variable, Scope, ErrorScope, StackFrame } from 'vs/workbench/contrib/de import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; -import { IAction, Action } from 'vs/base/common/actions'; +import { IAction, Action, Separator } from 'vs/base/common/actions'; import { CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 7be6dc7e55..5b8cf3d14a 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -14,8 +14,7 @@ import { AddWatchExpressionAction, RemoveAllWatchExpressionsAction, CopyValueAct import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IAction, Action } from 'vs/base/common/actions'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction, Action, Separator } from 'vs/base/common/actions'; import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index d0f002a21e..1c2648dd90 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -5,12 +5,11 @@ import 'vs/css!./media/extensionActions'; import { localize } from 'vs/nls'; -import { IAction, Action } from 'vs/base/common/actions'; +import { IAction, Action, Separator, SubmenuAction } from 'vs/base/common/actions'; import { Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; -import { ActionViewItem, Separator, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { dispose, Disposable } from 'vs/base/common/lifecycle'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, TOGGLE_IGNORE_EXTENSION_ACTION_ID } from 'vs/workbench/contrib/extensions/common/extensions'; @@ -61,6 +60,7 @@ import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/d import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { Codicon } from 'vs/base/common/codicons'; import { IViewsService } from 'vs/workbench/common/views'; +import { IActionViewItemOptions, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; // {{SQL CARBON EDIT}} import product from 'vs/platform/product/common/product'; @@ -729,7 +729,7 @@ export class DropDownMenuActionViewItem extends ExtensionActionViewItem { } } -export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService, instantiationService: IInstantiationService, extension: IExtension | undefined | null): ExtensionAction[][] { +export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService, instantiationService: IInstantiationService, extension: IExtension | undefined | null): IAction[][] { const scopedContextKeyService = contextKeyService.createScoped(); if (extension) { scopedContextKeyService.createKey('extension', extension.identifier.id); @@ -740,9 +740,14 @@ export function getContextMenuActions(menuService: IMenuService, contextKeyServi } } - const groups: ExtensionAction[][] = []; + const groups: IAction[][] = []; const menu = menuService.createMenu(MenuId.ExtensionContext, scopedContextKeyService); - menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => instantiationService.createInstance(MenuItemExtensionAction, action)))); + menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => { + if (action instanceof SubmenuAction) { + return action; + } + return instantiationService.createInstance(MenuItemExtensionAction, action); + }))); menu.dispose(); return groups; @@ -771,7 +776,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { } async getActionGroups(runningExtensions: IExtensionDescription[]): Promise { - const groups: ExtensionAction[][] = []; + const groups: IAction[][] = []; if (this.extension) { const actions = await Promise.all([ SetColorThemeAction.create(this.workbenchThemeService, this.instantiationService, this.extension), @@ -802,7 +807,11 @@ export class ManageExtensionAction extends ExtensionDropDownAction { getContextMenuActions(this.menuService, this.contextKeyService, this.instantiationService, this.extension).forEach(actions => groups.push(actions)); - groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = this.extension)); + groups.forEach(group => group.forEach(extensionAction => { + if (extensionAction instanceof ExtensionAction) { + extensionAction.extension = this.extension; + } + })); return groups; } @@ -1769,7 +1778,28 @@ export class ShowPopularExtensionsAction extends Action { return this.viewletService.openViewlet(VIEWLET_ID, true) .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) .then(viewlet => { - viewlet.search('@sort:installs '); + viewlet.search('@popular '); + viewlet.focus(); + }); + } +} + +export class PredefinedExtensionFilterAction extends Action { + + constructor( + id: string, + label: string, + private readonly filter: string, + @IViewletService private readonly viewletService: IViewletService + ) { + super(id, label, undefined, true); + } + + run(): Promise { + return this.viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) + .then(viewlet => { + viewlet.search(`${this.filter} `); viewlet.focus(); }); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index cd7f965a66..3fa95f8d62 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -10,8 +10,7 @@ import { isPromiseCanceledError } from 'vs/base/common/errors'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { Event as EventOf, Emitter } from 'vs/base/common/event'; -import { IAction, Action } from 'vs/base/common/actions'; -import { Separator, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction, Action, Separator, SubmenuAction } from 'vs/base/common/actions'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { append, $, addClass, toggleClass, Dimension, hide, show } from 'vs/base/browser/dom'; @@ -20,11 +19,12 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, AutoUpdateConfigurationKey, CloseExtensionDetailsOnViewChangeKey } from '../common/extensions'; import { - ShowRecommendedExtensionsAction, /*ShowPopularExtensionsAction,*/ ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction, - EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, SearchCategoryAction, /*RecentlyPublishedExtensionsAction, */ShowInstalledExtensionsAction, ShowOutdatedExtensionsAction, ShowDisabledExtensionsAction, ShowEnabledExtensionsAction + EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, SearchCategoryAction, + /*RecentlyPublishedExtensionsAction, */ShowInstalledExtensionsAction, ShowOutdatedExtensionsAction, ShowDisabledExtensionsAction, + ShowEnabledExtensionsAction, PredefinedExtensionFilterAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView, OutdatedExtensionsView, InstalledExtensionsView, SearchBuiltInExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; @@ -59,8 +59,6 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { URI } from 'vs/base/common/uri'; import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; -import { ContextSubMenu } from 'vs/base/browser/contextmenu'; -import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; const NonEmptyWorkspaceContext = new RawContextKey('nonEmptyWorkspace', false); const DefaultViewsContext = new RawContextKey('defaultExtensionViews', true); @@ -347,6 +345,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IInstantiationService instantiationService: IInstantiationService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @INotificationService private readonly notificationService: INotificationService, @IViewletService private readonly viewletService: IViewletService, @IThemeService themeService: IThemeService, @@ -497,41 +496,43 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE } getActions(): IAction[] { + const filterActions: IAction[] = []; + + // Local extensions filters + filterActions.push(...[ + this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, localize('builtin filter', "Built-in")), + this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, localize('installed filter', "Installed")), + this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, localize('enabled filter', "Enabled")), + this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, localize('disabled filter', "Disabled")), + this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, localize('outdated filter', "Outdated")), + ]); + + if (this.extensionGalleryService.isEnabled()) { + filterActions.splice(0, 0, ...[ + // this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.featured', localize('featured filter', "Featured"), '@featured'), // {{SQL CARBON EDIT}} + // this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.popular', localize('most popular filter', "Most Popular"), '@popular'), // {{SQL CARBON EDIT}} + this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.recommended', localize('most popular recommended', "Recommended"), '@recommended'), + // this.instantiationService.createInstance(RecentlyPublishedExtensionsAction, RecentlyPublishedExtensionsAction.ID, localize('recently published filter', "Recently Published")), // {{SQL CARBON EDIT}} + new SubmenuAction('workbench.extensions.action.filterExtensionsByCategory', localize('filter by category', "Category"), EXTENSION_CATEGORIES.map(category => this.instantiationService.createInstance(SearchCategoryAction, `extensions.actions.searchByCategory.${category}`, category, category))), + new Separator(), + ]); + filterActions.push(...[ + new Separator(), + new SubmenuAction('workbench.extensions.action.sortBy', localize('sorty by', "Sort By"), [ + // this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Install Count"), this.onSearchChange, 'installs'), // {{SQL CARBON EDIT}} + // this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.rating', localize('sort by rating', "Rating"), this.onSearchChange, 'rating'), // {{SQL CARBON EDIT}} + this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.name', localize('sort by name', "Name"), this.onSearchChange, 'name'), + this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.publishedDate', localize('sort by date', "Published Date"), this.onSearchChange, 'publishedDate'), + ]), + ]); + } + return [ - new Action('workbench.extensions.action.filterExtensions', localize('filterExtensions', "Filter Extensions..."), 'codicon-filter', true), + new SubmenuAction('workbench.extensions.action.filterExtensions', localize('filterExtensions', "Filter Extensions..."), filterActions, 'codicon-filter'), this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, this.searchBox ? this.searchBox.getValue() : ''), ]; } - getActionViewItem(action: IAction): IActionViewItem | undefined { - if (action.id === 'workbench.extensions.action.filterExtensions') { - return new DropdownMenuActionViewItem(action, - [ - // this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, localize('most popular filter', "Most Popular")), // {{SQL CARBON EDIT}} - // this.instantiationService.createInstance(RecentlyPublishedExtensionsAction, RecentlyPublishedExtensionsAction.ID, localize('recently published filter', "Recently Published")), // {{SQL CARBON EDIT}} - this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('recomended filter', "Recommended")), - new ContextSubMenu(localize('filter by category', "Category"), EXTENSION_CATEGORIES.map(category => this.instantiationService.createInstance(SearchCategoryAction, `extensions.actions.searchByCategory.${category}`, category, category))), - - new Separator(), - this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, localize('builtin filter', "Built-in")), - this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, localize('installed filter', "Installed")), - this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, localize('enabled filter', "Enabled")), - this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, localize('disabled filter', "Disabled")), - this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, localize('outdated filter', "Outdated")), - - new Separator(), - new ContextSubMenu(localize('sorty by', "Sort By"), [ - // this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Install Count"), this.onSearchChange, 'installs'), // {{SQL CARBON EDIT}} - // this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.rating', localize('sort by rating', "Rating"), this.onSearchChange, 'rating'), // {{SQL CARBON EDIT}} - this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.name', localize('sort by name', "Name"), this.onSearchChange, 'name'), - this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.publishedDate', localize('sort by date', "Published Date"), this.onSearchChange, 'publishedDate'), - ]), - ], - this.contextMenuService, undefined, undefined, undefined, 'codicon-filter', undefined, true); - } - return super.getActionViewItem(action); - } - getSecondaryActions(): IAction[] { const actions: IAction[] = []; @@ -541,11 +542,13 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE } else { actions.push(this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)); } - actions.push(this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL)); actions.push(new Separator()); - actions.push(this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL)); actions.push(this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL)); + actions.push(this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL)); + + actions.push(new Separator()); + actions.push(this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL)); return actions; } @@ -565,7 +568,14 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE } private normalizedQuery(): string { - return this.searchBox ? this.searchBox.getValue().replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:') : ''; + return this.searchBox + ? this.searchBox.getValue() + .replace(/@category/g, 'category') + .replace(/@tag:/g, 'tag:') + .replace(/@ext:/g, 'ext:') + .replace(/@featured/g, 'featured') + .replace(/@popular/g, '@sort:installs') + : ''; } saveState(): void { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index aa6d055a3f..4a24a51ecc 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -25,8 +25,7 @@ import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction, InstallLocalExtensionsInRemoteAction, getContextMenuActions } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction, InstallLocalExtensionsInRemoteAction, getContextMenuActions, ExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { WorkbenchPagedList, ListResourceNavigator } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -38,7 +37,7 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IAction, Action } from 'vs/base/common/actions'; +import { IAction, Action, Separator } from 'vs/base/common/actions'; import { ExtensionType, ExtensionIdentifier, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -248,7 +247,11 @@ export class ExtensionsListView extends ViewPane { }); } else if (e.element) { const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped(), this.instantiationService, e.element); - groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = e.element!)); + groups.forEach(group => group.forEach(extensionAction => { + if (extensionAction instanceof ExtensionAction) { + extensionAction.extension = e.element!; + } + })); let actions: IAction[] = []; for (const menuActions of groups) { actions = [...actions, ...menuActions, new Separator()]; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index fddaec2ab0..fcf2ee129d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -40,6 +40,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { asDomUri } from 'vs/base/browser/dom'; import { getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge'; import { isWeb } from 'vs/base/common/platform'; +import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; // {{SQL CARBON EDIT}} import { IOpenerService } from 'vs/platform/opener/common/opener'; // {{SQL CARBON EDIT}} @@ -686,14 +687,79 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (extensions.length === 1) { return extensions[0]; } + const enabledExtensions = extensions.filter(e => e.local && this.extensionEnablementService.isEnabled(e.local)); - if (enabledExtensions.length === 0) { - return extensions[0]; - } if (enabledExtensions.length === 1) { return enabledExtensions[0]; } - return enabledExtensions.find(e => e.server === this.extensionManagementServerService.remoteExtensionManagementServer) || enabledExtensions[0]; + + const extensionsToChoose = enabledExtensions.length ? enabledExtensions : extensions; + + let extension = extensionsToChoose.find(extension => { + for (const extensionKind of getExtensionKind(extension.local!.manifest, this.productService, this.configurationService)) { + switch (extensionKind) { + case 'ui': + /* UI extension is chosen only if it is installed locally */ + if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) { + return true; + } + return false; + case 'workspace': + /* Choose remote workspace extension if exists */ + if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) { + return true; + } + return false; + case 'web': + /* Choose web extension if exists */ + if (extension.server === this.extensionManagementServerService.webExtensionManagementServer) { + return true; + } + return false; + } + } + return false; + }); + + if (!extension && this.extensionManagementServerService.localExtensionManagementServer) { + extension = extensionsToChoose.find(extension => { + for (const extensionKind of getExtensionKind(extension.local!.manifest, this.productService, this.configurationService)) { + switch (extensionKind) { + case 'workspace': + /* Choose local workspace extension if exists */ + if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) { + return true; + } + return false; + case 'web': + /* Choose local web extension if exists */ + if (extension.server === this.extensionManagementServerService.localExtensionManagementServer) { + return true; + } + return false; + } + } + return false; + }); + } + + if (!extension && this.extensionManagementServerService.remoteExtensionManagementServer) { + extension = extensionsToChoose.find(extension => { + for (const extensionKind of getExtensionKind(extension.local!.manifest, this.productService, this.configurationService)) { + switch (extensionKind) { + case 'web': + /* Choose remote web extension if exists */ + if (extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) { + return true; + } + return false; + } + } + return false; + }); + } + + return extension || extensions[0]; } private fromGallery(gallery: IGalleryExtension, maliciousExtensionSet: Set): IExtension { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 516af6f860..bbd8e12668 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/runtimeExtensionsEditor'; import * as nls from 'vs/nls'; import * as os from 'os'; import { IProductService } from 'vs/platform/product/common/productService'; -import { Action, IAction } from 'vs/base/common/actions'; +import { Action, IAction, Separator } from 'vs/base/common/actions'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -18,7 +18,7 @@ import { IExtensionService, IExtensionsStatus, IExtensionHostProfile } from 'vs/ import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { append, $, addClass, toggleClass, Dimension, clearNode } from 'vs/base/browser/dom'; -import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -164,16 +164,17 @@ export class RuntimeExtensionsEditor extends BaseEditor { this._register(this._extensionService.onDidChangeExtensionsStatus(() => this._updateSoon.schedule())); } - private _updateExtensions(): void { - this._elements = this._resolveExtensions(); + private async _updateExtensions(): Promise { + this._elements = await this._resolveExtensions(); if (this._list) { this._list.splice(0, this._list.length, this._elements); } } - private _resolveExtensions(): IRuntimeExtension[] { + private async _resolveExtensions(): Promise { let marketplaceMap: { [id: string]: IExtension; } = Object.create(null); - for (let extension of this._extensionsWorkbenchService.local) { + const marketPlaceExtensions = await this._extensionsWorkbenchService.queryLocal(); + for (let extension of marketPlaceExtensions) { marketplaceMap[ExtensionIdentifier.toKey(extension.identifier.id)] = extension; } @@ -328,7 +329,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { } else { data.icon.style.visibility = 'inherit'; } - data.name.textContent = element.marketplaceInfo ? element.marketplaceInfo.displayName : element.description.displayName || ''; + data.name.textContent = element.marketplaceInfo.displayName; data.version.textContent = element.description.version; const activationTimes = element.status.activationTimes!; @@ -462,11 +463,10 @@ export class RuntimeExtensionsEditor extends BaseEditor { actions.push(new ReportExtensionIssueAction(e.element, this._openerService, this._clipboardService, this._productService)); actions.push(new Separator()); - if (e.element.marketplaceInfo) { - actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledWorkspace))); - actions.push(new Action('runtimeExtensionsEditor.action.disable', nls.localize('disable', "Disable"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledGlobally))); - actions.push(new Separator()); - } + actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledWorkspace))); + actions.push(new Action('runtimeExtensionsEditor.action.disable', nls.localize('disable', "Disable"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.DisabledGlobally))); + actions.push(new Separator()); + const state = this._extensionHostProfileService.state; if (state === ProfileSessionState.Running) { actions.push(this._instantiationService.createInstance(StopExtensionHostProfileAction, StopExtensionHostProfileAction.ID, StopExtensionHostProfileAction.LABEL)); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 59fd339b30..9563b2361b 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -14,7 +14,7 @@ import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, InstallOperation, IExtensionTipsService, IGalleryMetadata } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionRecommendationsService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; @@ -34,7 +34,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { NativeURLService } from 'vs/platform/url/common/urlService'; import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, IExtension, ExtensionKind } from 'vs/platform/extensions/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; @@ -46,6 +46,8 @@ import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestSer import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService'; +import { Schemas } from 'vs/base/common/network'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; suite('ExtensionsWorkbenchServiceTest', () => { @@ -981,6 +983,384 @@ suite('ExtensionsWorkbenchServiceTest', () => { assert.equal(actual[0].enablementState, EnablementState.DisabledWorkspace); }); + test('test user extension is preferred when the same extension exists as system and user extension', async () => { + testObject = await aWorkbenchService(); + const userExtension = aLocalExtension('pub.a'); + const systemExtension = aLocalExtension('pub.a', {}, { type: ExtensionType.System }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [systemExtension, userExtension]); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, userExtension); + }); + + test('test user extension is disabled when the same extension exists as system and user extension and system extension is disabled', async () => { + testObject = await aWorkbenchService(); + const systemExtension = aLocalExtension('pub.a', {}, { type: ExtensionType.System }); + await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([systemExtension], EnablementState.DisabledGlobally); + const userExtension = aLocalExtension('pub.a'); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [systemExtension, userExtension]); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, userExtension); + assert.equal(actual[0].enablementState, EnablementState.DisabledGlobally); + }); + + test('Test local ui extension is chosen if it exists only in local server', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['ui']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + }); + + test('Test local workspace extension is chosen if it exists only in local server', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['workspace']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + }); + + test('Test local web extension is chosen if it exists only in local server', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['web']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + }); + + test('Test local ui,workspace extension is chosen if it exists only in local server', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['ui', 'workspace']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + }); + + test('Test local workspace,ui extension is chosen if it exists only in local server', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['workspace', 'ui']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + }); + + test('Test local ui,workspace,web extension is chosen if it exists only in local server', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['ui', 'workspace', 'web']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + }); + + test('Test local ui,web,workspace extension is chosen if it exists only in local server', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['ui', 'web', 'workspace']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + }); + + test('Test local web,ui,workspace extension is chosen if it exists only in local server', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['web', 'ui', 'workspace']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + }); + + test('Test local web,workspace,ui extension is chosen if it exists only in local server', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['web', 'workspace', 'ui']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + }); + + test('Test local workspace,web,ui extension is chosen if it exists only in local server', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['workspace', 'web', 'ui']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + }); + + test('Test local workspace,ui,web extension is chosen if it exists only in local server', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['workspace', 'ui', 'web']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + }); + + test('Test local UI extension is chosen if it exists in both servers', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['ui']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + }); + + test('Test local ui,workspace extension is chosen if it exists in both servers', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['ui', 'workspace']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + }); + + test('Test remote workspace extension is chosen if it exists in remote server', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['workspace']; + const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, remoteExtension); + }); + + test('Test remote workspace extension is chosen if it exists in both servers', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['workspace']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, remoteExtension); + }); + + test('Test remote workspace extension is chosen if it exists in both servers and local is disabled', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['workspace']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localExtension], EnablementState.DisabledGlobally); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, remoteExtension); + assert.equal(actual[0].enablementState, EnablementState.DisabledGlobally); + }); + + test('Test remote workspace extension is chosen if it exists in both servers and remote is disabled in workspace', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['workspace']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([remoteExtension], EnablementState.DisabledWorkspace); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, remoteExtension); + assert.equal(actual[0].enablementState, EnablementState.DisabledWorkspace); + }); + + test('Test local ui, workspace extension is chosen if it exists in both servers and local is disabled', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['ui', 'workspace']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localExtension], EnablementState.DisabledGlobally); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + assert.equal(actual[0].enablementState, EnablementState.DisabledGlobally); + }); + + test('Test local ui, workspace extension is chosen if it exists in both servers and local is disabled in workspace', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['ui', 'workspace']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localExtension], EnablementState.DisabledWorkspace); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + assert.equal(actual[0].enablementState, EnablementState.DisabledWorkspace); + }); + + test('Test local web extension is chosen if it exists in both servers', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['web']; + const localExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`) }); + const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, localExtension); + }); + + test('Test remote web extension is chosen if it exists only in remote', async () => { + // multi server setup + const extensionKind: ExtensionKind[] = ['web']; + const remoteExtension = aLocalExtension('a', { extensionKind }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); + + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([]), createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + testObject = await aWorkbenchService(); + + const actual = await testObject.queryLocal(); + + assert.equal(actual.length, 1); + assert.equal(actual[0].local, remoteExtension); + }); + async function aWorkbenchService(): Promise { const workbenchService: ExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); await workbenchService.queryLocal(); @@ -1031,4 +1411,49 @@ suite('ExtensionsWorkbenchServiceTest', () => { }); }); } + + function aMultiExtensionManagementServerService(instantiationService: TestInstantiationService, localExtensionManagementService?: IExtensionManagementService, remoteExtensionManagementService?: IExtensionManagementService): IExtensionManagementServerService { + const localExtensionManagementServer: IExtensionManagementServer = { + id: 'vscode-local', + label: 'local', + extensionManagementService: localExtensionManagementService || createExtensionManagementService() + }; + const remoteExtensionManagementServer: IExtensionManagementServer = { + id: 'vscode-remote', + label: 'remote', + extensionManagementService: remoteExtensionManagementService || createExtensionManagementService() + }; + return { + _serviceBrand: undefined, + localExtensionManagementServer, + remoteExtensionManagementServer, + webExtensionManagementServer: null, + getExtensionManagementServer: (extension: IExtension) => { + if (extension.location.scheme === Schemas.file) { + return localExtensionManagementServer; + } + if (extension.location.scheme === REMOTE_HOST_SCHEME) { + return remoteExtensionManagementServer; + } + throw new Error(''); + } + }; + } + + function createExtensionManagementService(installed: ILocalExtension[] = []): IExtensionManagementService { + return { + onInstallExtension: Event.None, + onDidInstallExtension: Event.None, + onUninstallExtension: Event.None, + onDidUninstallExtension: Event.None, + getInstalled: () => Promise.resolve(installed), + installFromGallery: (extension: IGalleryExtension) => Promise.reject(new Error('not supported')), + updateMetadata: async (local: ILocalExtension, metadata: IGalleryMetadata) => { + local.identifier.uuid = metadata.id; + local.publisherDisplayName = metadata.publisherDisplayName; + local.publisherId = metadata.publisherId; + return local; + } + }; + } }); diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 28d8196cfc..9c2bff5093 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -16,7 +16,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { QuickFixAction, QuickFixActionViewItem } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import { ILabelService } from 'vs/platform/label/common/label'; import { dirname, basename, isEqual } from 'vs/base/common/resources'; @@ -52,6 +52,7 @@ import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Progress } from 'vs/platform/progress/common/progress'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export type TreeElement = ResourceMarkers | Marker | RelatedInformation; diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index 1df344420f..a71fe2bd78 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/markers'; import { URI } from 'vs/base/common/uri'; import * as dom from 'vs/base/browser/dom'; -import { IAction, IActionViewItem, Action } from 'vs/base/common/actions'; +import { IAction, IActionViewItem, Action, Separator } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; @@ -33,7 +33,7 @@ import { deepClone } from 'vs/base/common/objects'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement, MarkersTreeAccessibilityProvider, MarkersViewModel, ResourceDragAndDrop } from 'vs/workbench/contrib/markers/browser/markersTreeViewer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { Separator, ActionViewItem, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMenuService, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -50,6 +50,7 @@ import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/vie import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Codicon } from 'vs/base/common/codicons'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterable> { return Iterable.map(resourceMarkers.markers, m => { diff --git a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts index ec7e39d552..59c1ee7678 100644 --- a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -5,7 +5,7 @@ import { Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; -import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; +import { Action, IAction, IActionRunner, Separator } from 'vs/base/common/actions'; import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -15,7 +15,7 @@ import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { IThemeService, registerThemingParticipant, ICssStyleCollector, IColorTheme } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { toDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { BaseActionViewItem, ActionViewItem, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { badgeBackground, badgeForeground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -23,10 +23,11 @@ import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedH import { Marker } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; -import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IViewsService } from 'vs/workbench/common/views'; import { Codicon } from 'vs/base/common/codicons'; +import { BaseActionViewItem, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; export class ShowProblemsPanelAction extends Action { @@ -179,11 +180,12 @@ class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { super(action, { getActions: () => this.getActions() }, contextMenuService, - action => undefined, - actionRunner!, - undefined, - action.class, - () => { return AnchorAlignment.RIGHT; }); + { + actionRunner, + classNames: action.class, + anchorAlignmentProvider: () => AnchorAlignment.RIGHT + } + ); } render(container: HTMLElement): void { diff --git a/src/vs/workbench/contrib/notebook/browser/constants.ts b/src/vs/workbench/contrib/notebook/browser/constants.ts index 1be1d63b2a..01283f26fa 100644 --- a/src/vs/workbench/contrib/notebook/browser/constants.ts +++ b/src/vs/workbench/contrib/notebook/browser/constants.ts @@ -18,7 +18,7 @@ export const BOTTOM_CELL_TOOLBAR_OFFSET = 12; export const CELL_STATUSBAR_HEIGHT = 22; // Margin above editor -export const EDITOR_TOP_MARGIN = 6; +export const CELL_TOP_MARGIN = 6; export const CELL_BOTTOM_MARGIN = 6; // Top and bottom padding inside the monaco editor in a cell, which are included in `cell.editorHeight` @@ -26,3 +26,5 @@ export const EDITOR_TOP_PADDING = 12; export const EDITOR_BOTTOM_PADDING = 4; export const CELL_OUTPUT_PADDING = 14; + +export const COLLAPSED_INDICATOR_HEIGHT = 24; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 5c85088648..370d60c762 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { URI } from 'vs/base/common/uri'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; @@ -18,12 +18,12 @@ import { InputFocusedContext, InputFocusedContextKey } from 'vs/platform/context import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { BaseCellRenderTemplate, CellEditState, ICellViewModel, INotebookEditor, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_CELL_HAS_OUTPUTS, CellFocusMode, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_CELL_LIST_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, NotebookCellRunState, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { BaseCellRenderTemplate, CellCollapseState, CellEditState, CellFocusMode, ICellViewModel, INotebookEditor, NOTEBOOK_CELL_CONTENT_COLLAPSED, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { CellKind, CellUri, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; // Notebook Commands const EXECUTE_NOTEBOOK_COMMAND_ID = 'notebook.execute'; @@ -72,6 +72,11 @@ const CENTER_ACTIVE_CELL = 'notebook.centerActiveCell'; const FOCUS_IN_OUTPUT_COMMAND_ID = 'notebook.cell.focusInOutput'; const FOCUS_OUT_OUTPUT_COMMAND_ID = 'notebook.cell.focusOutOutput'; +const COLLAPSE_CELL_CONTENT_COMMAND_ID = 'notebook.cell.collapseCellContent'; +const COLLAPSE_CELL_OUTPUT_COMMAND_ID = 'notebook.cell.collapseCellOutput'; +const EXPAND_CELL_CONTENT_COMMAND_ID = 'notebook.cell.expandCellContent'; +const EXPAND_CELL_OUTPUT_COMMAND_ID = 'notebook.cell.expandCellOutput'; + export const NOTEBOOK_ACTIONS_CATEGORY = { value: localize('notebookActions.category', "Notebook"), original: 'Notebook' }; export const CELL_TITLE_CELL_GROUP_ID = 'inline/cell'; @@ -1398,7 +1403,7 @@ registerAction2(class extends NotebookCellAction { super( { id: JOIN_CELL_ABOVE_COMMAND_ID, - title: localize('notebookActions.joinCellAbove', "Join with Previous Cell"), + title: localize('notebookActions.joinCellAbove', "Join With Previous Cell"), keybinding: { when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.KEY_J, @@ -1417,7 +1422,7 @@ registerAction2(class extends NotebookCellAction { super( { id: JOIN_CELL_BELOW_COMMAND_ID, - title: localize('notebookActions.joinCellBelow', "Join with Next Cell"), + title: localize('notebookActions.joinCellBelow', "Join With Next Cell"), keybinding: { when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_J, @@ -1456,3 +1461,95 @@ registerAction2(class extends NotebookCellAction { return context.notebookEditor.revealInCenter(context.cell); } }); + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: COLLAPSE_CELL_CONTENT_COMMAND_ID, + title: localize('notebookActions.collapseCellContent', "Collapse Cell Content"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_CONTENT_COLLAPSED.toNegated(), InputFocusedContext.toNegated()), + primary: KeyChord(KeyCode.KEY_C, KeyCode.KEY_C), + weight: KeybindingWeight.WorkbenchContrib + }, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_CONTENT_COLLAPSED.toNegated()), + group: '3_collapse', + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + context.cell.collapseState = CellCollapseState.Collapsed; + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: EXPAND_CELL_CONTENT_COMMAND_ID, + title: localize('notebookActions.expandCellContent', "Expand Cell Content"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_CONTENT_COLLAPSED), + primary: KeyChord(KeyCode.KEY_C, KeyCode.KEY_C), + weight: KeybindingWeight.WorkbenchContrib + }, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_CONTENT_COLLAPSED), + group: '3_collapse', + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + context.cell.collapseState = CellCollapseState.Normal; + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: COLLAPSE_CELL_OUTPUT_COMMAND_ID, + title: localize('notebookActions.collapseCellOutput', "Collapse Cell Output"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated(), InputFocusedContext.toNegated()), + primary: KeyChord(KeyCode.KEY_C, KeyCode.KEY_O), + weight: KeybindingWeight.WorkbenchContrib + }, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated()), + group: '3_collapse', + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + context.cell.collapseState = CellCollapseState.Collapsed; + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super({ + id: EXPAND_CELL_OUTPUT_COMMAND_ID, + title: localize('notebookActions.expandCellOutput', "Expand Cell Output"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED), + primary: KeyChord(KeyCode.KEY_C, KeyCode.KEY_C), + weight: KeybindingWeight.WorkbenchContrib + }, + // menu: { + // id: MenuId.NotebookCellTitle, + // when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED), + // group: '3_collapse', + // } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + context.cell.collapseState = CellCollapseState.Normal; + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts index 6dba27352d..d5c65b3c8f 100644 --- a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts +++ b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts @@ -27,12 +27,14 @@ namespace NotebookRendererContribution { export const viewType = 'viewType'; export const displayName = 'displayName'; export const mimeTypes = 'mimeTypes'; + export const entrypoint = 'entrypoint'; } -interface INotebookRendererContribution { +export interface INotebookRendererContribution { readonly [NotebookRendererContribution.viewType]: string; readonly [NotebookRendererContribution.displayName]: string; readonly [NotebookRendererContribution.mimeTypes]?: readonly string[]; + readonly [NotebookRendererContribution.entrypoint]?: string; } const notebookProviderContribution: IJSONSchema = { @@ -115,7 +117,11 @@ const notebookRendererContribution: IJSONSchema = { items: { type: 'string' } - } + }, + [NotebookRendererContribution.entrypoint]: { + type: 'string', + description: nls.localize('contributes.notebook.renderer.entrypoint', 'File to load in the webview to render the extension.'), + }, } } }; diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index e226929a63..8e22372c53 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -211,6 +211,21 @@ outline: none !important; } +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part { + cursor: pointer; + box-sizing: border-box; + padding-left: 2px; +} + +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part .codicon { + margin-top: 2px; +} + +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed .notebook-folding-indicator, +.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed > .monaco-toolbar { + display: none; +} + /* top and bottom borders on cells */ .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-top:before, .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-bottom:before, @@ -423,7 +438,6 @@ position: absolute; box-sizing: border-box; top: 0px; - opacity: 0; } .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-side { @@ -686,12 +700,14 @@ top: 0; left: 0; right: 0; - height: 100%; + width: 26px; + height: 26px; + cursor: pointer; } .monaco-workbench .notebookOverlay > .cell-list-container .notebook-folding-indicator .codicon { visibility: visible; - padding: 10px 0 0 10px; + padding: 8px 0 0 10px; } /** Theming */ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 219873fea2..93dfbb3387 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -48,6 +48,8 @@ export const NOTEBOOK_CELL_RUNNABLE = new RawContextKey('notebookCellRu export const NOTEBOOK_CELL_MARKDOWN_EDIT_MODE = new RawContextKey('notebookCellMarkdownEditMode', false); // bool export const NOTEBOOK_CELL_RUN_STATE = new RawContextKey('notebookCellRunState', undefined); // idle, running export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey('notebookCellHasOutputs', false); // bool +export const NOTEBOOK_CELL_CONTENT_COLLAPSED = new RawContextKey('notebookCellContentIsCollapsed', false); // bool +export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey('notebookCellOutputIsCollapsed', false); // bool // Kernels @@ -110,6 +112,8 @@ export interface ICellViewModel { readonly model: NotebookCellTextModel; readonly id: string; readonly textBuffer: IReadonlyTextBuffer; + collapseState: CellCollapseState; + outputCollapseState: CellCollapseState; dragging: boolean; handle: number; uri: URI; @@ -454,6 +458,8 @@ export interface INotebookCellList { } export interface BaseCellRenderTemplate { + editorPart: HTMLElement; + collapsedPart: HTMLElement; contextKeyService: IContextKeyService; container: HTMLElement; cellContainer: HTMLElement; @@ -471,7 +477,6 @@ export interface BaseCellRenderTemplate { } export interface MarkdownCellRenderTemplate extends BaseCellRenderTemplate { - editorPart: HTMLElement; editorContainer: HTMLElement; foldingIndicator: HTMLElement; currentEditor?: ICodeEditor; @@ -535,6 +540,11 @@ export enum CellEditState { Editing } +export enum CellCollapseState { + Normal, + Collapsed +} + export enum CellFocusMode { Container, Editor @@ -553,6 +563,7 @@ export interface CellViewModelStateChangeEvent { focusModeChanged?: boolean; editStateChanged?: boolean; languageChanged?: boolean; + collapseStateChanged?: boolean; foldingStateChanged?: boolean; contentChanged?: boolean; outputIsHoveredChanged?: boolean; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index b21a6c97bb..aa1a820d59 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -27,7 +27,7 @@ import { contrastBorder, editorBackground, focusBorder, foreground, registerColo import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorOptions, IEditorMemento } from 'vs/workbench/common/editor'; -import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_BOTTOM_PADDING, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, SCROLLABLE_ELEMENT_PADDING_TOP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_BOTTOM_PADDING, CELL_TOP_MARGIN, EDITOR_TOP_PADDING, SCROLLABLE_ELEMENT_PADDING_TOP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, INotebookCellList, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_OUTPUT_FOCUSED, INotebookDeltaDecoration } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; @@ -69,6 +69,8 @@ export class NotebookEditorOptions extends EditorOptions { } } +const NotebookEditorActiveKernelCache = 'workbench.editor.notebook.activeKernel'; + export class NotebookEditorWidget extends Disposable implements INotebookEditor { static readonly ID: string = 'workbench.editor.notebook'; private static readonly EDITOR_MEMENTOS = new Map>(); @@ -98,6 +100,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor protected readonly _contributions: { [key: string]: INotebookEditorContribution; }; private _scrollBeyondLastLine: boolean; private readonly _memento: Memento; + private readonly _activeKernelMemento: Memento; private readonly _onDidFocusEmitter = this._register(new Emitter()); public readonly onDidFocus = this._onDidFocusEmitter.event; private _cellContextKeyManager: CellContextKeyManager | null = null; @@ -150,6 +153,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } this._activeKernel = kernel; + + const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL); + memento[this.viewModel!.viewType] = this._activeKernel?.id; + this._activeKernelMemento.saveMemento(); this._onDidChangeKernel.fire(); } @@ -197,6 +204,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor ) { super(); this._memento = new Memento(NotebookEditorWidget.ID, storageService); + this._activeKernelMemento = new Memento(NotebookEditorActiveKernelCache, storageService); this._outputRenderer = new OutputRenderer(this, this.instantiationService); this._contributions = {}; @@ -613,12 +621,17 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private async _setKernelsFromProviders(provider: NotebookProviderInfo, kernels: INotebookKernelInfo2[], tokenSource: CancellationTokenSource) { const rawAssociations = this.configurationService.getValue(notebookKernelProviderAssociationsSettingId) || []; const userSetKernelProvider = rawAssociations.filter(e => e.viewType === this.viewModel?.viewType)[0]?.kernelProvider; + const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL); if (userSetKernelProvider) { const filteredKernels = kernels.filter(kernel => kernel.extension.value === userSetKernelProvider); if (filteredKernels.length) { - this.activeKernel = filteredKernels.find(kernel => kernel.isPreferred) || filteredKernels[0]; + const cachedKernelId = memento[provider.id]; + this.activeKernel = + filteredKernels.find(kernel => kernel.isPreferred) + || filteredKernels.find(kernel => kernel.id === cachedKernelId) + || filteredKernels[0]; } else { this.activeKernel = undefined; } @@ -628,6 +641,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor await (this.activeKernel as INotebookKernelInfo2).resolve(this.viewModel!.uri, this.getId(), tokenSource.token); // {{SQL CARBON EDIT}} strict-null-checks } + memento[provider.id] = this._activeKernel?.id; + this._activeKernelMemento.saveMemento(); + tokenSource.dispose(); return; } @@ -635,10 +651,17 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // choose a preferred kernel const kernelsFromSameExtension = kernels.filter(kernel => kernel.extension.value === provider.providerExtensionId); if (kernelsFromSameExtension.length) { - const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred) || kernelsFromSameExtension[0]; + const cachedKernelId = memento[provider.id]; + + const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred) + || kernelsFromSameExtension.find(kernel => kernel.id === cachedKernelId) + || kernelsFromSameExtension[0]; this.activeKernel = preferedKernel; await this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel); await preferedKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token); + + memento[provider.id] = this._activeKernel?.id; + this._activeKernelMemento.saveMemento(); tokenSource.dispose(); return; } @@ -1677,12 +1700,14 @@ registerThemingParticipant((theme, collector) => { if (cellToolbarSeperator) { collector.addRule(`.notebookOverlay .monaco-list-row > .monaco-toolbar { border: solid 1px ${cellToolbarSeperator}; }`); collector.addRule(`.notebookOverlay .cell-bottom-toolbar-container .action-item { border: solid 1px ${cellToolbarSeperator} }`); + collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part { border-bottom: solid 1px ${cellToolbarSeperator} }`); } const focusedCellBackgroundColor = theme.getColor(focusedCellBackground); if (focusedCellBackgroundColor) { collector.addRule(`.notebookOverlay .code-cell-row.focused .cell-focus-indicator, .notebookOverlay .markdown-cell-row.focused { background-color: ${focusedCellBackgroundColor} !important; }`); + collector.addRule(`.notebookOverlay .code-cell-row.focused.collapsed .cell-collapsed-part { background-color: ${focusedCellBackgroundColor} !important; }`); } const cellHoverBackgroundColor = theme.getColor(cellHoverBackground); @@ -1690,6 +1715,8 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .code-cell-row:not(.focused):hover .cell-focus-indicator, .notebookOverlay .code-cell-row:not(.focused).cell-output-hover .cell-focus-indicator, .notebookOverlay .markdown-cell-row:not(.focused):hover { background-color: ${cellHoverBackgroundColor} !important; }`); + collector.addRule(`.notebookOverlay .code-cell-row:not(.focused).cell-output-hover .cell-collapsed-part, + .notebookOverlay .code-cell-row:not(.focused):hover.collapsed .cell-collapsed-part { background-color: ${cellHoverBackgroundColor} !important; }`); } const focusedCellBorderColor = theme.getColor(focusedCellBorder); @@ -1765,7 +1792,7 @@ registerThemingParticipant((theme, collector) => { // Cell Margin collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > div.cell { margin: 0px ${CELL_MARGIN * 2}px 0px ${CELL_MARGIN}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > div.cell.code { margin-left: ${CODE_CELL_LEFT_MARGIN}px; }`); - collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { padding-top: ${EDITOR_TOP_MARGIN}px; }`); + collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { padding-top: ${CELL_TOP_MARGIN}px; }`); collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row { padding-bottom: ${CELL_BOTTOM_MARGIN}px; }`); collector.addRule(`.notebookOverlay .output { margin: 0px ${CELL_MARGIN}px 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .output { width: calc(100% - ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER + (CELL_MARGIN * 2)}px); }`); @@ -1773,11 +1800,13 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > div.cell.markdown { padding-left: ${CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .cell .run-button-container { width: ${CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .cell-drag-image .cell-editor-container > div { padding: ${EDITOR_TOP_PADDING}px 16px ${EDITOR_BOTTOM_PADDING}px 16px; }`); - collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top { height: ${EDITOR_TOP_MARGIN}px; }`); + collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top { height: ${CELL_TOP_MARGIN}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-side { bottom: ${BOTTOM_CELL_TOOLBAR_HEIGHT}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row.code-cell-row .cell-focus-indicator-left { width: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row .cell-focus-indicator-left { width: ${CODE_CELL_LEFT_MARGIN}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator.cell-focus-indicator-right { width: ${CELL_MARGIN * 2}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom { height: ${CELL_BOTTOM_MARGIN}px; }`); collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-shadow-container-bottom { top: ${CELL_BOTTOM_MARGIN}px; }`); + + collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed .cell-collapsed-part { margin-left: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; height: ${COLLAPSED_INDICATOR_HEIGHT}px; }`); }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookPureOutputRenderer.ts b/src/vs/workbench/contrib/notebook/browser/notebookPureOutputRenderer.ts new file mode 100644 index 0000000000..87fbfb6bc8 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookPureOutputRenderer.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI, UriComponents } from 'vs/base/common/uri'; +import { INotebookRendererInfo, IOutputRenderResponse, IOutputRenderRequest } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { joinPath } from 'vs/base/common/resources'; + +/** + * A 'stub' output renderer used when the contribution has an `entrypoint` + * property. Include the entrypoint as its reload and renders an empty string. + */ +export class PureNotebookOutputRenderer implements INotebookRendererInfo { + + public readonly extensionId: ExtensionIdentifier; + public readonly extensionLocation: URI; + public readonly preloads: URI[]; + + + constructor(public readonly id: string, extension: IExtensionDescription, entrypoint: string) { + this.extensionId = extension.identifier; + this.extensionLocation = extension.extensionLocation; + this.preloads = [joinPath(extension.extensionLocation, entrypoint)]; + } + + public render(uri: URI, request: IOutputRenderRequest): Promise | undefined> { + return this.render2(uri, request); + } + + public render2(_uri: URI, request: IOutputRenderRequest): Promise | undefined> { + return Promise.resolve({ + items: request.items.map(cellInfo => ({ + key: cellInfo.key, + outputs: cellInfo.outputs.map(output => ({ + index: output.index, + outputId: output.outputId, + mimeType: output.mimeType, + handlerId: this.id, + // todo@connor4312: temp approach exploring this API: + transformedOutput: `` + })) + })) + }); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 01c5963ff7..b568f744ab 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -31,6 +31,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { flatten } from 'vs/base/common/arrays'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { NotebookKernelProviderAssociationRegistry, updateNotebookKernelProvideAssociationSchema, NotebookViewTypesExtensionRegistry } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; +import { PureNotebookOutputRenderer } from 'vs/workbench/contrib/notebook/browser/notebookPureOutputRenderer'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -287,8 +288,12 @@ export class NotebookService extends Disposable implements INotebookService, ICu this.notebookRenderersInfoStore.add(new NotebookOutputRendererInfo({ id: notebookContribution.viewType, displayName: notebookContribution.displayName, - mimeTypes: notebookContribution.mimeTypes || [] + mimeTypes: notebookContribution.mimeTypes || [], })); + + if (notebookContribution.entrypoint) { + this._notebookRenderers.set(notebookContribution.viewType, new PureNotebookOutputRenderer(notebookContribution.viewType, extension.description, notebookContribution.entrypoint)); + } } } @@ -371,6 +376,8 @@ export class NotebookService extends Disposable implements INotebookService, ICu const kernelChangeEventListener = provider.onDidChangeKernels(() => { this._onDidChangeKernels.fire(); }); + + this._onDidChangeKernels.fire(); return toDisposable(() => { kernelChangeEventListener.dispose(); d.dispose(); @@ -688,7 +695,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu let orderMimeTypes: IOrderedMimeType[] = []; sorted.forEach(mimeType => { - let handlers = this.findBestMatchedRenderer(mimeType); + let handlers = this._findBestMatchedRenderer(mimeType); if (handlers.length) { const handler = handlers[0]; @@ -732,7 +739,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu }; } - findBestMatchedRenderer(mimeType: string): readonly NotebookOutputRendererInfo[] { + private _findBestMatchedRenderer(mimeType: string): readonly NotebookOutputRendererInfo[] { return this.notebookRenderersInfoStore.getContributedRenderer(mimeType); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellActionView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellActionView.ts index f3cbb4e71b..497cb487df 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellActionView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellActionView.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { Action, IAction } from 'vs/base/common/actions'; -import { BaseActionViewItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Action, IAction, Separator } from 'vs/base/common/actions'; import { IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class VerticalSeparator extends Action { static readonly ID = 'vs.actions.verticalSeparator'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys.ts index c904928832..00b3174fae 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys.ts @@ -6,7 +6,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { INotebookTextModel, NotebookCellRunState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; -import { NOTEBOOK_CELL_TYPE, NOTEBOOK_VIEW_TYPE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_RUNNABLE, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_RUN_STATE, NOTEBOOK_CELL_HAS_OUTPUTS, CellViewModelStateChangeEvent, CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NOTEBOOK_CELL_TYPE, NOTEBOOK_VIEW_TYPE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_RUNNABLE, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_RUN_STATE, NOTEBOOK_CELL_HAS_OUTPUTS, CellViewModelStateChangeEvent, CellEditState, NOTEBOOK_CELL_CONTENT_COLLAPSED, CellCollapseState, NOTEBOOK_CELL_OUTPUT_COLLAPSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -19,6 +19,8 @@ export class CellContextKeyManager extends Disposable { private cellRunnable: IContextKey; private cellRunState: IContextKey; private cellHasOutputs: IContextKey; + private cellContentCollapsed: IContextKey; + private cellOutputCollapsed: IContextKey; private markdownEditMode: IContextKey; @@ -38,6 +40,8 @@ export class CellContextKeyManager extends Disposable { this.markdownEditMode = NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.bindTo(this.contextKeyService); this.cellRunState = NOTEBOOK_CELL_RUN_STATE.bindTo(this.contextKeyService); this.cellHasOutputs = NOTEBOOK_CELL_HAS_OUTPUTS.bindTo(this.contextKeyService); + this.cellContentCollapsed = NOTEBOOK_CELL_CONTENT_COLLAPSED.bindTo(this.contextKeyService); + this.cellOutputCollapsed = NOTEBOOK_CELL_OUTPUT_COLLAPSED.bindTo(this.contextKeyService); this.updateForElement(element); } @@ -59,6 +63,7 @@ export class CellContextKeyManager extends Disposable { this.updateForMetadata(); this.updateForEditState(); + this.updateForCollapseState(); this.updateForOutputs(); this.viewType.set(this.element.viewType); @@ -72,6 +77,10 @@ export class CellContextKeyManager extends Disposable { if (e.editStateChanged) { this.updateForEditState(); } + + if (e.collapseStateChanged) { + this.updateForCollapseState(); + } } private updateForMetadata() { @@ -91,6 +100,11 @@ export class CellContextKeyManager extends Disposable { } } + private updateForCollapseState() { + this.cellContentCollapsed.set(this.element.collapseState === CellCollapseState.Collapsed); + this.cellOutputCollapsed.set(this.element.outputCollapseState === CellCollapseState.Collapsed); + } + private updateForOutputs() { if (this.element instanceof CodeCellViewModel) { this.cellHasOutputs.set(this.element.outputs.length > 0); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 10adb50a30..d9dbde1ce4 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -27,8 +27,8 @@ import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenu, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenu, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -36,9 +36,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_TOP_MARGIN, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { CancelCellAction, ChangeCellLanguageAction, ExecuteCellAction, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; -import { BaseCellRenderTemplate, CellEditState, CodeCellRenderTemplate, ICellViewModel, INotebookCellList, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BaseCellRenderTemplate, CellCollapseState, CellEditState, CodeCellRenderTemplate, ICellViewModel, INotebookCellList, INotebookEditor, isCodeCellRenderTemplate, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus'; import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/codeCell'; @@ -47,7 +47,7 @@ import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { CellKind, NotebookCellMetadata, NotebookCellRunState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { VerticalSeparator, createAndFillInActionBarActionsWithVerticalSeparators, VerticalSeparatorViewItem } from './cellActionView'; +import { createAndFillInActionBarActionsWithVerticalSeparators, VerticalSeparator, VerticalSeparatorViewItem } from './cellActionView'; const $ = DOM.$; @@ -78,7 +78,7 @@ export class NotebookCellListDelegate implements IListVirtualDelegate this.keybindingService.lookupKeybinding(action.id), actionViewItemProvider: action => { if (action instanceof MenuItemAction) { - const item = new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); - return item; + return this.instantiationService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); } if (action.id === VerticalSeparator.ID) { @@ -270,14 +271,14 @@ abstract class AbstractCellRenderer { if (actions.primary.length || actions.secondary.length) { templateData.container.classList.add('cell-has-toolbar-actions'); if (isCodeCellRenderTemplate(templateData)) { - templateData.focusIndicator.style.top = `${EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN}px`; - templateData.focusIndicatorRight.style.top = `${EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN}px`; + templateData.focusIndicator.style.top = `${EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN}px`; + templateData.focusIndicatorRight.style.top = `${EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN}px`; } } else { templateData.container.classList.remove('cell-has-toolbar-actions'); if (isCodeCellRenderTemplate(templateData)) { - templateData.focusIndicator.style.top = `${EDITOR_TOP_MARGIN}px`; - templateData.focusIndicatorRight.style.top = `${EDITOR_TOP_MARGIN}px`; + templateData.focusIndicator.style.top = `${CELL_TOP_MARGIN}px`; + templateData.focusIndicatorRight.style.top = `${CELL_TOP_MARGIN}px`; } } }; @@ -298,6 +299,8 @@ abstract class AbstractCellRenderer { this.notebookEditor.selectElement(templateData.currentRenderedCell); } }, true)); + + this.setupCollapsedPart(templateData); } protected commonRenderElement(element: ICellViewModel, index: number, templateData: BaseCellRenderTemplate): void { @@ -307,6 +310,18 @@ abstract class AbstractCellRenderer { templateData.container.classList.remove(DRAGGING_CLASS); } } + + protected setupCollapsedPart(templateData: BaseCellRenderTemplate): void { + templateData.collapsedPart.innerHTML = renderCodicons('$(unfold)'); + DOM.hide(templateData.collapsedPart); + templateData.disposables.add(domEvent(templateData.collapsedPart, DOM.EventType.CLICK)(() => { + if (!templateData.currentRenderedCell) { + return; + } + + templateData.currentRenderedCell.collapseState = CellCollapseState.Normal; + })); + } } export class MarkdownCellRenderer extends AbstractCellRenderer implements IListRenderer { @@ -344,7 +359,9 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR editorPart.style.display = 'none'; const innerContent = DOM.append(container, $('.cell.markdown')); - const foldingIndicator = DOM.append(focusIndicator, DOM.$('.notebook-folding-indicator')); + const foldingIndicator = DOM.append(container, DOM.$('.notebook-folding-indicator')); + + const collapsedPart = DOM.append(container, $('.cell.cell-collapsed-part')); const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); const betweenCellToolbar = disposables.add(this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService)); @@ -353,6 +370,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR const titleMenu = disposables.add(this.cellMenus.getCellTitleMenu(contextKeyService)); const templateData: MarkdownCellRenderTemplate = { + collapsedPart, contextKeyService, container, cellContainer: innerContent, @@ -925,6 +943,8 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende disposables.add(this.editorOptions.onDidChange(newValue => editor.updateOptions(newValue))); + const collapsedPart = DOM.append(container, $('.cell.cell-collapsed-part')); + const progressBar = new ProgressBar(editorPart); progressBar.hide(); disposables.add(progressBar); @@ -946,6 +966,8 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const titleMenu = disposables.add(this.cellMenus.getCellTitleMenu(contextKeyService)); const templateData: CodeCellRenderTemplate = { + editorPart, + collapsedPart, contextKeyService, container, cellContainer, diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts index 581f11a986..39788c0e82 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -11,7 +11,7 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import * as nls from 'vs/nls'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellFocusMode, CodeCellRenderTemplate, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellCollapseState, CellFocusMode, CodeCellRenderTemplate, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; @@ -84,14 +84,20 @@ export class CodeCell extends Disposable { DOM.toggleClass(templateData.container, 'cell-editor-focus', viewCell.focusMode === CellFocusMode.Editor); }; + const updateForCollapseState = () => { + this.viewUpdate(); + }; this._register(viewCell.onDidChangeState((e) => { - if (!e.focusModeChanged) { - return; + if (e.focusModeChanged) { + updateForFocusMode(); } - updateForFocusMode(); + if (e.collapseStateChanged) { + updateForCollapseState(); + } })); updateForFocusMode(); + updateForCollapseState(); templateData.editor?.updateOptions({ readOnly: !(viewCell.getEvaluatedMetadata(notebookEditor.viewModel!.metadata).editable) }); this._register(viewCell.onDidChangeState((e) => { @@ -101,22 +107,24 @@ export class CodeCell extends Disposable { })); this._register(viewCell.onDidChangeState((e) => { - if (!e.languageChanged) { - return; + if (e.languageChanged) { + const mode = this._modeService.create(viewCell.language); + templateData.editor?.getModel()?.setMode(mode.languageIdentifier); } - const mode = this._modeService.create(viewCell.language); - templateData.editor?.getModel()?.setMode(mode.languageIdentifier); + if (e.collapseStateChanged) { + // meh + this.viewCell.layoutChange({}); + this.relayoutCell(); + } })); this._register(viewCell.onDidChangeLayout((e) => { - if (e.outerWidth === undefined) { - return; - } - - const layoutInfo = templateData.editor!.getLayoutInfo(); - if (layoutInfo.width !== viewCell.layoutInfo.editorWidth) { - this.onCellWidthChange(); + if (e.outerWidth !== undefined) { + const layoutInfo = templateData.editor!.getLayoutInfo(); + if (layoutInfo.width !== viewCell.layoutInfo.editorWidth) { + this.onCellWidthChange(); + } } })); @@ -249,6 +257,14 @@ export class CodeCell extends Disposable { } }); + this._register(templateData.editor!.onMouseDown(e => { + // prevent default on right mouse click, otherwise it will trigger unexpected focus changes + // the catch is, it means we don't allow customization of right button mouse down handlers other than the built in ones. + if (e.event.rightButton) { + e.event.preventDefault(); + } + })); + const updateFocusMode = () => viewCell.focusMode = templateData.editor!.hasWidgetFocus() ? CellFocusMode.Editor : CellFocusMode.Container; this._register(templateData.editor!.onDidFocusEditorWidget(() => { updateFocusMode(); @@ -291,6 +307,30 @@ export class CodeCell extends Disposable { } } + private viewUpdate(): void { + if (this.viewCell.collapseState === CellCollapseState.Collapsed) { + this.viewUpdateCollapsed(); + } else { + this.viewUpdateExpanded(); + } + } + + private viewUpdateCollapsed(): void { + DOM.hide(this.templateData.cellContainer); + DOM.show(this.templateData.collapsedPart); + this.templateData.container.classList.toggle('collapsed', true); + + this.relayoutCell(); + } + + private viewUpdateExpanded(): void { + DOM.show(this.templateData.cellContainer); + DOM.hide(this.templateData.collapsedPart); + this.templateData.container.classList.toggle('collapsed', false); + + this.relayoutCell(); + } + private layoutEditor(dimension: IDimension): void { this.templateData.editor?.layout(dimension); this.templateData.statusBarContainer.style.width = `${dimension.width}px`; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts index 750329a39d..8f008d1634 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { hide, IDimension, show, toggleClass, addClass, removeClass } from 'vs/base/browser/dom'; +import * as DOM from 'vs/base/browser/dom'; import { raceCancellation } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { renderCodicons } from 'vs/base/common/codicons'; @@ -12,7 +12,7 @@ import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, INotebookEditor, MarkdownCellRenderTemplate, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFocusMode, INotebookEditor, MarkdownCellRenderTemplate, ICellViewModel, CellCollapseState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellFoldingState } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -50,7 +50,7 @@ export class StatefulMarkdownCell extends Disposable { if (e.editStateChanged) { this.localDisposables.clear(); this.viewUpdate(); - } else if (e.contentChanged) { + } else if (e.contentChanged || e.collapseStateChanged) { this.viewUpdate(); } })); @@ -66,7 +66,7 @@ export class StatefulMarkdownCell extends Disposable { this.focusEditorIfNeeded(); } - toggleClass(templateData.container, 'cell-editor-focus', viewCell.focusMode === CellFocusMode.Editor); + templateData.container.classList.toggle('cell-editor-focus', viewCell.focusMode === CellFocusMode.Editor); }; this._register(viewCell.onDidChangeState((e) => { if (!e.focusModeChanged) { @@ -105,13 +105,13 @@ export class StatefulMarkdownCell extends Disposable { this._register(viewCell.onCellDecorationsChanged((e) => { e.added.forEach(options => { if (options.className) { - addClass(templateData.container, options.className); + DOM.addClass(templateData.container, options.className); } }); e.removed.forEach(options => { if (options.className) { - removeClass(templateData.container, options.className); + DOM.removeClass(templateData.container, options.className); } }); })); @@ -120,7 +120,7 @@ export class StatefulMarkdownCell extends Disposable { viewCell.getCellDecorations().forEach(options => { if (options.className) { - addClass(templateData.container, options.className); + DOM.addClass(templateData.container, options.className); } }); @@ -128,19 +128,31 @@ export class StatefulMarkdownCell extends Disposable { } private viewUpdate(): void { - if (this.viewCell.editState === CellEditState.Editing) { + if (this.viewCell.collapseState === CellCollapseState.Collapsed) { + this.viewUpdateCollapsed(); + } else if (this.viewCell.editState === CellEditState.Editing) { this.viewUpdateEditing(); } else { this.viewUpdatePreview(); } } + private viewUpdateCollapsed(): void { + DOM.show(this.templateData.collapsedPart); + DOM.hide(this.editorPart); + DOM.hide(this.markdownContainer); + this.templateData.container.classList.toggle('collapsed', true); + } + private viewUpdateEditing(): void { // switch to editing mode let editorHeight: number; - show(this.editorPart); - hide(this.markdownContainer); + DOM.show(this.editorPart); + DOM.hide(this.markdownContainer); + DOM.hide(this.templateData.collapsedPart); + this.templateData.container.classList.toggle('collapsed', false); + if (this.editor) { editorHeight = this.editor!.getContentHeight(); @@ -216,8 +228,11 @@ export class StatefulMarkdownCell extends Disposable { private viewUpdatePreview(): void { this.viewCell.detachTextEditor(); - hide(this.editorPart); - show(this.markdownContainer); + DOM.hide(this.editorPart); + DOM.hide(this.templateData.collapsedPart); + DOM.show(this.markdownContainer); + this.templateData.container.classList.toggle('collapsed', false); + this.renderedEditors.delete(this.viewCell); this.markdownContainer.innerHTML = ''; @@ -259,7 +274,7 @@ export class StatefulMarkdownCell extends Disposable { } } - private layoutEditor(dimension: IDimension): void { + private layoutEditor(dimension: DOM.IDimension): void { this.editor?.layout(dimension); this.templateData.statusBarContainer.style.width = `${dimension.width}px`; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 2a18de731d..edb3b4524d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -261,6 +261,8 @@ function webviewPreloads() { interface ICreateCellInfo { outputId: string; + output?: unknown; + mimeType?: string; element: HTMLElement; } @@ -374,10 +376,21 @@ function webviewPreloads() { outputNode.innerHTML = content; cellOutputContainer.appendChild(outputNode); + let pureData: { mimeType: string, output: unknown } | undefined; + const outputScript = cellOutputContainer.querySelector('script.vscode-pure-data'); + if (outputScript) { + try { pureData = JSON.parse(outputScript.innerHTML); } catch { } + } + // eval domEval(outputNode); resizeObserve(outputNode, outputId); - onDidCreateOutput.fire([data.apiNamespace, { element: outputNode, outputId }]); + onDidCreateOutput.fire([data.apiNamespace, { + element: outputNode, + output: pureData?.output, + mimeType: pureData?.mimeType, + outputId + }]); vscode.postMessage({ __vscode_notebook_message: true, diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index 630a77923f..c136455eac 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -14,7 +13,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; import { SearchParams } from 'vs/editor/common/model/textModelSearch'; import { EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, CursorAtBoundary, CellViewModelStateChangeEvent, IEditableCellViewModel, INotebookCellDecorationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFocusMode, CursorAtBoundary, CellViewModelStateChangeEvent, IEditableCellViewModel, INotebookCellDecorationOptions, CellCollapseState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NotebookCellMetadata, NotebookDocumentMetadata, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; @@ -61,13 +60,24 @@ export abstract class BaseCellViewModel extends Disposable { } } - private _currentTokenSource: CancellationTokenSource | undefined; - public set currentTokenSource(v: CancellationTokenSource | undefined) { - this._currentTokenSource = v; + private _collapseState: CellCollapseState = CellCollapseState.Normal; + public get collapseState(): CellCollapseState { + return this._collapseState; } - public get currentTokenSource(): CancellationTokenSource | undefined { - return this._currentTokenSource; + public set collapseState(v: CellCollapseState) { + this._collapseState = v; + this._onDidChangeState.fire({ collapseStateChanged: true }); + } + + private _outputCollapseState: CellCollapseState = CellCollapseState.Normal; + public get outputCollapseState(): CellCollapseState { + return this._outputCollapseState; + } + + public set outputCollapseState(v: CellCollapseState) { + this._collapseState = v; + this._onDidChangeState.fire({ collapseStateChanged: true }); } private _focusMode: CellFocusMode = CellFocusMode.Container; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index 464d7bdac3..026b95b060 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -8,8 +8,8 @@ import * as UUID from 'vs/base/common/uuid'; import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; -import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_RUN_GUTTER, CELL_STATUSBAR_HEIGHT, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, BOTTOM_CELL_TOOLBAR_OFFSET } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFindMatch, CodeCellLayoutChangeEvent, CodeCellLayoutInfo, ICellViewModel, NotebookLayoutInfo, CodeCellLayoutState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_RUN_GUTTER, CELL_STATUSBAR_HEIGHT, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, CELL_TOP_MARGIN, EDITOR_TOP_PADDING, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, BOTTOM_CELL_TOOLBAR_OFFSET, COLLAPSED_INDICATOR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellEditState, CellFindMatch, CodeCellLayoutChangeEvent, CodeCellLayoutInfo, ICellViewModel, NotebookLayoutInfo, CodeCellLayoutState, CellCollapseState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, NotebookCellOutputsSplice, INotebookSearchOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { BaseCellViewModel } from './baseCellViewModel'; @@ -101,42 +101,61 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod this._ensureOutputsTop(); const outputTotalHeight = this._outputsTop!.getTotalValue(); - let newState: CodeCellLayoutState; - let editorHeight: number; - let totalHeight: number; - if (!state.editorHeight && this._layoutInfo.layoutState === CodeCellLayoutState.FromCache) { - // No new editorHeight info - keep cached totalHeight and estimate editorHeight - editorHeight = this.estimateEditorHeight(state.font?.lineHeight); - totalHeight = this._layoutInfo.totalHeight; - newState = CodeCellLayoutState.FromCache; - } else if (state.editorHeight || this._layoutInfo.layoutState === CodeCellLayoutState.Measured) { - // Editor has been measured - editorHeight = this._editorHeight; - totalHeight = this.computeTotalHeight(this._editorHeight, outputTotalHeight); - newState = CodeCellLayoutState.Measured; + if (this.collapseState === CellCollapseState.Normal) { + let newState: CodeCellLayoutState; + let editorHeight: number; + let totalHeight: number; + if (!state.editorHeight && this._layoutInfo.layoutState === CodeCellLayoutState.FromCache) { + // No new editorHeight info - keep cached totalHeight and estimate editorHeight + editorHeight = this.estimateEditorHeight(state.font?.lineHeight); + totalHeight = this._layoutInfo.totalHeight; + newState = CodeCellLayoutState.FromCache; + } else if (state.editorHeight || this._layoutInfo.layoutState === CodeCellLayoutState.Measured) { + // Editor has been measured + editorHeight = this._editorHeight; + totalHeight = this.computeTotalHeight(this._editorHeight, outputTotalHeight); + newState = CodeCellLayoutState.Measured; + } else { + editorHeight = this.estimateEditorHeight(state.font?.lineHeight); + totalHeight = this.computeTotalHeight(editorHeight, outputTotalHeight); + newState = CodeCellLayoutState.Estimated; + } + + const indicatorHeight = editorHeight + CELL_STATUSBAR_HEIGHT + outputTotalHeight; + const outputContainerOffset = EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN + editorHeight + CELL_STATUSBAR_HEIGHT; + const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET; + const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo?.editorWidth; + + this._layoutInfo = { + fontInfo: state.font || null, + editorHeight, + editorWidth, + outputContainerOffset, + outputTotalHeight, + totalHeight, + indicatorHeight, + bottomToolbarOffset, + layoutState: newState + }; } else { - editorHeight = this.estimateEditorHeight(state.font?.lineHeight); - totalHeight = this.computeTotalHeight(editorHeight, outputTotalHeight); - newState = CodeCellLayoutState.Estimated; + const indicatorHeight = COLLAPSED_INDICATOR_HEIGHT + outputTotalHeight; + const outputContainerOffset = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT; + const totalHeight = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT + CELL_BOTTOM_MARGIN + BOTTOM_CELL_TOOLBAR_HEIGHT + outputTotalHeight; + const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET; + + this._layoutInfo = { + fontInfo: state.font || null, + editorHeight: this._layoutInfo.editorHeight, + editorWidth: this._layoutInfo.editorWidth, + outputContainerOffset, + outputTotalHeight, + totalHeight, + indicatorHeight, + bottomToolbarOffset, + layoutState: this._layoutInfo.layoutState + }; } - const indicatorHeight = editorHeight + CELL_STATUSBAR_HEIGHT + outputTotalHeight; - const outputContainerOffset = EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN + editorHeight + CELL_STATUSBAR_HEIGHT; - const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET; - const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo?.editorWidth; - - this._layoutInfo = { - fontInfo: state.font || null, - editorHeight, - editorWidth, - outputContainerOffset, - outputTotalHeight, - totalHeight, - indicatorHeight, - bottomToolbarOffset, - layoutState: newState - }; - if (state.editorHeight || state.outputHeight) { state.totalHeight = true; } @@ -188,7 +207,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod } private computeTotalHeight(editorHeight: number, outputsTotalHeight: number): number { - return EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN + editorHeight + CELL_STATUSBAR_HEIGHT + outputsTotalHeight + BOTTOM_CELL_TOOLBAR_HEIGHT + CELL_BOTTOM_MARGIN; + return EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN + editorHeight + CELL_STATUSBAR_HEIGHT + outputsTotalHeight + BOTTOM_CELL_TOOLBAR_HEIGHT + CELL_BOTTOM_MARGIN; } /** diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts index 0e5e7700c0..900e9289c4 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts @@ -8,8 +8,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import * as UUID from 'vs/base/common/uuid'; import * as editorCommon from 'vs/editor/common/editorCommon'; import * as model from 'vs/editor/common/model'; -import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_STATUSBAR_HEIGHT, EDITOR_TOP_MARGIN, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, BOTTOM_CELL_TOOLBAR_OFFSET } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellFindMatch, ICellViewModel, MarkdownCellLayoutChangeEvent, MarkdownCellLayoutInfo, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_MARGIN, CELL_STATUSBAR_HEIGHT, CELL_TOP_MARGIN, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, BOTTOM_CELL_TOOLBAR_OFFSET, COLLAPSED_INDICATOR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellCollapseState, CellFindMatch, ICellViewModel, MarkdownCellLayoutChangeEvent, MarkdownCellLayoutInfo, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { MarkdownRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/mdRenderer'; import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; import { EditorFoldingStateDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; @@ -45,7 +45,7 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie set editorHeight(newHeight: number) { this._editorHeight = newHeight; - this.totalHeight = this._editorHeight + EDITOR_TOP_MARGIN + CELL_BOTTOM_MARGIN + BOTTOM_CELL_TOOLBAR_HEIGHT + CELL_STATUSBAR_HEIGHT; + this.totalHeight = this._editorHeight + CELL_TOP_MARGIN + CELL_BOTTOM_MARGIN + BOTTOM_CELL_TOOLBAR_HEIGHT + CELL_STATUSBAR_HEIGHT; } get editorHeight() { @@ -92,16 +92,31 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie layoutChange(state: MarkdownCellLayoutChangeEvent) { // recompute - const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo.editorWidth; - const totalHeight = state.totalHeight === undefined ? this._layoutInfo.totalHeight : state.totalHeight; - this._layoutInfo = { - fontInfo: state.font || this._layoutInfo.fontInfo, - editorWidth, - editorHeight: this._editorHeight, - bottomToolbarOffset: totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET, - totalHeight - }; + if (this.collapseState === CellCollapseState.Normal) { + const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo.editorWidth; + const totalHeight = state.totalHeight === undefined ? this._layoutInfo.totalHeight : state.totalHeight; + + this._layoutInfo = { + fontInfo: state.font || this._layoutInfo.fontInfo, + editorWidth, + editorHeight: this._editorHeight, + bottomToolbarOffset: totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET, + totalHeight + }; + } else { + const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo.editorWidth; + const totalHeight = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT + BOTTOM_CELL_TOOLBAR_HEIGHT + CELL_BOTTOM_MARGIN; + state.totalHeight = totalHeight; + + this._layoutInfo = { + fontInfo: state.font || this._layoutInfo.fontInfo, + editorWidth, + editorHeight: this._editorHeight, + bottomToolbarOffset: totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - BOTTOM_CELL_TOOLBAR_OFFSET, + totalHeight + }; + } this._onDidChangeLayout.fire(state); } diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index d42adb6091..187f24163d 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { Action, IAction, RadioGroup } from 'vs/base/common/actions'; +import { Action, IAction, RadioGroup, Separator } from 'vs/base/common/actions'; import { createCancelablePromise, TimeoutTimer } from 'vs/base/common/async'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index 836568f46c..1a43d790db 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IAction } from 'vs/base/common/actions'; -import { IActionViewItem, SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction, IActionViewItem } from 'vs/base/common/actions'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -37,6 +36,7 @@ import { groupBy } from 'vs/base/common/arrays'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { editorBackground, selectBorder } from 'vs/platform/theme/common/colorRegistry'; import { addClass } from 'vs/base/browser/dom'; +import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class OutputViewPane extends ViewPane { diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 067b4dd83f..1a9a2d4888 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -12,8 +12,8 @@ import { dispose, Disposable, IDisposable, combinedDisposable, DisposableStore } import { CheckboxActionViewItem } from 'vs/base/browser/ui/checkbox/checkbox'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; -import { IAction, Action } from 'vs/base/common/actions'; -import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction, Action, Separator } from 'vs/base/common/actions'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorOptions } from 'vs/workbench/common/editor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index ed6f769ee7..e6a24138f6 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -3,9 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ContextSubMenu } from 'vs/base/browser/contextmenu'; import { EventHelper, getDomNodePagePosition } from 'vs/base/browser/dom'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, SubmenuAction } from 'vs/base/common/actions'; import { Delayer } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; @@ -826,7 +825,7 @@ class EditSettingRenderer extends Disposable { const anchor = { x: e.event.posx, y: e.event.posy + 10 }; const actions = this.getSettings(editPreferenceWidget.getLine()).length === 1 ? this.getActions(editPreferenceWidget.preferences[0], this.getConfigurationsMap()[editPreferenceWidget.preferences[0].key]) - : editPreferenceWidget.preferences.map(setting => new ContextSubMenu(setting.key, this.getActions(setting, this.getConfigurationsMap()[setting.key]))); + : editPreferenceWidget.preferences.map(setting => new SubmenuAction(`preferences.submenu.${setting.key}`, setting.key, this.getActions(setting, this.getConfigurationsMap()[setting.key]))); this.contextMenuService.showContextMenu({ getAnchor: () => anchor, getActions: () => actions diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 1f7e86c3d1..e0fe1ff1d2 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -5,7 +5,7 @@ import * as DOM from 'vs/base/browser/dom'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { ActionBar, ActionsOrientation, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { IInputOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; import { Action, IAction } from 'vs/base/common/actions'; @@ -36,6 +36,7 @@ import { ISettingsGroup, IPreferencesService } from 'vs/workbench/services/prefe import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { isEqual } from 'vs/base/common/resources'; import { registerIcon, Codicon } from 'vs/base/common/codicons'; +import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class SettingsHeaderWidget extends Widget implements IViewZone { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 19c5eabbd3..478f6dae5b 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -8,7 +8,6 @@ import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { alert as ariaAlert } from 'vs/base/browser/ui/aria/aria'; import { Button } from 'vs/base/browser/ui/button/button'; import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; @@ -20,7 +19,7 @@ import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IObjectTreeOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; import { ITreeFilter, ITreeModel, ITreeNode, ITreeRenderer, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; -import { Action, IAction } from 'vs/base/common/actions'; +import { Action, IAction, Separator } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; import { Color, RGBA } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; diff --git a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts index 19c7f602ba..84f9b0ce7f 100644 --- a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts +++ b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts @@ -7,7 +7,6 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IAction, Action } from 'vs/base/common/actions'; -import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -19,6 +18,7 @@ import { isStringArray } from 'vs/base/common/types'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export interface IRemoteSelectItem extends ISelectOptionItem { authority: string[]; diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 27eb375e10..9f08d7a060 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -21,11 +21,11 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent, ITreeMouseEvent } from 'vs/base/browser/ui/tree/tree'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { Disposable, IDisposable, toDisposable, MutableDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; -import { ActionBar, ActionViewItem, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { ActionRunner, IAction } from 'vs/base/common/actions'; -import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction, ILocalizedString } from 'vs/platform/actions/common/actions'; -import { createAndFillInContextMenuActions, createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction, ILocalizedString, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { createAndFillInContextMenuActions, createAndFillInActionBarActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IRemoteExplorerService, TunnelModel, MakeAddress, TunnelType, ITunnelItem, Tunnel } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -41,6 +41,7 @@ import { RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export const forwardedPortsViewEnabled = new RawContextKey('forwardedPortsViewEnabled', false); @@ -213,10 +214,11 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer { if (action instanceof MenuItemAction) { - return this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action); + return this.instantiationService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); } return undefined; @@ -451,7 +453,6 @@ export class TunnelPanel extends ViewPane { @IQuickInputService protected quickInputService: IQuickInputService, @ICommandService protected commandService: ICommandService, @IMenuService private readonly menuService: IMenuService, - @INotificationService private readonly notificationService: INotificationService, @IContextViewService private readonly contextViewService: IContextViewService, @IThemeService themeService: IThemeService, @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, @@ -647,10 +648,6 @@ export class TunnelPanel extends ViewPane { super.layoutBody(height, width); this.tree.layout(height, width); } - - getActionViewItem(action: IAction): IActionViewItem | undefined { - return action instanceof MenuItemAction ? new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService) : undefined; - } } export class TunnelPanelDescriptor implements IViewDescriptor { diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 8bb580914a..35e1938d00 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -34,19 +34,17 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IDiffEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Action, IAction, ActionRunner } from 'vs/base/common/actions'; -import { IActionBarOptions, ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { basename, isEqualOrParent } from 'vs/base/common/resources'; import { MenuId, IMenuService, IMenu, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; -import { createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IChange, IEditorModel, ScrollType, IEditorContribution, IDiffEditorModel } from 'vs/editor/common/editorCommon'; import { OverviewRulerLane, ITextModel, IModelDecorationOptions, MinimapPosition } from 'vs/editor/common/model'; import { sortedDiff, firstIndex } from 'vs/base/common/arrays'; import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ISplice } from 'vs/base/common/sequence'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { createStyleSheet } from 'vs/base/browser/dom'; import { ITextFileEditorModel, IResolvedTextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { EncodingMode } from 'vs/workbench/common/editor'; @@ -176,14 +174,11 @@ class DirtyDiffWidget extends PeekViewWidget { editor: ICodeEditor, private model: DirtyDiffModel, @IThemeService private readonly themeService: IThemeService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IMenuService menuService: IMenuService, - @IKeybindingService private readonly keybindingService: IKeybindingService, - @INotificationService private readonly notificationService: INotificationService, - @IContextKeyService contextKeyService: IContextKeyService, - @IContextMenuService private readonly contextMenuService: IContextMenuService + @IContextKeyService contextKeyService: IContextKeyService ) { - super(editor, { isResizeable: true, frameWidth: 1, keepEditorSelection: true }); + super(editor, { isResizeable: true, frameWidth: 1, keepEditorSelection: true }, instantiationService); this._disposables.add(themeService.onDidColorThemeChange(this._applyTheme, this)); this._applyTheme(themeService.getColorTheme()); @@ -274,20 +269,12 @@ class DirtyDiffWidget extends PeekViewWidget { }); return { + ...super._getActionBarOptions(), actionRunner, - actionViewItemProvider: action => this.getActionViewItem(action), orientation: ActionsOrientation.HORIZONTAL_REVERSE }; } - getActionViewItem(action: IAction): IActionViewItem | undefined { - if (!(action instanceof MenuItemAction)) { - return undefined; - } - - return new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); - } - protected _fillBody(container: HTMLElement): void { const options: IDiffEditorOptions = { scrollBeyondLastLine: true, diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index bc67998592..c0887e8ab9 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -123,7 +123,6 @@ export class SCMRepositoryMenus implements IDisposable { disposable.dispose(); menu.dispose(); - contextKeyService.dispose(); if (this.provider.rootUri) { secondary.push(new Action('_openInTerminal', localize('open in terminal', "Open In Terminal"), undefined, true, async () => { @@ -157,7 +156,6 @@ export class SCMRepositoryMenus implements IDisposable { createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g)); menu.dispose(); - contextKeyService.dispose(); return result; } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 0008c951dc..b8a68501b6 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -20,17 +20,15 @@ import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/c import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { MenuItemAction, IMenuService } from 'vs/platform/actions/common/actions'; -import { IAction, IActionViewItem, ActionRunner, Action, RadioGroup } from 'vs/base/common/actions'; -import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IAction, IActionViewItem, ActionRunner, Action, RadioGroup, Separator, SubmenuAction, IActionViewItemProvider } from 'vs/base/common/actions'; import { SCMMenus } from './menus'; -import { ActionBar, IActionViewItemProvider, Separator, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService, LIGHT, registerThemingParticipant, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, connectPrimaryMenu } from './util'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { WorkbenchCompressibleObjectTree, IOpenEvent } from 'vs/platform/list/browser/listService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { disposableTimeout, ThrottledDelayer } from 'vs/base/common/async'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { ITreeNode, ITreeFilter, ITreeSorter, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { ResourceTree, IResourceNode } from 'vs/base/common/resourceTree'; import { ISequence, ISplice, SimpleSequence } from 'vs/base/common/sequence'; @@ -72,7 +70,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ILabelService } from 'vs/platform/label/common/label'; -import { ContextSubMenu } from 'vs/base/browser/contextmenu'; import { KeyCode } from 'vs/base/common/keyCodes'; import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; import { Command } from 'vs/editor/common/modes'; @@ -81,6 +78,7 @@ import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; type TreeElement = ISCMRepository | ISCMInput | ISCMResourceGroup | IResourceNode | ISCMResource; @@ -1102,7 +1100,7 @@ class ViewModel { const viewAction = new SCMViewSubMenuAction(this); if (this.repositories.elements.length !== 1) { - return viewAction.entries; + return Array.isArray(viewAction.actions) ? viewAction.actions : viewAction.actions(); } const menus = this.menus.getRepositoryMenus(this.repositories.elements[0].provider); @@ -1167,9 +1165,11 @@ class ViewModel { } } -class SCMViewSubMenuAction extends ContextSubMenu { +class SCMViewSubMenuAction extends SubmenuAction { constructor(viewModel: ViewModel) { - super(localize('sortAction', "View & Sort"), + super( + 'scm.viewsort', + localize('sortAction', "View & Sort"), [ ...new RadioGroup([ new SCMViewModeListAction(viewModel), @@ -1591,7 +1591,6 @@ export class SCMViewPane extends ViewPane { @IContextMenuService protected contextMenuService: IContextMenuService, @IContextViewService protected contextViewService: IContextViewService, @ICommandService protected commandService: ICommandService, - @INotificationService private readonly notificationService: INotificationService, @IEditorService protected editorService: IEditorService, @IInstantiationService protected instantiationService: IInstantiationService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @@ -1768,11 +1767,7 @@ export class SCMViewPane extends ViewPane { return new StatusBarActionViewItem(action); } - if (!(action instanceof MenuItemAction)) { - return undefined; - } - - return new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + return super.getActionViewItem(action); } getActionsContext(): any { diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index c6ae6cdece..0f446b8098 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -80,6 +80,7 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; import { isWorkspaceFolder, TaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, TaskQuickPick, QUICKOPEN_SKIP_CONFIG } from 'vs/workbench/contrib/tasks/browser/taskQuickPick'; import { ILogService } from 'vs/platform/log/common/log'; +import { once } from 'vs/base/common/functional'; const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history'; const PROBLEM_MATCHER_NEVER_CONFIG = 'task.problemMatchers.neverPrompt'; @@ -223,6 +224,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected _outputChannel: IOutputChannel; protected readonly _onDidStateChange: Emitter; + private _waitForSupportedExecutions: Promise; + private _onDidRegisterSupportedExecutions: Emitter = new Emitter(); constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @@ -331,16 +334,26 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } return task._label; }); - this.setExecutionContexts(); + + this._waitForSupportedExecutions = new Promise(resolve => { + once(this._onDidRegisterSupportedExecutions.event)(() => resolve()); + }); } - protected setExecutionContexts(custom: boolean = true, shell: boolean = true, process: boolean = true): void { - const customContext = CustomExecutionSupportedContext.bindTo(this.contextKeyService); - customContext.set(custom); - const shellContext = ShellExecutionSupportedContext.bindTo(this.contextKeyService); - shellContext.set(shell); - const processContext = ProcessExecutionSupportedContext.bindTo(this.contextKeyService); - processContext.set(process); + public registerSupportedExecutions(custom?: boolean, shell?: boolean, process?: boolean) { + if (custom !== undefined) { + const customContext = CustomExecutionSupportedContext.bindTo(this.contextKeyService); + customContext.set(custom); + } + if (shell !== undefined) { + const shellContext = ShellExecutionSupportedContext.bindTo(this.contextKeyService); + shellContext.set(shell); + } + if (process !== undefined) { + const processContext = ProcessExecutionSupportedContext.bindTo(this.contextKeyService); + processContext.set(process); + } + this._onDidRegisterSupportedExecutions.fire(); } public get onDidStateChange(): Event { @@ -530,9 +543,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } public registerTaskSystem(key: string, info: TaskSystemInfo): void { - if (info.platform === Platform.Platform.Web) { - this.setExecutionContexts(true, false, false); - } this._taskSystemInfos.set(key, info); } @@ -834,7 +844,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?LinkId=733558')); } - public build(): Promise { + public async build(): Promise { return this.getGroupedTasks().then((tasks) => { let runnable = this.createRunnableTask(tasks, TaskGroup.Build); if (!runnable || !runnable.task) { @@ -844,7 +854,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer throw new TaskError(Severity.Info, nls.localize('TaskService.noBuildTask2', 'No build task defined. Mark a task with as a \'build\' group in the tasks.json file.'), TaskErrors.NoBuildTask); } } - return this.executeTask(runnable.task, runnable.resolver); + return this.executeTask(runnable.task, runnable.resolver, TaskRunSource.User); }).then(value => value, (error) => { this.handleError(error); return Promise.reject(error); @@ -861,7 +871,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer throw new TaskError(Severity.Info, nls.localize('TaskService.noTestTask2', 'No test task defined. Mark a task with as a \'test\' group in the tasks.json file.'), TaskErrors.NoTestTask); } } - return this.executeTask(runnable.task, runnable.resolver); + return this.executeTask(runnable.task, runnable.resolver, TaskRunSource.User); }).then(value => value, (error) => { this.handleError(error); return Promise.reject(error); @@ -878,7 +888,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (options && options.attachProblemMatcher && this.shouldAttachProblemMatcher(task) && !InMemoryTask.is(task)) { const toExecute = await this.attachProblemMatcher(task); if (toExecute) { - resolve(this.executeTask(toExecute, resolver)); + resolve(this.executeTask(toExecute, resolver, runSource)); } else { resolve(undefined); } @@ -1452,7 +1462,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }; } - private executeTask(task: Task, resolver: ITaskResolver, runSource?: TaskRunSource): Promise { + private executeTask(task: Task, resolver: ITaskResolver, runSource: TaskRunSource): Promise { enum SaveBeforeRunConfigOptions { Always = 'always', Never = 'never', @@ -1831,7 +1841,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return result; } - public getWorkspaceTasks(runSource: TaskRunSource = TaskRunSource.User): Promise> { + public async getWorkspaceTasks(runSource: TaskRunSource = TaskRunSource.User): Promise> { + await this._waitForSupportedExecutions; if (this._workspaceTasksPromise) { return this._workspaceTasksPromise; } diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index e5d058c0bc..120a73e4f8 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -528,7 +528,7 @@ export class TerminalTaskSystem implements ITaskSystem { } } - private resolveVariablesFromSet(taskSystemInfo: TaskSystemInfo | undefined, workspaceFolder: IWorkspaceFolder | undefined, task: CustomTask | ContributedTask, variables: Set, alreadyResolved: Map): Promise { + private resolveVariablesFromSet(taskSystemInfo: TaskSystemInfo | undefined, workspaceFolder: IWorkspaceFolder | undefined, task: CustomTask | ContributedTask, variables: Set, alreadyResolved: Map): Promise { let isProcess = task.command && task.command.runtime === RuntimeType.Process; let options = task.command && task.command.options ? task.command.options : undefined; let cwd = options ? options.cwd : undefined; @@ -544,7 +544,7 @@ export class TerminalTaskSystem implements ITaskSystem { } } const unresolved = this.findUnresolvedVariables(variables, alreadyResolved); - let resolvedVariables: Promise; + let resolvedVariables: Promise; if (taskSystemInfo && workspaceFolder) { let resolveSet: ResolveSet = { variables: unresolved @@ -560,6 +560,10 @@ export class TerminalTaskSystem implements ITaskSystem { } } resolvedVariables = taskSystemInfo.resolveVariables(workspaceFolder, resolveSet, TaskSourceKind.toConfigurationTarget(task._source.kind)).then(async (resolved) => { + if (!resolved) { + return undefined; + } + this.mergeMaps(alreadyResolved, resolved.variables); resolved.variables = new Map(alreadyResolved); if (isProcess) { @@ -569,14 +573,14 @@ export class TerminalTaskSystem implements ITaskSystem { } resolved.variables.set(TerminalTaskSystem.ProcessVarName, process); } - return Promise.resolve(resolved); + return resolved; }); return resolvedVariables; } else { let variablesArray = new Array(); unresolved.forEach(variable => variablesArray.push(variable)); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { this.configurationResolverService.resolveWithInteraction(workspaceFolder, variablesArray, 'tasks', undefined, TaskSourceKind.toConfigurationTarget(task._source.kind)).then(async (resolvedVariablesMap: Map | undefined) => { if (resolvedVariablesMap) { this.mergeMaps(alreadyResolved, resolvedVariablesMap); @@ -657,6 +661,9 @@ export class TerminalTaskSystem implements ITaskSystem { if (!hasAllVariables) { return this.resolveVariablesFromSet(lastTask.getVerifiedTask().systemInfo, lastTask.getVerifiedTask().workspaceFolder, task, variables, alreadyResolved).then((resolvedVariables) => { + if (!resolvedVariables) { + return { exitCode: 0 }; + } this.currentTask.resolvedVariables = resolvedVariables; return this.executeInTerminal(task, trigger, new VariableResolver(lastTask.getVerifiedTask().workspaceFolder, lastTask.getVerifiedTask().systemInfo, resolvedVariables.variables, this.configurationResolverService), workspaceFolder!); }, reason => { diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index d802abdde4..5f064bf817 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -95,6 +95,7 @@ export interface ITaskService { registerTaskProvider(taskProvider: ITaskProvider, type: string): IDisposable; registerTaskSystem(scheme: string, taskSystemInfo: TaskSystemInfo): void; + registerSupportedExecutions(custom?: boolean, shell?: boolean, process?: boolean): void; setJsonTasksSupported(areSuppored: Promise): void; extensionCallbackTaskComplete(task: Task, result: number | undefined): Promise; diff --git a/src/vs/workbench/contrib/tasks/common/taskSystem.ts b/src/vs/workbench/contrib/tasks/common/taskSystem.ts index f2bd55e51f..8b2d108b10 100644 --- a/src/vs/workbench/contrib/tasks/common/taskSystem.ts +++ b/src/vs/workbench/contrib/tasks/common/taskSystem.ts @@ -119,7 +119,7 @@ export interface TaskSystemInfo { platform: Platform; context: any; uriProvider: (this: void, path: string) => URI; - resolveVariables(workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise; + resolveVariables(workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet, target: ConfigurationTarget): Promise; getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }>; findExecutable(command: string, cwd?: string, paths?: string[]): Promise; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 867478aca2..83941e5eec 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -7,7 +7,6 @@ import { Action, IAction } from 'vs/base/common/actions'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { TERMINAL_VIEW_ID, ITerminalConfigHelper, TitleEventSource, TERMINAL_COMMAND_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS } from 'vs/workbench/contrib/terminal/common/terminal'; -import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -39,6 +38,7 @@ import { localize } from 'vs/nls'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITerminalContributionService } from 'vs/workbench/contrib/terminal/common/terminalExtensionPoints'; +import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; async function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITerminalInstance, folders?: IWorkspaceFolder[], commandService?: ICommandService): Promise { switch (configHelper.config.splitCwd) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index bf910299ce..57b8b4c058 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -6,8 +6,7 @@ import * as dom from 'vs/base/browser/dom'; import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; -import { Action, IAction } from 'vs/base/common/actions'; -import { IActionViewItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Action, IAction, Separator, IActionViewItem } from 'vs/base/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 6ad5b03cb8..93acec8ef7 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/timelinePane'; import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; -import { IAction, ActionRunner } from 'vs/base/common/actions'; +import { IAction, ActionRunner, IActionViewItemProvider } from 'vs/base/common/actions'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { fromNow } from 'vs/base/common/date'; import { debounce } from 'vs/base/common/decorators'; @@ -36,10 +36,11 @@ import { IThemeService, LIGHT, ThemeIcon } from 'vs/platform/theme/common/themeS import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IActionViewItemProvider, ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { MenuItemAction, IMenuService, MenuId, registerAction2, Action2, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { MenuEntryActionViewItem, createAndFillInContextMenuActions, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { MenuItemAction, IMenuService, MenuId, registerAction2, Action2, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; const ItemHeight = 22; @@ -1092,9 +1093,15 @@ class TimelineTreeRenderer implements ITreeRenderer action instanceof MenuItemAction - ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) - : undefined; + this.actionViewItemProvider = (action: IAction) => { + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); + } + + return undefined; + }; } private uri: URI | undefined; @@ -1239,7 +1246,6 @@ class TimelinePaneCommands extends Disposable { createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g)); menu.dispose(); - scoped.dispose(); return result; } diff --git a/src/vs/workbench/contrib/views/browser/treeView.ts b/src/vs/workbench/contrib/views/browser/treeView.ts index 9616248388..7b63dae1f7 100644 --- a/src/vs/workbench/contrib/views/browser/treeView.ts +++ b/src/vs/workbench/contrib/views/browser/treeView.ts @@ -7,11 +7,11 @@ import 'vs/css!./media/views'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IAction, ActionRunner } from 'vs/base/common/actions'; +import { IAction, ActionRunner, IActionViewItemProvider } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IMenuService, MenuId, MenuItemAction, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; -import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuService, MenuId, MenuItemAction, registerAction2, Action2, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuEntryActionViewItem, createAndFillInContextMenuActions, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, IViewDescriptorService, ViewContainer, ViewContainerLocation, ResolvableTreeItem } from 'vs/workbench/common/views'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -21,7 +21,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { ICommandService } from 'vs/platform/commands/common/commands'; import * as DOM from 'vs/base/browser/dom'; import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; -import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { URI } from 'vs/base/common/uri'; import { dirname, basename } from 'vs/base/common/resources'; import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService'; @@ -39,6 +39,7 @@ import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { IHoverService, IHoverOptions } from 'vs/workbench/services/hover/browser/hover'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; class Root implements ITreeItem { label = { label: 'root' }; @@ -349,7 +350,15 @@ export class TreeView extends Disposable implements ITreeView { } private createTree() { - const actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) : undefined; + const actionViewItemProvider = (action: IAction) => { + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(MenuEntryActionViewItem, action); + } else if (action instanceof SubmenuItemAction) { + return this.instantiationService.createInstance(SubmenuEntryActionViewItem, action); + } + + return undefined; + }; const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id)); this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: this.id }, () => task)); @@ -980,7 +989,6 @@ class TreeMenus extends Disposable implements IDisposable { createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g)); menu.dispose(); - contextKeyService.dispose(); return result; } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index d98154b2b9..0d9d24db26 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -8,8 +8,7 @@ import { URI } from 'vs/base/common/uri'; import * as errors from 'vs/base/common/errors'; import { equals } from 'vs/base/common/objects'; import * as DOM from 'vs/base/browser/dom'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, Separator } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { toResource, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor'; import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 8c8d3b6caf..ffde5377a2 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -527,12 +527,13 @@ export class WorkspaceService extends Disposable implements IConfigurationServic private registerConfigurationSchemas(): void { if (this.workspace) { const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); + const defaultSettingsSchema: IJSONSchema = { additionalProperties: true, allowTrailingCommas: true, allowComments: true }; const allSettingsSchema: IJSONSchema = { properties: allSettings.properties, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true }; const userSettingsSchema: IJSONSchema = this.remoteUserConfiguration ? { properties: { ...applicationSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true } : allSettingsSchema; const machineSettingsSchema: IJSONSchema = { properties: { ...machineSettings.properties, ...machineOverridableSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true }; const workspaceSettingsSchema: IJSONSchema = { properties: { ...machineOverridableSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true }; - jsonRegistry.registerSchema(defaultSettingsSchemaId, allSettingsSchema); + jsonRegistry.registerSchema(defaultSettingsSchemaId, defaultSettingsSchema); jsonRegistry.registerSchema(userSettingsSchemaId, userSettingsSchema); jsonRegistry.registerSchema(machineSettingsSchemaId, machineSettingsSchema); diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index b20d4f37bf..b6115d7e73 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -3,8 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction, IActionRunner, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction, IActionRunner, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, Separator, SubmenuAction } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -13,7 +12,7 @@ import { getZoomFactor } from 'vs/base/browser/browser'; import { unmnemonicLabel } from 'vs/base/common/labels'; import { Event, Emitter } from 'vs/base/common/event'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IContextMenuDelegate, ContextSubMenu, IContextMenuEvent } from 'vs/base/browser/contextmenu'; +import { IContextMenuDelegate, IContextMenuEvent } from 'vs/base/browser/contextmenu'; import { once } from 'vs/base/common/functional'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu'; @@ -26,6 +25,7 @@ import { ContextMenuService as HTMLContextMenuService } from 'vs/platform/contex import { IThemeService } from 'vs/platform/theme/common/themeService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { stripCodicons } from 'vs/base/common/codicons'; +import { coalesce } from 'vs/base/common/arrays'; export class ContextMenuService extends Disposable implements IContextMenuService { @@ -124,24 +124,28 @@ class NativeContextMenuService extends Disposable implements IContextMenuService } } - private createMenu(delegate: IContextMenuDelegate, entries: ReadonlyArray, onHide: () => void): IContextMenuItem[] { + private createMenu(delegate: IContextMenuDelegate, entries: IAction[], onHide: () => void, submenuIds = new Set()): IContextMenuItem[] { const actionRunner = delegate.actionRunner || new ActionRunner(); - - return entries.map(entry => this.createMenuItem(delegate, entry, actionRunner, onHide)); + return coalesce(entries.map(entry => this.createMenuItem(delegate, entry, actionRunner, onHide, submenuIds))); } - private createMenuItem(delegate: IContextMenuDelegate, entry: IAction | ContextSubMenu, actionRunner: IActionRunner, onHide: () => void): IContextMenuItem { - + private createMenuItem(delegate: IContextMenuDelegate, entry: IAction, actionRunner: IActionRunner, onHide: () => void, submenuIds: Set): IContextMenuItem | undefined { // Separator if (entry instanceof Separator) { return { type: 'separator' }; } // Submenu - if (entry instanceof ContextSubMenu) { + if (entry instanceof SubmenuAction) { + if (submenuIds.has(entry.id)) { + console.warn(`Found submenu cycle: ${entry.id}`); + return undefined; + } + + const actions = Array.isArray(entry.actions) ? entry.actions : entry.actions(); return { label: unmnemonicLabel(stripCodicons(entry.label)).trim(), - submenu: this.createMenu(delegate, entry.entries, onHide) + submenu: this.createMenu(delegate, actions, onHide, new Set([...submenuIds, entry.id])) }; } diff --git a/src/vs/workbench/services/experiment/common/experimentService.ts b/src/vs/workbench/services/experiment/common/experimentService.ts new file mode 100644 index 0000000000..cabc0c28c0 --- /dev/null +++ b/src/vs/workbench/services/experiment/common/experimentService.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const ITASExperimentService = createDecorator('TASExperimentService'); + +export interface ITASExperimentService { + readonly _serviceBrand: undefined; + getTreatment(name: string): Promise; +} diff --git a/src/vs/workbench/services/experiment/electron-browser/experimentService.ts b/src/vs/workbench/services/experiment/electron-browser/experimentService.ts new file mode 100644 index 0000000000..3ed6f8f898 --- /dev/null +++ b/src/vs/workbench/services/experiment/electron-browser/experimentService.ts @@ -0,0 +1,217 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as platform from 'vs/base/common/platform'; +import { IKeyValueStorage, IExperimentationTelemetry, IExperimentationFilterProvider, ExperimentationService as TASClient } from 'tas-client'; +import { MementoObject, Memento } from 'vs/workbench/common/memento'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { ITelemetryData } from 'vs/base/common/actions'; +import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +const storageKey = 'VSCode.ABExp.FeatureData'; +const refetchInterval = 1000 * 60 * 30; // By default it's set up to 30 minutes. + +class MementoKeyValueStorage implements IKeyValueStorage { + constructor(private mementoObj: MementoObject) { } + + async getValue(key: string, defaultValue?: T | undefined): Promise { + const value = await this.mementoObj[key]; + return value || defaultValue; + } + + setValue(key: string, value: T): void { + this.mementoObj[key] = value; + } +} + +class ExperimentServiceTelemetry implements IExperimentationTelemetry { + constructor(private telemetryService: ITelemetryService) { } + + // __GDPR__COMMON__ "VSCode.ABExp.Features" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + // __GDPR__COMMON__ "abexp.assignmentcontext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + setSharedProperty(name: string, value: string): void { + this.telemetryService.setExperimentProperty(name, value); + } + + postEvent(eventName: string, props: Map): void { + const data: ITelemetryData = {}; + for (const [key, value] of props.entries()) { + data[key] = value; + } + + /* __GDPR__ + "query-expfeature" : { + "ABExp.queriedFeature": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog(eventName, data); + } +} + +class ExperimentServiceFilterProvider implements IExperimentationFilterProvider { + constructor( + private version: string, + private appName: string, + private machineId: string, + private targetPopulation: TargetPopulation + ) { } + + getFilterValue(filter: string): string | null { + switch (filter) { + case Filters.ApplicationVersion: + return this.version; // productService.version + case Filters.Build: + return this.appName; // productService.nameLong + case Filters.ClientId: + return this.machineId; + case Filters.Language: + return platform.language; + case Filters.ExtensionName: + return 'vscode-core'; // always return vscode-core for exp service + case Filters.TargetPopulation: + return this.targetPopulation; + default: + return ''; + } + } + + getFilters(): Map { + let filters: Map = new Map(); + let filterValues = Object.values(Filters); + for (let value of filterValues) { + filters.set(value, this.getFilterValue(value)); + } + + return filters; + } +} + +/* +Based upon the official VSCode currently existing filters in the +ExP backend for the VSCode cluster. +https://experimentation.visualstudio.com/Analysis%20and%20Experimentation/_git/AnE.ExP.TAS.TachyonHost.Configuration?path=%2FConfigurations%2Fvscode%2Fvscode.json&version=GBmaster +"X-MSEdge-Market": "detection.market", +"X-FD-Corpnet": "detection.corpnet", +"X-VSCode–AppVersion": "appversion", +"X-VSCode-Build": "build", +"X-MSEdge-ClientId": "clientid", +"X-VSCode-ExtensionName": "extensionname", +"X-VSCode-TargetPopulation": "targetpopulation", +"X-VSCode-Language": "language" +*/ + +enum Filters { + /** + * The market in which the extension is distributed. + */ + Market = 'X-MSEdge-Market', + + /** + * The corporation network. + */ + CorpNet = 'X-FD-Corpnet', + + /** + * Version of the application which uses experimentation service. + */ + ApplicationVersion = 'X-VSCode-AppVersion', + + /** + * Insiders vs Stable. + */ + Build = 'X-VSCode-Build', + + /** + * Client Id which is used as primary unit for the experimentation. + */ + ClientId = 'X-MSEdge-ClientId', + + /** + * Extension header. + */ + ExtensionName = 'X-VSCode-ExtensionName', + + /** + * The language in use by VS Code + */ + Language = 'X-VSCode-Language', + + /** + * The target population. + * This is used to separate internal, early preview, GA, etc. + */ + TargetPopulation = 'X-VSCode-TargetPopulation', +} + +enum TargetPopulation { + Team = 'team', + Internal = 'internal', + Insiders = 'insider', + Public = 'public', +} + +export class ExperimentService implements ITASExperimentService { + _serviceBrand: undefined; + private tasClient: Promise | undefined; + private static MEMENTO_ID = 'experiment.service.memento'; + + constructor( + @IProductService private productService: IProductService, + @ITelemetryService private telemetryService: ITelemetryService, + @IStorageService private storageService: IStorageService + ) { + + if (this.productService.tasConfig) { + this.tasClient = this.setupTASClient(); + } + } + + async getTreatment(name: string): Promise { + if (!this.tasClient) { + return undefined; + } + + return (await this.tasClient).getTreatmentVariable('vscode', name); + } + + private async setupTASClient(): Promise { + const telemetryInfo = await this.telemetryService.getTelemetryInfo(); + const targetPopulation = telemetryInfo.msftInternal ? TargetPopulation.Internal : (this.productService.quality === 'stable' ? TargetPopulation.Public : TargetPopulation.Insiders); + const machineId = telemetryInfo.machineId; + const filterProvider = new ExperimentServiceFilterProvider( + this.productService.version, + this.productService.nameLong, + machineId, + targetPopulation + ); + + const memento = new Memento(ExperimentService.MEMENTO_ID, this.storageService); + const keyValueStorage = new MementoKeyValueStorage(memento.getMemento(StorageScope.GLOBAL)); + + const telemetry = new ExperimentServiceTelemetry(this.telemetryService); + + const tasConfig = this.productService.tasConfig!; + const tasClient = new TASClient({ + filterProviders: [filterProvider], + telemetry: telemetry, + storageKey: storageKey, + keyValueStorage: keyValueStorage, + featuresTelemetryPropertyName: tasConfig.featuresTelemetryPropertyName, + assignmentContextTelemetryPropertyName: tasConfig.assignmentContextTelemetryPropertyName, + telemetryEventName: tasConfig.telemetryEventName, + endpoint: tasConfig.endpoint, + refetchInterval: refetchInterval, + }); + + await tasClient.initializePromise; + return tasClient; + } +} + +registerSingleton(ITASExperimentService, ExperimentService, false); + diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index 1c2333a2b2..89031ed2c8 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -65,6 +65,10 @@ export class TelemetryService extends Disposable implements ITelemetryService { return this.impl.setEnabled(value); } + setExperimentProperty(name: string, value: string): void { + return this.impl.setExperimentProperty(name, value); + } + get isOptedIn(): boolean { return this.impl.isOptedIn; } diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index f38a6c7180..6b951d787c 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -57,6 +57,10 @@ export class TelemetryService extends Disposable implements ITelemetryService { return this.impl.setEnabled(value); } + setExperimentProperty(name: string, value: string): void { + return this.impl.setExperimentProperty(name, value); + } + get isOptedIn(): boolean { return this.impl.isOptedIn; } diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index 3cc3269541..15008fb757 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -176,6 +176,9 @@ class TestTelemetryService implements ITelemetryService { public setEnabled(value: boolean): void { } + public setExperimentProperty(name: string, value: string): void { + } + public publicLog(eventName: string, data?: any): Promise { const event = { name: eventName, data: data }; this.events.push(event); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 66f8c145c2..7e718bc8a6 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -58,6 +58,7 @@ import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncAccountS import 'vs/workbench/services/sharedProcess/electron-browser/sharedProcessService'; import 'vs/workbench/services/localizations/electron-browser/localizationsService'; import 'vs/workbench/services/path/electron-browser/pathService'; +import 'vs/workbench/services/experiment/electron-browser/experimentService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; diff --git a/yarn.lock b/yarn.lock index 3993675ed7..19f4263038 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1189,6 +1189,13 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== +axios@^0.19.0: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" + azure-storage@^2.10.2: version "2.10.2" resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-2.10.2.tgz#3bcabdbf10e72fd0990db81116e49023c4a675b6" @@ -2480,7 +2487,7 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@3.1.0: +debug@3.1.0, debug@=3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== @@ -3680,6 +3687,13 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2: inherits "^2.0.1" readable-stream "^2.0.4" +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + for-in@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.5.tgz#007374e2b6d5c67420a1479bdb75a04872b738c4" @@ -9249,6 +9263,13 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.2" +tas-client@^0.0.950: + version "0.0.950" + resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.0.950.tgz#0fadc684721d5bc6d6af03b09e1ff5a83a5186fc" + integrity sha512-AvCNjvfouxJyKln+TsobOBO5KmXklL9+FlxrEPlIgaixy1TxCC2v2Vs/MflCiyHlGl+BeIStP4oAVPqo5c0pIA== + dependencies: + axios "^0.19.0" + temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d"