diff --git a/atom-extensions/.gitignore b/atom-extensions/.gitignore new file mode 100644 index 000000000..4f5887f2e --- /dev/null +++ b/atom-extensions/.gitignore @@ -0,0 +1 @@ +/**/.idea \ No newline at end of file diff --git a/atom-extensions/atom-commons/.babelrc b/atom-extensions/atom-commons/.babelrc new file mode 100644 index 000000000..3b514bd56 --- /dev/null +++ b/atom-extensions/atom-commons/.babelrc @@ -0,0 +1,15 @@ +{ + "sourceMap": "inline", + "plugins": [ + ["add-module-exports", {}], + ["transform-async-to-generator", {}], + ["transform-decorators-legacy", {}], + ["transform-class-properties", {}], + ["transform-es2015-modules-commonjs", {"strictMode": false}], + ["transform-export-extensions", {}], + ["transform-do-expressions", {}], + ["transform-function-bind", {}], + ["transform-object-rest-spread", {}], + ["transform-flow-strip-types", {}], + ] +} \ No newline at end of file diff --git a/atom-extensions/atom-commons/.gitignore b/atom-extensions/atom-commons/.gitignore new file mode 100644 index 000000000..246d599d5 --- /dev/null +++ b/atom-extensions/atom-commons/.gitignore @@ -0,0 +1,2 @@ +/dist +/node_modules \ No newline at end of file diff --git a/atom-extensions/atom-commons/package.json b/atom-extensions/atom-commons/package.json new file mode 100644 index 000000000..1584428fe --- /dev/null +++ b/atom-extensions/atom-commons/package.json @@ -0,0 +1,52 @@ +{ + "name": "atom-commons", + "version": "0.1.0", + "description": "Atom Commons for STS4", + "repository": "https://github.com/spring-projects/sts4", + "license": "MIT", + "engines": { + "atom": ">=1.17.0" + }, + "main": "./dist/index", + "dependencies": { + "atom-languageclient": "0.1.2", + "decompress": "^4.2.0", + "portfinder": "^1.0.13", + "remote-file-size": "^3.0.3" + }, + "scripts": { + "clean": "rm -rf dist", + "compile": "babel src --out-dir dist", + "postinstall": "postinstall-build --only-as-dependency build \"npm run compile\"", + "prepublish": "npm run clean && npm run compile", + "watch": "babel src --out-dir dist -w" + }, + "atomTranspilers": [ + { + "glob": "{src}/**/*.js", + "transpiler": "atom-babel6-transpiler", + "options": { + "cacheKeyFiles": [ + "package.json", + ".babelrc" + ] + } + } + ], + "devDependencies": { + "atom-babel6-transpiler": "0.0.3", + "atom-mocha-test-runner": "^1.0.0", + "babel-cli": "^6.24.0", + "babel-core": "6.22.1", + "babel-plugin-add-module-exports": "0.2.1", + "babel-plugin-transform-async-to-generator": "6.22.0", + "babel-plugin-transform-class-properties": "6.23.0", + "babel-plugin-transform-decorators-legacy": "1.3.4", + "babel-plugin-transform-do-expressions": "6.22.0", + "babel-plugin-transform-es2015-modules-commonjs": "6.23.0", + "babel-plugin-transform-export-extensions": "6.22.0", + "babel-plugin-transform-flow-strip-types": "6.22.0", + "babel-plugin-transform-function-bind": "6.22.0", + "babel-plugin-transform-object-rest-spread": "6.23.0" + } +} diff --git a/atom-extensions/atom-commons/src/index.js b/atom-extensions/atom-commons/src/index.js new file mode 100644 index 000000000..e21efad92 --- /dev/null +++ b/atom-extensions/atom-commons/src/index.js @@ -0,0 +1,3 @@ +import { JarLanguageClient } from './jar-language-client'; + +export { JarLanguageClient }; \ No newline at end of file diff --git a/atom-extensions/atom-commons/src/jar-language-client.js b/atom-extensions/atom-commons/src/jar-language-client.js new file mode 100644 index 000000000..8fbb10801 --- /dev/null +++ b/atom-extensions/atom-commons/src/jar-language-client.js @@ -0,0 +1,184 @@ +const cp = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const url = require('url'); +const remote = require('remote-file-size'); +const PortFinder = require('portfinder'); +const net = require('net'); +const rpc = require('vscode-jsonrpc'); +const {AutoLanguageClient, DownloadFile} = require('atom-languageclient'); + +export class JarLanguageClient extends AutoLanguageClient { + + constructor(serverDownloadUrl, serverHome) { + super(); + + this.serverHome = serverHome; + this.serverDownloadUrl = serverDownloadUrl; + this.serverLauncherJar = path.basename(url.parse(this.serverDownloadUrl).pathname); + } + + startServerProcess () { + // //TODO: Remove when debugging is over + atom.config.set('core.debugLSP', true); + + let childProcess; + + return new Promise((resolve, reject) => { + PortFinder.getPort((err, port) => { + let server = net.createServer(socket => { + server.close(); + this.socket = socket; + resolve(childProcess); + }); + + server.listen(port, 'localhost', () => { + this.launchProcess(port).then(p => childProcess = p); + }); + + }); + }); + } + + launchProcess(port) { + const command = this.findJavaExecutable('java'); + + return this.compatibleJavaVersion(command).then(version => { + if (version) { + var args = this.launchVmArgs(version); + if (version >= 9) { + args.push('--add-modules=java.se.ee'); + } + return this.getOrInstallLauncher().then(launcher => this.doLaunchProcess(command, launcher, port, args)); + } else { + this.logger.error('Java executable is not Java 8 or higher'); + } + }); + } + + launchVmArgs(version) { + return []; + } + + doLaunchProcess(javaExecutable, launcher, port, args=[]) { + let vmArgs = args.concat([ + `-Dserver.port=${port}`, + '-jar', + launcher + ]); + this.logger.debug(`starting "${javaExecutable} ${vmArgs.join(' ')}"`); + return cp.spawn(javaExecutable, vmArgs, { cwd: this.serverHome }) + } + + getOrInstallLauncher() { + const fullLauncherJar = path.join(this.serverHome, this.serverLauncherJar); + return this.fileExists(fullLauncherJar).then(doesExist => + doesExist ? fullLauncherJar : this.installServer().then(() => fullLauncherJar) + ); + } + + installServer () { + const localFileName = path.join(this.serverHome, this.serverLauncherJar); + this.logger.log(`Downloading ${this.serverDownloadUrl} to ${localFileName}`); + return this.fileExists(this.serverHome) + .then(doesExist => { if (!doesExist) fs.mkdir(this.serverHome) }) + .then(() => this.remoteFileSize(this.serverDownloadUrl)) + .then((size) => DownloadFile(this.serverDownloadUrl, localFileName, (bytesDone, percent) => this.handleDownlaodPercentChange(bytesDone, size, percent), size)) + .then(() => this.fileExists(path.join(this.serverHome, this.serverLauncherJar))) + .then(doesExist => { if (!doesExist) throw Error(`Failed to install the ${this.getServerName()} language server`) }) + .then(() => this.handleServerInstalled()) + .then(() => Promise.resolve(true)); + } + + handleDownlaodPercentChange(bytesDone, size, percent) { + + } + + handleServerInstalled() { + + } + + preInitialization(connection) { + connection.onCustom('language/status', (e) => this.updateStatusBar(`${e.type.replace(/^Started$/, '')} ${e.message}`)); + } + + remoteFileSize(url) { + return new Promise((resolve, reject) => { + remote(url, (e,s) => { + if (e) { + reject(e); + } else { + resolve(s); + } + }); + }); + } + + fileExists (path) { + return new Promise((resolve, reject) => { + fs.access(path, fs.R_OK, error => { + resolve(!error || error.code !== 'ENOENT'); + }) + }) + } + + // createRpcConnection(process) { + // let connection = super.createRpcConnection(process); + // connection.trace(rpc.Trace.Messages, console); + // return connection; + // } + + findJavaExecutable(binname) { + binname = this.correctBinname(binname); + + // First search each JAVA_HOME bin folder + if (process.env['JAVA_HOME']) { + let workspaces = process.env['JAVA_HOME'].split(path.delimiter); + for (let i = 0; i < workspaces.length; i++) { + let binpath = path.join(workspaces[i], 'bin', binname); + if (fs.existsSync(binpath)) { + return binpath; + } + } + } + + // Then search PATH parts + if (process.env['PATH']) { + let pathparts = process.env['PATH'].split(path.delimiter); + for (let i = 0; i < pathparts.length; i++) { + let binpath = path.join(pathparts[i], binname); + if (fs.existsSync(binpath)) { + return binpath; + } + } + } + + // Else return the binary name directly (this will likely always fail downstream) + return null; + } + + correctBinname(binname) { + if (process.platform === 'win32') + return binname + '.exe'; + else + return binname; + } + + compatibleJavaVersion(javaExecutablePath) { + return new Promise((resolve, reject) => { + cp.execFile(javaExecutablePath, ['-version'], {}, (error, stdout, stderr) => { + cp.execFile(javaExecutablePath, ['-version'], {}, (error, stdout, stderr) => { + if (stderr.indexOf('1.8') >= 0) { + resolve(8); + } else if (stderr.indexOf('java version "9"') >= 0) { + resolve(9); + } else { + resolve(0); + } + }); + }); + }); + } + +} + diff --git a/atom-extensions/atom-manifest-yaml/.gitignore b/atom-extensions/atom-manifest-yaml/.gitignore new file mode 100644 index 000000000..eb7ba7f6c --- /dev/null +++ b/atom-extensions/atom-manifest-yaml/.gitignore @@ -0,0 +1,2 @@ +/node_modules +/server diff --git a/atom-extensions/atom-manifest-yaml/README.MD b/atom-extensions/atom-manifest-yaml/README.MD new file mode 100644 index 000000000..20c4bf2dd --- /dev/null +++ b/atom-extensions/atom-manifest-yaml/README.MD @@ -0,0 +1,10 @@ +#CF Manifest YAML for Atom + +## Dev environment setup: +**Prerequisite**: Node 6.x.x or higher is installed, Atom 1.17 or higher is installed +1. Clone STS4 git repository +2. Navigate to `sts4/atom-extensions/atom-manifest-yaml` folder +3. Run `npm install` +4. Execute `apm link .` from the folder above +5. Perform `Reload Window` in Atom (Cmd-Shift-P opens commands palette, search for `reaload`, select, press `Return`) +6. Open any `manifest.yml` file in Atom observe reconciling, content assist and other IDE features \ No newline at end of file diff --git a/atom-extensions/atom-manifest-yaml/lib/main.js b/atom-extensions/atom-manifest-yaml/lib/main.js new file mode 100644 index 000000000..14bf4d41b --- /dev/null +++ b/atom-extensions/atom-manifest-yaml/lib/main.js @@ -0,0 +1,274 @@ +// const cp = require('child_process'); +// const fs = require('fs'); +const path = require('path'); +// const url = require('url'); +// const remote = require('remote-file-size'); +// const PortFinder = require('portfinder'); +// const net = require('net'); +// const rpc = require('vscode-jsonrpc'); +// const {AutoLanguageClient, DownloadFile} = require('atom-languageclient'); +// +// const serverDownloadUrl = 'https://s3-us-west-1.amazonaws.com/s3-test.spring.io/sts4/fatjars/snapshots/manifest-yaml-language-server-0.0.9-201707121637.jar'; +// const serverLauncherJar = path.basename(url.parse(serverDownloadUrl).pathname); +// +// class ManifestYamlLanguageClient extends AutoLanguageClient { +// getGrammarScopes () { return [ 'source.yaml' ] } +// getLanguageName () { return 'CF-Manifest-YAML' } +// getServerName () { return 'CF Manifest YAML' } +// +// constructor() { +// super(); +// this.statusElement = document.createElement('span') +// this.statusElement.className = 'inline-block' +// } +// +// startServerProcess () { +// // //TODO: Remove when debugging is over +// atom.config.set('core.debugLSP', true); +// +// let childProcess; +// +// return new Promise((resolve, reject) => { +// +// PortFinder.getPort((err, port) => { +// +// let server = net.createServer(socket => { +// console.log('Socket is present!'); +// server.close(); +// this.socket = socket; +// resolve(childProcess); +// }); +// +// server.listen(port, 'localhost', () => { +// this.launchProcess(port).then(p => childProcess = p); +// }); +// +// }); +// +// }); +// } +// +// launchProcess(port) { +// const serverHome = path.join(__dirname, '..', 'server'); +// const command = this.findJavaExecutable('java'); +// +// return this.compatibleJavaVersion(command).then(version => { +// if (version) { +// var args = []; +// if (version >= 9) { +// args.push('--add-modules=java.se.ee'); +// } +// return this.getOrInstallLauncher(serverHome).then(launcher => this.doLaunchProcess(serverHome, command, launcher, port, args)); +// } else { +// this.logger.error('Java executable is not Java 8 or higher'); +// } +// }); +// } +// +// doLaunchProcess(serverHome, javaExecutable, launcher, port, args=[]) { +// let vmArgs = args.concat([ +// `-Dserver.port=${port}`, +// '-Xdebug', +// '-agentlib:jdwp=transport=dt_socket,address=9000,server=y,suspend=n', +// '-Dorg.slf4j.simpleLogger.logFile=manifest-yaml.log', +// '-Dorg.slf4j.simpleLogger.defaultLogLevel=trace', +// '-Djava.util.logging.config.file=logging.properties', +// '-jar', +// launcher +// ]); +// this.logger.debug(`starting "${javaExecutable} ${vmArgs.join(' ')}"`); +// return cp.spawn(javaExecutable, vmArgs, { cwd: serverHome }) +// } +// +// getOrInstallLauncher (serverHome) { +// const fullLauncherJar = path.join(serverHome, serverLauncherJar); +// return this.fileExists(fullLauncherJar).then(doesExist => +// doesExist ? fullLauncherJar : this.installServer(serverHome).then(() => fullLauncherJar) +// ); +// } +// +// installServer (serverHome) { +// const localFileName = path.join(serverHome, serverLauncherJar); +// this.logger.log(`Downloading ${serverDownloadUrl} to ${localFileName}`); +// return this.fileExists(serverHome) +// .then(doesExist => { if (!doesExist) fs.mkdir(serverHome) }) +// .then(() => this.remoteFileSize(serverDownloadUrl)) +// .then((size) => DownloadFile(serverDownloadUrl, localFileName, (bytesDone, percent) => this.updateStatusBar(`downloading ${percent}%`), size)) +// .then(() => this.fileExists(path.join(serverHome, serverLauncherJar))) +// .then(doesExist => { if (!doesExist) throw Error(`Failed to install the ${this.getServerName()} language server`) }) +// .then(() => this.updateStatusBar('installed')) +// // .then(() => fs.unlink(localFileName)) +// .then(() => Promise.resolve(true)); +// } +// +// // Determine whether we should start a server for a given editor if we don't have one yet +// shouldStartForEditor(editor) { +// return super.shouldStartForEditor(editor) && /.*manifest.*\.yml/.test(editor.getFileName()); +// } +// +// +// preInitialization(connection) { +// connection.onCustom('language/status', (e) => this.updateStatusBar(`${e.type.replace(/^Started$/, '')} ${e.message}`)); +// } +// +// updateStatusBar (text) { +// this.statusElement.textContent = `${this.name} ${text}`; +// } +// +// remoteFileSize(url) { +// return new Promise((resolve, reject) => { +// remote(url, (e,s) => { +// if (e) { +// reject(e); +// } else { +// resolve(s); +// } +// }); +// }); +// } +// +// consumeStatusBar (statusBar) { +// this.statusTile = statusBar.addRightTile({ item: this.statusElement, priority: 1000 }); +// } +// +// fileExists (path) { +// return new Promise((resolve, reject) => { +// fs.access(path, fs.R_OK, error => { +// resolve(!error || error.code !== 'ENOENT'); +// }) +// }) +// } +// +// createServerConnection () { +// return rpc.createMessageConnection( +// new rpc.SocketMessageReader(this.socket), +// new rpc.SocketMessageWriter(this.socket) +// ) +// } +// +// createRpcConnection(process) { +// let connection = super.createRpcConnection(process); +// connection.trace(rpc.Trace.Messages, console); +// return connection; +// } +// +// findJavaExecutable(binname) { +// binname = this.correctBinname(binname); +// +// // First search each JAVA_HOME bin folder +// if (process.env['JAVA_HOME']) { +// let workspaces = process.env['JAVA_HOME'].split(Path.delimiter); +// for (let i = 0; i < workspaces.length; i++) { +// let binpath = Path.join(workspaces[i], 'bin', binname); +// if (FS.existsSync(binpath)) { +// return binpath; +// } +// } +// } +// +// // Then search PATH parts +// if (process.env['PATH']) { +// let pathparts = process.env['PATH'].split(path.delimiter); +// for (let i = 0; i < pathparts.length; i++) { +// let binpath = path.join(pathparts[i], binname); +// if (fs.existsSync(binpath)) { +// return binpath; +// } +// } +// } +// +// // Else return the binary name directly (this will likely always fail downstream) +// return null; +// +// // return '/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/bin/java'; +// +// // return '/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/bin/java'; +// } +// +// correctBinname(binname) { +// if (process.platform === 'win32') +// return binname + '.exe'; +// else +// return binname; +// } +// +// compatibleJavaVersion(javaExecutablePath) { +// return new Promise((resolve, reject) => { +// cp.execFile(javaExecutablePath, ['-version'], {}, (error, stdout, stderr) => { +// cp.execFile(javaExecutablePath, ['-version'], {}, (error, stdout, stderr) => { +// if (stderr.indexOf('1.8') >= 0) { +// resolve(8); +// } else if (stderr.indexOf('java version "9"') >= 0) { +// resolve(9); +// } else { +// resolve(0); +// } +// }); +// }); +// }); +// } +// +// } +// +// module.exports = new ManifestYamlLanguageClient(); + +const { JarLanguageClient } = require('atom-commons'); + +class ManifestYamlLanguageClient extends JarLanguageClient { + + constructor() { + super( + 'https://s3-us-west-1.amazonaws.com/s3-test.spring.io/sts4/fatjars/snapshots/manifest-yaml-language-server-0.0.9-201707192158.jar', + path.join(__dirname, '..', 'server') + ); + + this.statusElement = document.createElement('span'); + this.statusElement.className = 'inline-block'; + } + + getGrammarScopes() { + return ['source.yaml'] + } + + getLanguageName() { + return 'CF-Manifest-YAML' + } + + getServerName() { + return 'CF Manifest YAML' + } + + handleDownlaodPercentChange(bytesDone, size, percent) { + this.updateStatusBar(`downloading ${percent}%`); + } + + handleServerInstalled() { + this.updateStatusBar('installed'); + } + + // Determine whether we should start a server for a given editor if we don't have one yet + shouldStartForEditor(editor) { + return super.shouldStartForEditor(editor) && /.*manifest.*\.yml/.test(editor.getFileName()); + } + + consumeStatusBar (statusBar) { + this.statusTile = statusBar.addRightTile({ item: this.statusElement, priority: 1000 }); + } + + updateStatusBar (text) { + this.statusElement.textContent = `${this.name} ${text}`; + } + + launchVmArgs(version) { + return [ + '-Xdebug', + '-agentlib:jdwp=transport=dt_socket,address=9000,server=y,suspend=n', + '-Dorg.slf4j.simpleLogger.logFile=manifest-yaml.log', + '-Dorg.slf4j.simpleLogger.defaultLogLevel=debug', + ]; + + } + +} + +module.exports = new ManifestYamlLanguageClient(); diff --git a/atom-extensions/atom-manifest-yaml/package.json b/atom-extensions/atom-manifest-yaml/package.json new file mode 100644 index 000000000..b98896a6b --- /dev/null +++ b/atom-extensions/atom-manifest-yaml/package.json @@ -0,0 +1,65 @@ +{ + "name": "manifest-yaml", + "main": "./lib/main", + "version": "0.1.0", + "description": "Cloud Foundry Deployment Manifest YAML support for Atom", + "repository": "https://github.com/spring-projects/sts4", + "license": "MIT", + "engines": { + "atom": ">=1.17.0" + }, + "dependencies": { + "atom-languageclient": "0.1.1", + "decompress": "^4.2.0", + "portfinder": "^1.0.13", + "remote-file-size": "^3.0.3", + "atom-commons": "file:../atom-commons" + }, + "scripts": { + "clean": "rm -rf node_modules" + }, + "consumedServices": { + "linter-indie": { + "versions": { + "2.0.0": "consumeLinterV2" + } + }, + "nuclide-datatip.provider": { + "versions": { + "0.0.0": "consumeDatatip" + } + }, + "status-bar": { + "versions": { + "^1.0.0": "consumeStatusBar" + } + } + }, + "providedServices": { + "autocomplete.provider": { + "versions": { + "2.0.0": "provideAutocomplete" + } + }, + "nuclide-code-format.provider": { + "versions": { + "0.0.0": "provideCodeFormat" + } + }, + "nuclide-definition-provider": { + "versions": { + "0.0.0": "provideDefinitions" + } + }, + "nuclide-find-references.provider": { + "versions": { + "0.0.0": "provideFindReferences" + } + }, + "nuclide-outline-view": { + "versions": { + "0.0.0": "provideOutlines" + } + } + } +} diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java index d0e752069..96a424b8b 100644 --- a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/Main.java @@ -20,9 +20,15 @@ public class Main { SimpleLanguageServer server = new ManifestYamlLanguageServer(); public static void main(String[] args) throws IOException, InterruptedException { - File logfile = File.createTempFile("manifest-yaml-language-server", ".log"); + File logfile = null; + if (System.getProperty("org.slf4j.simpleLogger.logFile") == null) { + logfile = File.createTempFile("manifest-yaml-language-server", ".log"); + System.setProperty("org.slf4j.simpleLogger.logFile", logfile.toString()); + } else { + logfile = new File(System.getProperty("org.slf4j.simpleLogger.logFile")); + } System.err.println("Redirecting log output to: "+logfile); - System.setProperty("org.slf4j.simpleLogger.logFile", logfile.toString()); LaunguageServerApp.start(ManifestYamlLanguageServer::new); } + } diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java index 5b4bfee9e..9d6c057bf 100644 --- a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java @@ -56,6 +56,7 @@ import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvid import org.yaml.snakeyaml.Yaml; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; public class ManifestYamlLanguageServer extends SimpleLanguageServer { @@ -66,7 +67,7 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer { private final CfClientConfig cfClientConfig; private final LazyCompletionResolver completionResolver = new LazyCompletionResolver(); //Set to null to disable lazy resolving - private final LanguageId FALLBACK_YML_ID = LanguageId.of("yml"); + private final ImmutableSet FALLBACK_YML_IDS = ImmutableSet.of(LanguageId.of("yml"), LanguageId.of("yaml")); final private ClientParamsProvider defaultClientParamsProvider; public ManifestYamlLanguageServer() { @@ -181,7 +182,7 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer { private void validateOnDocumentChange(IReconcileEngine engine, TextDocument doc) { if (LanguageId.CF_MANIFEST.equals(doc.getLanguageId()) - || FALLBACK_YML_ID.equals(doc.getLanguageId())) { + || FALLBACK_YML_IDS.contains(doc.getLanguageId())) { // // this FALLBACK_YML_ID got introduced to workaround a limitation in LSP4E, which sets the file extension as language ID to the document //