configuration.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import * as browser from '../../../base/browser/browser.js';
  6. import { Emitter } from '../../../base/common/event.js';
  7. import { Disposable } from '../../../base/common/lifecycle.js';
  8. import * as platform from '../../../base/common/platform.js';
  9. import { CharWidthRequest, readCharWidths } from './charWidthReader.js';
  10. import { ElementSizeObserver } from './elementSizeObserver.js';
  11. import { CommonEditorConfiguration } from '../../common/config/commonEditorConfig.js';
  12. import { EditorFontLigatures, EDITOR_FONT_DEFAULTS } from '../../common/config/editorOptions.js';
  13. import { FontInfo } from '../../common/config/fontInfo.js';
  14. class CSSBasedConfigurationCache {
  15. constructor() {
  16. this._keys = Object.create(null);
  17. this._values = Object.create(null);
  18. }
  19. has(item) {
  20. const itemId = item.getId();
  21. return !!this._values[itemId];
  22. }
  23. get(item) {
  24. const itemId = item.getId();
  25. return this._values[itemId];
  26. }
  27. put(item, value) {
  28. const itemId = item.getId();
  29. this._keys[itemId] = item;
  30. this._values[itemId] = value;
  31. }
  32. remove(item) {
  33. const itemId = item.getId();
  34. delete this._keys[itemId];
  35. delete this._values[itemId];
  36. }
  37. getValues() {
  38. return Object.keys(this._keys).map(id => this._values[id]);
  39. }
  40. }
  41. export function clearAllFontInfos() {
  42. CSSBasedConfiguration.INSTANCE.clearCache();
  43. }
  44. class CSSBasedConfiguration extends Disposable {
  45. constructor() {
  46. super();
  47. this._onDidChange = this._register(new Emitter());
  48. this.onDidChange = this._onDidChange.event;
  49. this._cache = new CSSBasedConfigurationCache();
  50. this._evictUntrustedReadingsTimeout = -1;
  51. }
  52. dispose() {
  53. if (this._evictUntrustedReadingsTimeout !== -1) {
  54. clearTimeout(this._evictUntrustedReadingsTimeout);
  55. this._evictUntrustedReadingsTimeout = -1;
  56. }
  57. super.dispose();
  58. }
  59. clearCache() {
  60. this._cache = new CSSBasedConfigurationCache();
  61. this._onDidChange.fire();
  62. }
  63. _writeToCache(item, value) {
  64. this._cache.put(item, value);
  65. if (!value.isTrusted && this._evictUntrustedReadingsTimeout === -1) {
  66. // Try reading again after some time
  67. this._evictUntrustedReadingsTimeout = setTimeout(() => {
  68. this._evictUntrustedReadingsTimeout = -1;
  69. this._evictUntrustedReadings();
  70. }, 5000);
  71. }
  72. }
  73. _evictUntrustedReadings() {
  74. const values = this._cache.getValues();
  75. let somethingRemoved = false;
  76. for (const item of values) {
  77. if (!item.isTrusted) {
  78. somethingRemoved = true;
  79. this._cache.remove(item);
  80. }
  81. }
  82. if (somethingRemoved) {
  83. this._onDidChange.fire();
  84. }
  85. }
  86. readConfiguration(bareFontInfo) {
  87. if (!this._cache.has(bareFontInfo)) {
  88. let readConfig = CSSBasedConfiguration._actualReadConfiguration(bareFontInfo);
  89. if (readConfig.typicalHalfwidthCharacterWidth <= 2 || readConfig.typicalFullwidthCharacterWidth <= 2 || readConfig.spaceWidth <= 2 || readConfig.maxDigitWidth <= 2) {
  90. // Hey, it's Bug 14341 ... we couldn't read
  91. readConfig = new FontInfo({
  92. zoomLevel: browser.getZoomLevel(),
  93. pixelRatio: browser.getPixelRatio(),
  94. fontFamily: readConfig.fontFamily,
  95. fontWeight: readConfig.fontWeight,
  96. fontSize: readConfig.fontSize,
  97. fontFeatureSettings: readConfig.fontFeatureSettings,
  98. lineHeight: readConfig.lineHeight,
  99. letterSpacing: readConfig.letterSpacing,
  100. isMonospace: readConfig.isMonospace,
  101. typicalHalfwidthCharacterWidth: Math.max(readConfig.typicalHalfwidthCharacterWidth, 5),
  102. typicalFullwidthCharacterWidth: Math.max(readConfig.typicalFullwidthCharacterWidth, 5),
  103. canUseHalfwidthRightwardsArrow: readConfig.canUseHalfwidthRightwardsArrow,
  104. spaceWidth: Math.max(readConfig.spaceWidth, 5),
  105. middotWidth: Math.max(readConfig.middotWidth, 5),
  106. wsmiddotWidth: Math.max(readConfig.wsmiddotWidth, 5),
  107. maxDigitWidth: Math.max(readConfig.maxDigitWidth, 5),
  108. }, false);
  109. }
  110. this._writeToCache(bareFontInfo, readConfig);
  111. }
  112. return this._cache.get(bareFontInfo);
  113. }
  114. static createRequest(chr, type, all, monospace) {
  115. const result = new CharWidthRequest(chr, type);
  116. all.push(result);
  117. if (monospace) {
  118. monospace.push(result);
  119. }
  120. return result;
  121. }
  122. static _actualReadConfiguration(bareFontInfo) {
  123. const all = [];
  124. const monospace = [];
  125. const typicalHalfwidthCharacter = this.createRequest('n', 0 /* Regular */, all, monospace);
  126. const typicalFullwidthCharacter = this.createRequest('\uff4d', 0 /* Regular */, all, null);
  127. const space = this.createRequest(' ', 0 /* Regular */, all, monospace);
  128. const digit0 = this.createRequest('0', 0 /* Regular */, all, monospace);
  129. const digit1 = this.createRequest('1', 0 /* Regular */, all, monospace);
  130. const digit2 = this.createRequest('2', 0 /* Regular */, all, monospace);
  131. const digit3 = this.createRequest('3', 0 /* Regular */, all, monospace);
  132. const digit4 = this.createRequest('4', 0 /* Regular */, all, monospace);
  133. const digit5 = this.createRequest('5', 0 /* Regular */, all, monospace);
  134. const digit6 = this.createRequest('6', 0 /* Regular */, all, monospace);
  135. const digit7 = this.createRequest('7', 0 /* Regular */, all, monospace);
  136. const digit8 = this.createRequest('8', 0 /* Regular */, all, monospace);
  137. const digit9 = this.createRequest('9', 0 /* Regular */, all, monospace);
  138. // monospace test: used for whitespace rendering
  139. const rightwardsArrow = this.createRequest('→', 0 /* Regular */, all, monospace);
  140. const halfwidthRightwardsArrow = this.createRequest('→', 0 /* Regular */, all, null);
  141. // U+00B7 - MIDDLE DOT
  142. const middot = this.createRequest('·', 0 /* Regular */, all, monospace);
  143. // U+2E31 - WORD SEPARATOR MIDDLE DOT
  144. const wsmiddotWidth = this.createRequest(String.fromCharCode(0x2E31), 0 /* Regular */, all, null);
  145. // monospace test: some characters
  146. const monospaceTestChars = '|/-_ilm%';
  147. for (let i = 0, len = monospaceTestChars.length; i < len; i++) {
  148. this.createRequest(monospaceTestChars.charAt(i), 0 /* Regular */, all, monospace);
  149. this.createRequest(monospaceTestChars.charAt(i), 1 /* Italic */, all, monospace);
  150. this.createRequest(monospaceTestChars.charAt(i), 2 /* Bold */, all, monospace);
  151. }
  152. readCharWidths(bareFontInfo, all);
  153. const maxDigitWidth = Math.max(digit0.width, digit1.width, digit2.width, digit3.width, digit4.width, digit5.width, digit6.width, digit7.width, digit8.width, digit9.width);
  154. let isMonospace = (bareFontInfo.fontFeatureSettings === EditorFontLigatures.OFF);
  155. const referenceWidth = monospace[0].width;
  156. for (let i = 1, len = monospace.length; isMonospace && i < len; i++) {
  157. const diff = referenceWidth - monospace[i].width;
  158. if (diff < -0.001 || diff > 0.001) {
  159. isMonospace = false;
  160. break;
  161. }
  162. }
  163. let canUseHalfwidthRightwardsArrow = true;
  164. if (isMonospace && halfwidthRightwardsArrow.width !== referenceWidth) {
  165. // using a halfwidth rightwards arrow would break monospace...
  166. canUseHalfwidthRightwardsArrow = false;
  167. }
  168. if (halfwidthRightwardsArrow.width > rightwardsArrow.width) {
  169. // using a halfwidth rightwards arrow would paint a larger arrow than a regular rightwards arrow
  170. canUseHalfwidthRightwardsArrow = false;
  171. }
  172. // let's trust the zoom level only 2s after it was changed.
  173. const canTrustBrowserZoomLevel = (browser.getTimeSinceLastZoomLevelChanged() > 2000);
  174. return new FontInfo({
  175. zoomLevel: browser.getZoomLevel(),
  176. pixelRatio: browser.getPixelRatio(),
  177. fontFamily: bareFontInfo.fontFamily,
  178. fontWeight: bareFontInfo.fontWeight,
  179. fontSize: bareFontInfo.fontSize,
  180. fontFeatureSettings: bareFontInfo.fontFeatureSettings,
  181. lineHeight: bareFontInfo.lineHeight,
  182. letterSpacing: bareFontInfo.letterSpacing,
  183. isMonospace: isMonospace,
  184. typicalHalfwidthCharacterWidth: typicalHalfwidthCharacter.width,
  185. typicalFullwidthCharacterWidth: typicalFullwidthCharacter.width,
  186. canUseHalfwidthRightwardsArrow: canUseHalfwidthRightwardsArrow,
  187. spaceWidth: space.width,
  188. middotWidth: middot.width,
  189. wsmiddotWidth: wsmiddotWidth.width,
  190. maxDigitWidth: maxDigitWidth
  191. }, canTrustBrowserZoomLevel);
  192. }
  193. }
  194. CSSBasedConfiguration.INSTANCE = new CSSBasedConfiguration();
  195. export class Configuration extends CommonEditorConfiguration {
  196. constructor(isSimpleWidget, options, referenceDomElement = null, accessibilityService) {
  197. super(isSimpleWidget, options);
  198. this.accessibilityService = accessibilityService;
  199. this._elementSizeObserver = this._register(new ElementSizeObserver(referenceDomElement, options.dimension, () => this._recomputeOptions()));
  200. this._register(CSSBasedConfiguration.INSTANCE.onDidChange(() => this._recomputeOptions()));
  201. if (this._validatedOptions.get(10 /* automaticLayout */)) {
  202. this._elementSizeObserver.startObserving();
  203. }
  204. this._register(browser.onDidChangeZoomLevel(_ => this._recomputeOptions()));
  205. this._register(this.accessibilityService.onDidChangeScreenReaderOptimized(() => this._recomputeOptions()));
  206. this._recomputeOptions();
  207. }
  208. static applyFontInfoSlow(domNode, fontInfo) {
  209. domNode.style.fontFamily = fontInfo.getMassagedFontFamily(browser.isSafari ? EDITOR_FONT_DEFAULTS.fontFamily : null);
  210. domNode.style.fontWeight = fontInfo.fontWeight;
  211. domNode.style.fontSize = fontInfo.fontSize + 'px';
  212. domNode.style.fontFeatureSettings = fontInfo.fontFeatureSettings;
  213. domNode.style.lineHeight = fontInfo.lineHeight + 'px';
  214. domNode.style.letterSpacing = fontInfo.letterSpacing + 'px';
  215. }
  216. static applyFontInfo(domNode, fontInfo) {
  217. domNode.setFontFamily(fontInfo.getMassagedFontFamily(browser.isSafari ? EDITOR_FONT_DEFAULTS.fontFamily : null));
  218. domNode.setFontWeight(fontInfo.fontWeight);
  219. domNode.setFontSize(fontInfo.fontSize);
  220. domNode.setFontFeatureSettings(fontInfo.fontFeatureSettings);
  221. domNode.setLineHeight(fontInfo.lineHeight);
  222. domNode.setLetterSpacing(fontInfo.letterSpacing);
  223. }
  224. observeReferenceElement(dimension) {
  225. this._elementSizeObserver.observe(dimension);
  226. }
  227. updatePixelRatio() {
  228. this._recomputeOptions();
  229. }
  230. static _getExtraEditorClassName() {
  231. let extra = '';
  232. if (!browser.isSafari && !browser.isWebkitWebView) {
  233. // Use user-select: none in all browsers except Safari and native macOS WebView
  234. extra += 'no-user-select ';
  235. }
  236. if (browser.isSafari) {
  237. // See https://github.com/microsoft/vscode/issues/108822
  238. extra += 'no-minimap-shadow ';
  239. }
  240. if (platform.isMacintosh) {
  241. extra += 'mac ';
  242. }
  243. return extra;
  244. }
  245. _getEnvConfiguration() {
  246. return {
  247. extraEditorClassName: Configuration._getExtraEditorClassName(),
  248. outerWidth: this._elementSizeObserver.getWidth(),
  249. outerHeight: this._elementSizeObserver.getHeight(),
  250. emptySelectionClipboard: browser.isWebKit || browser.isFirefox,
  251. pixelRatio: browser.getPixelRatio(),
  252. zoomLevel: browser.getZoomLevel(),
  253. accessibilitySupport: (this.accessibilityService.isScreenReaderOptimized()
  254. ? 2 /* Enabled */
  255. : this.accessibilityService.getAccessibilitySupport())
  256. };
  257. }
  258. readConfiguration(bareFontInfo) {
  259. return CSSBasedConfiguration.INSTANCE.readConfiguration(bareFontInfo);
  260. }
  261. }