diff --git a/patches/display-language.patch b/patches/display-language.patch index 95c9a2197..2eea9fe80 100644 --- a/patches/display-language.patch +++ b/patches/display-language.patch @@ -1,139 +1,3 @@ -Index: sagemaker-code-editor/vscode/src/vs/base/common/platform.ts -=================================================================== ---- sagemaker-code-editor.orig/vscode/src/vs/base/common/platform.ts -+++ sagemaker-code-editor/vscode/src/vs/base/common/platform.ts -@@ -2,9 +2,6 @@ - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -- --import * as nls from '../../nls.js'; -- - export const LANGUAGE_DEFAULT = 'en'; - - let _isWindows = false; -@@ -23,6 +20,13 @@ - let _translationsConfigFile: string | undefined = undefined; - let _userAgent: string | undefined = undefined; - -+interface NLSConfig { -+ locale: string; -+ osLocale: string; -+ availableLanguages: { [key: string]: string }; -+ _translationsConfigFile: string; -+} -+ - export interface IProcessEnvironment { - [key: string]: string | undefined; - } -@@ -83,15 +80,16 @@ if (typeof nodeProcess === 'obj - const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG']; - if (rawNlsConfig) { - try { -- const nlsConfig: nls.INLSConfiguration = JSON.parse(rawNlsConfig); -+ const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig); -+ const resolved = nlsConfig.availableLanguages['*']; -- _locale = nlsConfig.userLocale; -+ _locale = nlsConfig.locale; - _platformLocale = nlsConfig.osLocale; -- _language = nlsConfig.resolvedLanguage || LANGUAGE_DEFAULT; -+ _language = resolved ? resolved : LANGUAGE_DEFAULT; -- _translationsConfigFile = nlsConfig.languagePack?.translationsConfigFile; -+ _translationsConfigFile = nlsConfig._translationsConfigFile; - } catch (e) { - } - } - _isNative = true; - } - - // Web environment -@@ -104,8 +102,20 @@ else if (typeof navigator === 'object' & - _isMobile = _userAgent?.indexOf('Mobi') >= 0; - _isWeb = true; -- _language = nls.getNLSLanguage() || LANGUAGE_DEFAULT; -- _locale = navigator.language.toLowerCase(); -- _platformLocale = _locale; -+ _locale = LANGUAGE_DEFAULT; -+ _language = _locale; -+ _platformLocale = navigator.language; -+ const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration'); -+ const rawNlsConfig = el && el.getAttribute('data-settings'); -+ if (rawNlsConfig) { -+ try { -+ const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig); -+ const resolved = nlsConfig.availableLanguages['*']; -+ _locale = nlsConfig.locale; -+ _platformLocale = nlsConfig.osLocale; -+ _language = resolved ? resolved : LANGUAGE_DEFAULT; -+ _translationsConfigFile = nlsConfig._translationsConfigFile; -+ } catch (error) { /* Oh well. */ } -+ } - } - - // Unknown environment -Index: sagemaker-code-editor/vscode/src/vs/code/browser/workbench/workbench.html -=================================================================== ---- sagemaker-code-editor.orig/vscode/src/vs/code/browser/workbench/workbench.html -+++ sagemaker-code-editor/vscode/src/vs/code/browser/workbench/workbench.html -@@ -19,6 +19,9 @@ - - - -+ -+ -+ - - - -@@ -37,6 +40,47 @@ - const baseUrl = new URL('{{WORKBENCH_WEB_BASE_URL}}', window.location.origin).toString(); - globalThis._VSCODE_FILE_ROOT = baseUrl + '/out/'; -+ -+ // Set up nls if the user is not using the default language (English) -+ const nlsConfig = {}; -+ // Normalize locale to lowercase because translationServiceUrl is case-sensitive. -+ // ref: https://github.com/microsoft/vscode/issues/187795 -+ const locale = localStorage.getItem('vscode.nls.locale') || navigator.language.toLowerCase(); -+ try { -+ nlsConfig['vs/nls'] = JSON.parse(document.getElementById("vscode-remote-nls-configuration").getAttribute("data-settings")) -+ if (nlsConfig['vs/nls']._resolvedLanguagePackCoreLocation) { -+ const bundles = Object.create(null) -+ nlsConfig['vs/nls'].loadBundle = (bundle, _language, cb) => { -+ const result = bundles[bundle] -+ if (result) { -+ return cb(undefined, result) -+ } -+ const path = nlsConfig['vs/nls']._resolvedLanguagePackCoreLocation + "/" + bundle.replace(/\//g, "!") + ".nls.json" -+ fetch(`{{WORKBENCH_WEB_BASE_URL}}/../vscode-remote-resource?path=${encodeURIComponent(path)}`) -+ .then((response) => response.json()) -+ .then((json) => { -+ bundles[bundle] = json -+ cb(undefined, json) -+ }) -+ .catch(cb) -+ } -+ } -+ } catch (error) { /* Probably fine. */ } -+ -+ require.config({ -+ baseUrl: `${baseUrl}/out`, -+ recordStats: true, -+ trustedTypesPolicy: window.trustedTypes?.createPolicy('amdLoader', { -+ createScriptURL(value) { -+ if(value.startsWith(window.location.origin)) { -+ return value; -+ } -+ throw new Error(`Invalid script url: ${value}`) -+ } -+ }), -+ paths: self.webPackagePaths, -+ ...nlsConfig -+ }); - - - Index: sagemaker-code-editor/vscode/src/vs/platform/environment/common/environmentService.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/platform/environment/common/environmentService.ts @@ -186,6 +50,139 @@ Index: sagemaker-code-editor/vscode/src/vs/platform/languagePacks/browser/langua + return this.languagePackService.getInstalledLanguages() } } +Index: sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/server/node/remoteLanguagePacks.ts ++++ sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts +@@ -3,37 +3,111 @@ + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + ++import * as path from 'path'; ++import { promises as fs } from 'fs'; + import { FileAccess } from '../../base/common/network.js'; + import { join } from '../../base/common/path.js'; + import type { INLSConfiguration } from '../../nls.js'; + import { resolveNLSConfiguration } from '../../base/node/nls.js'; +-import { Promises } from '../../base/node/pfs.js'; +-import product from '../../platform/product/common/product.js'; + + const nlsMetadataPath = join(FileAccess.asFileUri('').fsPath); +-const defaultMessagesFile = join(nlsMetadataPath, 'nls.messages.json'); + const nlsConfigurationCache = new Map>(); + + export async function getNLSConfiguration(language: string, userDataPath: string): Promise { +- if (!product.commit || !(await Promises.exists(defaultMessagesFile))) { +- return { +- userLocale: 'en', +- osLocale: 'en', +- resolvedLanguage: 'en', +- defaultMessagesFile, +- +- // NLS: below 2 are a relic from old times only used by vscode-nls and deprecated +- locale: 'en', +- availableLanguages: {} +- }; +- } +- + const cacheKey = `${language}||${userDataPath}`; + let result = nlsConfigurationCache.get(cacheKey); + if (!result) { +- result = resolveNLSConfiguration({ userLocale: language, osLocale: language, commit: product.commit, userDataPath, nlsMetadataPath }); ++ // passing a dummy commit which is required to resolve language packs ++ result = resolveNLSConfiguration({ userLocale: language, osLocale: language, commit: 'dummy_commit', userDataPath, nlsMetadataPath }); + nlsConfigurationCache.set(cacheKey, result); ++ // If the language pack does not yet exist, it defaults to English, which is ++ // then cached and you have to restart even if you then install the pack. ++ result.then((r) => { ++ if (!language.startsWith('en') && r.resolvedLanguage.startsWith('en')) { ++ nlsConfigurationCache.delete(cacheKey); ++ } ++ }) + } + + return result; + } ++ ++/** ++ * Copied from from src/main.js. ++ */ ++export const getLocaleFromConfig = async (argvResource: string): Promise => { ++ try { ++ const content = stripComments(await fs.readFile(argvResource, 'utf8')); ++ return JSON.parse(content).locale; ++ } catch (error) { ++ if (error.code !== "ENOENT") { ++ console.warn(error) ++ } ++ return 'en'; ++ } ++}; ++ ++/** ++ * Copied from from src/main.js. ++ */ ++const stripComments = (content: string): string => { ++ const regexp = /('(?:[^\\']*(?:\\.)?)*')|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g; ++ ++ return content.replace(regexp, (match, _m1, _m2, m3, m4) => { ++ // Only one of m1, m2, m3, m4 matches ++ if (m3) { ++ // A block comment. Replace with nothing ++ return ''; ++ } else if (m4) { ++ // A line comment. If it ends in \r?\n then keep it. ++ const length_1 = m4.length; ++ if (length_1 > 2 && m4[length_1 - 1] === '\n') { ++ return m4[length_1 - 2] === '\r' ? '\r\n' : '\n'; ++ } ++ else { ++ return ''; ++ } ++ } else { ++ // We match a string ++ return match; ++ } ++ }); ++}; ++ ++/** ++ * Generate translations then return a path to a JavaScript file that sets the ++ * translations into global variables. This file is loaded by the browser to ++ * set global variables that the loader uses when looking for translations. ++ * ++ * Normally, VS Code pulls these files from a CDN but we want them to be local. ++ */ ++export async function getBrowserNLSConfiguration(locale: string, userDataPath: string): Promise { ++ if (locale.startsWith('en')) { ++ return ''; // Use fallback translations. ++ } ++ ++ const nlsConfig = await getNLSConfiguration(locale, userDataPath); ++ const messagesFile = nlsConfig?.languagePack?.messagesFile; ++ const resolvedLanguage = nlsConfig?.resolvedLanguage; ++ if (!messagesFile || !resolvedLanguage) { ++ return ''; // Use fallback translations. ++ } ++ ++ const nlsFile = path.join(path.dirname(messagesFile), "nls.messages.js"); ++ try { ++ await fs.stat(nlsFile); ++ return nlsFile; // We already generated the file. ++ } catch (error) { ++ // ENOENT is fine, that just means we need to generate the file. ++ if (error.code !== 'ENOENT') { ++ throw error; ++ } ++ } ++ ++ const messages = (await fs.readFile(messagesFile)).toString(); ++ const content = `globalThis._VSCODE_NLS_MESSAGES=${messages}; ++globalThis._VSCODE_NLS_LANGUAGE=${JSON.stringify(resolvedLanguage)};` ++ await fs.writeFile(nlsFile, content, "utf-8"); ++ ++ return nlsFile; ++} Index: sagemaker-code-editor/vscode/src/vs/server/node/serverEnvironmentService.ts =================================================================== @@ -214,7 +211,7 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/serverServices.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/server/node/serverServices.ts +++ sagemaker-code-editor/vscode/src/vs/server/node/serverServices.ts -@@ -11,7 +11,7 @@ import * as path from '.. +@@ -12,7 +12,7 @@ import * as path from '.. import { IURITransformer } from '../../base/common/uriIpc.js'; import { getMachineId, getSqmMachineId, getdevDeviceId } from '../../base/node/id.js'; import { Promises } from '../../base/node/pfs.js'; @@ -223,7 +220,7 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/serverServices.ts import { ProtocolConstants } from '../../base/parts/ipc/common/ipc.net.js'; import { IConfigurationService } from '../../platform/configuration/common/configuration.js'; import { ConfigurationService } from '../../platform/configuration/common/configurationService.js'; -@@ -225,6 +225,9 @@ export async function setupServerService +@@ -255,6 +255,9 @@ export async function setupServerService const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority)); socketServer.registerChannel('extensions', channel); @@ -238,16 +235,149 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/server/node/webClientServer.ts +++ sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts -@@ -401,7 +405,7 @@ export class WebClientServer { - `frame-src 'self' https://*.vscode-cdn.net data:;`, - 'worker-src \'self\' data: blob:;', - 'style-src \'self\' \'unsafe-inline\';', -- 'connect-src \'self\' ws: wss: https://main.vscode-cdn.net http://localhost:* https://localhost:* https://login.microsoftonline.com/ https://update.code.visualstudio.com https://*.vscode-unpkg.net/ https://default.exp-tas.com/vscode/ab https://vscode-sync.trafficmanager.net https://vscode-sync-insiders.trafficmanager.net https://*.gallerycdn.vsassets.io https://marketplace.visualstudio.com https://az764295.vo.msecnd.net https://code.visualstudio.com https://*.gallery.vsassets.io https://*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com https://*.servicebus.windows.net/ https://vscode.blob.core.windows.net https://vscode.search.windows.net https://vsmarketplacebadges.dev https://vscode.download.prss.microsoft.com https://download.visualstudio.microsoft.com https://*.vscode-unpkg.net https://open-vsx.org;', -+ 'connect-src \'self\' ws: wss: https://main.vscode-cdn.net http://localhost:* https://localhost:* https://login.microsoftonline.com/ https://update.code.visualstudio.com https://*.vscode-unpkg.net/ https://default.exp-tas.com/vscode/ab https://vscode-sync.trafficmanager.net https://vscode-sync-insiders.trafficmanager.net https://*.gallerycdn.vsassets.io https://marketplace.visualstudio.com https://openvsxorg.blob.core.windows.net https://az764295.vo.msecnd.net https://code.visualstudio.com https://*.gallery.vsassets.io https://*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com https://*.servicebus.windows.net/ https://vscode.blob.core.windows.net https://vscode.search.windows.net https://vsmarketplacebadges.dev https://vscode.download.prss.microsoft.com https://download.visualstudio.microsoft.com https://*.vscode-unpkg.net https://open-vsx.org;', - 'font-src \'self\' blob:;', - 'manifest-src \'self\';' - ].join(' '); +@@ -25,6 +25,7 @@ import { URI } from '.. + import { streamToBuffer } from '../../base/common/buffer.js'; + import { IProductConfiguration } from '../../base/common/product.js'; + import { isString, Mutable } from '../../base/common/types.js'; ++import { getLocaleFromConfig, getBrowserNLSConfiguration } from '../../server/node/remoteLanguagePacks.js'; + import { CharCode } from '../../base/common/charCode.js'; + import { IExtensionManifest } from '../../platform/extensions/common/extensions.js'; + import { ICSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js'; +@@ -245,7 +246,10 @@ export class WebClientServer { + }; + + // Prefix routes with basePath for clients +- const basePath = getFirstHeader('x-forwarded-prefix') || this._basePath; ++ const proxyPath = this._environmentService.args["base-path"] || "/"; ++ const base = relativeRoot(proxyPath); ++ const vscodeBase = relativePath(proxyPath); ++ const basePath = vscodeBase || getFirstHeader("x-forwarded-prefix") || this._basePath; + + const queryConnectionToken = parsedUrl.query[connectionTokenQueryName]; + if (typeof queryConnectionToken === 'string') { +@@ -287,7 +291,7 @@ export class WebClientServer { + let remoteAuthority = ( + useTestResolver + ? 'test+test' +- : (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host) ++ : (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host || window.location.host) + ); + if (!remoteAuthority) { + return serveError(req, res, 400, `Bad request.`); +@@ -333,6 +337,7 @@ export class WebClientServer { + } : undefined; + + const productConfiguration: Partial> = { ++ rootEndpoint: base, + embedderIdentifier: 'server-distro', + extensionsGallery: this._webExtensionResourceUrlTemplate && this._productService.extensionsGallery ? { + ...this._productService.extensionsGallery, +@@ -364,7 +369,7 @@ export class WebClientServer { + const workbenchWebConfiguration = { + remoteAuthority, +- serverBasePath: basePath, ++ serverBasePath: this._basePath, + webviewEndpoint: staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', + userDataPath: this._environmentService.userDataPath, + _wrapWebWorkerExtHostInIframe, +@@ -385,22 +390,32 @@ export class WebClientServer { + }; + + const cookies = cookie.parse(req.headers.cookie || ''); +- const locale = cookies['vscode.nls.locale'] || req.headers['accept-language']?.split(',')[0]?.toLowerCase() || 'en'; ++ const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath) || cookies['vscode.nls.locale'] || req.headers['accept-language']?.split(',')[0]?.toLowerCase() || 'en'; + let WORKBENCH_NLS_BASE_URL: string | undefined; + let WORKBENCH_NLS_URL: string; + if (!locale.startsWith('en') && this._productService.nlsCoreBaseUrl) { + WORKBENCH_NLS_BASE_URL = this._productService.nlsCoreBaseUrl; + WORKBENCH_NLS_URL = `${WORKBENCH_NLS_BASE_URL}${this._productService.commit}/${this._productService.version}/${locale}/nls.messages.js`; + } else { +- WORKBENCH_NLS_URL = ''; // fallback will apply ++ try { ++ const nlsFile = await getBrowserNLSConfiguration(locale, this._environmentService.userDataPath); ++ WORKBENCH_NLS_URL = nlsFile ++ ? `${vscodeBase}/vscode-remote-resource?path=${encodeURIComponent(nlsFile)}` ++ : ''; ++ } catch (error) { ++ console.error("Failed to generate translations", error); ++ WORKBENCH_NLS_URL = ''; ++ } + } + + const values: { [key: string]: string } = { + WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration), + WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '', + WORKBENCH_WEB_BASE_URL: staticRoute, + WORKBENCH_NLS_URL, +- WORKBENCH_NLS_FALLBACK_URL: `${staticRoute}/out/nls.messages.js` ++ WORKBENCH_NLS_FALLBACK_URL: `${staticRoute}/out/nls.messages.js`, ++ BASE: base, ++ VS_BASE: vscodeBase + }; + + // DEV --------------------------------------------------------------------------------------- +@@ -618,3 +633,60 @@ export class WebClientServer { + serveError(req, res, 500, error.message); + } + } ++ ++ ++/** ++ * Remove extra slashes in a URL. ++ * ++ * This is meant to fill the job of `path.join` so you can concatenate paths and ++ * then normalize out any extra slashes. ++ * ++ * If you are using `path.join` you do not need this but note that `path` is for ++ * file system paths, not URLs. ++ */ ++export const normalizeUrlPath = (url: string, keepTrailing = false): string => { ++ return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "") ++} ++ ++/** ++ * Get the relative path that will get us to the root of the page. For each ++ * slash we need to go up a directory. Will not have a trailing slash. ++ * ++ * For example: ++ * ++ * / => . ++ * /foo => . ++ * /foo/ => ./.. ++ * /foo/bar => ./.. ++ * /foo/bar/ => ./../.. ++ * ++ * All paths must be relative in order to work behind a reverse proxy since we ++ * we do not know the base path. Anything that needs to be absolute (for ++ * example cookies) must get the base path from the frontend. ++ * ++ * All relative paths must be prefixed with the relative root to ensure they ++ * work no matter the depth at which they happen to appear. ++ * ++ * For Express `req.originalUrl` should be used as they remove the base from the ++ * standard `url` property making it impossible to get the true depth. ++ */ ++export const relativeRoot = (originalUrl: string): string => { ++ const depth = (originalUrl.split("?", 1)[0].match(/\//g) || []).length ++ return normalizeUrlPath("./" + (depth > 1 ? "../".repeat(depth - 1) : "")) ++} ++ ++/** ++ * Get the relative path to the current resource. ++ * ++ * For example: ++ * ++ * / => . ++ * /foo => ./foo ++ * /foo/ => . ++ * /foo/bar => ./bar ++ * /foo/bar/ => . ++ */ ++export const relativePath = (originalUrl: string): string => { ++ const parts = originalUrl.split("?", 1)[0].split("/") ++ return normalizeUrlPath("./" + parts[parts.length - 1]) ++} Index: sagemaker-code-editor/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -340,3 +470,71 @@ Index: sagemaker-code-editor/vscode/src/vs/workbench/services/localization/elect await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: locale }], true); return true; } + +Index: sagemaker-code-editor/vscode/build/gulpfile.reh.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/gulpfile.reh.js ++++ sagemaker-code-editor/vscode/build/gulpfile.reh.js +@@ -59,6 +59,7 @@ const serverResourceIncludes = [ + + // NLS + 'out-build/nls.messages.json', ++ 'out-build/nls.keys.json', // Required to generate translations. + + // Process monitor + 'out-build/vs/base/node/cpuUsage.sh', +Index: sagemaker-code-editor/vscode/src/vs/workbench/workbench.web.main.internal.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/workbench/workbench.web.main.internal.ts ++++ sagemaker-code-editor/vscode/src/vs/workbench/workbench.web.main.internal.ts +@@ -53,7 +53,7 @@ import './services/dialogs/browser/fileD + import './services/host/browser/browserHostService.js'; + import './services/lifecycle/browser/lifecycleService.js'; + import './services/clipboard/browser/clipboardService.js'; +-import './services/localization/browser/localeService.js'; ++import './services/localization/electron-sandbox/localeService.js'; + import './services/path/browser/pathService.js'; + import './services/themes/browser/browserHostColorSchemeService.js'; + import './services/encryption/browser/encryptionService.js'; +Index: sagemaker-code-editor/vscode/src/vs/workbench/contrib/localization/common/localizationsActions.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/workbench/contrib/localization/common/localizationsActions.ts ++++ sagemaker-code-editor/vscode/src/vs/workbench/contrib/localization/common/localizationsActions.ts +@@ -12,6 +12,7 @@ import { ServicesAccessor } from '../../ + import { ILanguagePackItem, ILanguagePackService } from '../../../../platform/languagePacks/common/languagePacks.js'; + import { ILocaleService } from '../../../services/localization/common/locale.js'; + import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; ++import { language } from '../../../../base/common/platform.js'; + + export class ConfigureDisplayLanguageAction extends Action2 { + public static readonly ID = 'workbench.action.configureLocale'; +@@ -37,6 +38,16 @@ export class ConfigureDisplayLanguageAct + + const installedLanguages = await languagePackService.getInstalledLanguages(); + ++ // Clean any existing (Current) text and add it back only to the correct locale ++ installedLanguages?.forEach(lang => { ++ if (lang.description) { ++ lang.description = lang.description.replace(/ \(Current\)$/, ''); ++ } ++ if (lang.id?.toLowerCase() === language.toLowerCase()) { ++ lang.description = (lang.description || '') + localize('currentDisplayLanguage', " (Current)"); ++ } ++ }); ++ + const disposables = new DisposableStore(); + const qp = disposables.add(quickInputService.createQuickPick({ useSeparators: true })); + qp.matchOnDescription = true; + +Index: sagemaker-code-editor/vscode/src/vs/base/common/product.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/base/common/product.ts ++++ sagemaker-code-editor/vscode/src/vs/base/common/product.ts +@@ -56,6 +56,7 @@ export type ExtensionVirtualWorkspaceSup + }; + + export interface IProductConfiguration { ++ readonly rootEndpoint?: string; + readonly version: string; + readonly date?: string; + readonly quality?: string; \ No newline at end of file diff --git a/patches/sagemaker-idle-extension.patch b/patches/sagemaker-idle-extension.patch index f475f6071..3a1966b34 100644 --- a/patches/sagemaker-idle-extension.patch +++ b/patches/sagemaker-idle-extension.patch @@ -137,27 +137,19 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/src/exte =================================================================== --- /dev/null +++ sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/src/extension.ts -@@ -0,0 +1,112 @@ +@@ -0,0 +1,58 @@ +import * as vscode from "vscode"; +import * as fs from "fs"; -+import { join } from "path"; ++import * as path from "path"; + -+let idleFilePath: string -+let terminalActivityInterval: NodeJS.Timeout | undefined -+const LOG_PREFIX = "[sagemaker-idle-extension]" -+const CHECK_INTERVAL = 60000; // 60 seconds interval ++let idleFilePath: string; + +export function activate(context: vscode.ExtensionContext) { + initializeIdleFilePath(); + registerEventListeners(context); -+ startMonitoringTerminalActivity(); +} + -+export function deactivate() { -+ if(terminalActivityInterval) { -+ clearInterval(terminalActivityInterval) -+ } -+} ++export function deactivate() {} + +/** + * Initializes the file path where the idle timestamp will be stored. @@ -165,10 +157,10 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/src/exte + */ +function initializeIdleFilePath() { + const tmpDirectory = "/tmp/"; -+ idleFilePath = join(tmpDirectory, ".sagemaker-last-active-timestamp"); ++ idleFilePath = path.join(tmpDirectory, ".sagemaker-last-active-timestamp"); + + // Set initial lastActivetimestamp -+ updateLastActivityTimestamp() ++ updateLastActivityTimestamp(); +} + +/** @@ -197,52 +189,6 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/src/exte +} + +/** -+ * Starts monitoring terminal activity by setting an interval to check for activity in the /dev/pts directory. -+ */ -+const startMonitoringTerminalActivity = () => { -+ terminalActivityInterval = setInterval(checkTerminalActivity, CHECK_INTERVAL); -+}; -+ -+ -+/** -+ * Checks for terminal activity by reading the /dev/pts directory and comparing modification times of the files. -+ * -+ * The /dev/pts directory is used in Unix-like operating systems to represent pseudo-terminal (PTY) devices. -+ * Each active terminal session is assigned a PTY device. These devices are represented as files within the /dev/pts directory. -+ * When a terminal session has activity, such as when a user inputs commands or output is written to the terminal, -+ * the modification time (mtime) of the corresponding PTY device file is updated. By monitoring the modification -+ * times of the files in the /dev/pts directory, we can detect terminal activity. -+ * -+ * If activity is detected (i.e., if any PTY device file was modified within the CHECK_INTERVAL), this function -+ * updates the last activity timestamp. -+ */ -+const checkTerminalActivity = () => { -+ fs.readdir("/dev/pts", (err, files) => { -+ if (err) { -+ console.error(`${LOG_PREFIX} Error reading /dev/pts directory:`, err); -+ return; -+ } -+ -+ const now = Date.now(); -+ const activityDetected = files.some((file) => { -+ const filePath = join("/dev/pts", file); -+ try { -+ const stats = fs.statSync(filePath); -+ const mtime = new Date(stats.mtime).getTime(); -+ return now - mtime < CHECK_INTERVAL; -+ } catch (error) { -+ console.error(`${LOG_PREFIX} Error reading file stats:`, error); -+ return false; -+ } -+ }); -+ -+ if (activityDetected) { -+ updateLastActivityTimestamp(); -+ } -+ }); -+}; -+ -+/** + * Updates the last activity timestamp by recording the current timestamp in the idle file and + * refreshing the status bar. The timestamp should be in ISO 8601 format and set to the UTC timezone. + */ @@ -279,16 +225,62 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/server/node/webClientServer.ts +++ sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts -@@ -3,7 +3,7 @@ +@@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createReadStream, promises } from 'fs'; +import { createReadStream, promises, existsSync, writeFileSync } from 'fs'; ++import { readFile } from 'fs/promises' ++import * as path from 'path'; import * as http from 'http'; import * as url from 'url'; import * as cookie from 'cookie'; -@@ -95,6 +95,7 @@ const APP_ROOT = dirname(FileAccess.asFi +@@ -90,12 +91,52 @@ export async function serveFile(filePath + } + } + ++const CHECK_INTERVAL = 60000; // 60 seconds interval + const APP_ROOT = dirname(FileAccess.asFileUri('').fsPath); + ++/** ++ * Checks for terminal activity by reading the /dev/pts directory and comparing modification times of the files. ++ * ++ * The /dev/pts directory is used in Unix-like operating systems to represent pseudo-terminal (PTY) devices. ++ * Each active terminal session is assigned a PTY device. These devices are represented as files within the /dev/pts directory. ++ * When a terminal session has activity, such as when a user inputs commands or output is written to the terminal, ++ * the modification time (mtime) of the corresponding PTY device file is updated. By monitoring the modification ++ * times of the files in the /dev/pts directory, we can detect terminal activity. ++ * ++ * If activity is detected (i.e., if any PTY device file was modified within the CHECK_INTERVAL), this function ++ * updates the last activity timestamp. ++ */ ++const checkTerminalActivity = (idleFilePath: string) => { ++ fs.readdir('/dev/pts', (err, files) => { ++ if (err) { ++ console.error('Error reading /dev/pts directory:', err); ++ return; ++ } ++ ++ const now = new Date(); ++ const activityDetected = files.some((file) => { ++ const filePath = path.join('/dev/pts', file); ++ try { ++ const stats = fs.statSync(filePath); ++ const mtime = new Date(stats.mtime).getTime(); ++ return now.getTime() - mtime < CHECK_INTERVAL; ++ } catch (error) { ++ console.error('Error reading file stats:', error); ++ return false; ++ } ++ }); ++ ++ if (activityDetected) { ++ fs.writeFileSync(idleFilePath, now.toISOString()); ++ } ++ }); ++}; ++ const STATIC_PATH = `/static`; const CALLBACK_PATH = `/callback`; const WEB_EXTENSION_PATH = `/web-extension-resource`; @@ -296,6 +288,7 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts export class WebClientServer { + private readonly _webExtensionResourceUrlTemplate: URI | undefined; @@ -131,6 +132,9 @@ export class WebClientServer { // callback support return this._handleCallback(res); @@ -306,33 +299,36 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts if (pathname.startsWith(WEB_EXTENSION_PATH) && pathname.charCodeAt(WEB_EXTENSION_PATH.length) === CharCode.Slash) { // extension resource support return this._handleWebExtensionResource(req, res, pathname.substring(WEB_EXTENSION_PATH.length)); -@@ -496,4 +500,29 @@ export class WebClientServer { +@@ -496,4 +500,31 @@ export class WebClientServer { }); return void res.end(data); } + + /** -+ * Handles API requests to retrieve the last activity timestamp. -+ */ -+ private async _handleIdle(req: http.IncomingMessage, res: http.ServerResponse): Promise { -+ try { -+ const tmpDirectory = '/tmp/' -+ const idleFilePath = join(tmpDirectory, '.sagemaker-last-active-timestamp'); -+ -+ // If idle shutdown file does not exist, this indicates the app UI may never been opened -+ // Create the initial metadata file -+ if (!existsSync(idleFilePath)) { -+ const timestamp = new Date().toISOString(); -+ writeFileSync(idleFilePath, timestamp); -+ } -+ -+ const data = await promises.readFile(idleFilePath, 'utf8'); -+ -+ res.statusCode = 200; -+ res.setHeader('Content-Type', 'application/json'); -+ res.end(JSON.stringify({ lastActiveTimestamp: data })); -+ } catch (error) { -+ serveError(req, res, 500, error.message) -+ } -+ } ++ * Handles API requests to retrieve the last activity timestamp. ++ */ ++ private async _handleIdle(req: http.IncomingMessage, res: http.ServerResponse): Promise { ++ try { ++ const tmpDirectory = '/tmp/' ++ const idleFilePath = path.join(tmpDirectory, '.sagemaker-last-active-timestamp'); ++ ++ // If idle shutdown file does not exist, this indicates the app UI may never been opened ++ // Create the initial metadata file ++ if (!existsSync(idleFilePath)) { ++ const timestamp = new Date().toISOString(); ++ writeFileSync(idleFilePath, timestamp); ++ } ++ ++ checkTerminalActivity(idleFilePath); ++ ++ const data = await readFile(idleFilePath, 'utf8'); ++ ++ res.statusCode = 200; ++ res.setHeader('Content-Type', 'application/json'); ++ res.end(JSON.stringify({ lastActiveTimestamp: data })); ++ } catch (error) { ++ serveError(req, res, 500, error.message); ++ } ++ } } + \ No newline at end of file diff --git a/patches/webview.diff b/patches/webview.diff index 25f77c266..6d96dc0de 100644 --- a/patches/webview.diff +++ b/patches/webview.diff @@ -74,10 +74,19 @@ Index: sagemaker-code-editor/vscode/src/vs/workbench/contrib/webview/browser/pre -+ content="default-src 'none'; script-src 'sha256-1qYtPnTQa4VwKNJO61EOhs2agF9TvuQSYIJ27OgzZqI=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> ++ content="default-src 'none'; script-src 'sha256-Oi71Tq4Buohx0KDH3yEbVJUzABnqYv9iVLo420HZXqI=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> { + /** + * @param {MessageEvent} event @@ -344,6 +344,12 @@ const hostname = location.hostname;